Skip to content

Instantly share code, notes, and snippets.

@salmaanahmed
Created October 22, 2018 10:35
Show Gist options
  • Save salmaanahmed/602aa264f1b0e66758808eb8c0607596 to your computer and use it in GitHub Desktop.
Save salmaanahmed/602aa264f1b0e66758808eb8c0607596 to your computer and use it in GitHub Desktop.
FlagChatAdapter Gist
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true" >
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:orientation="vertical">
<View
android:id="@+id/flagPadding"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="35dp" />
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:paddingTop="5dp"
android:paddingHorizontal="10dp"
android:text="Lorem ipsum."
android:textColor="@android:color/black" />
<TextView
android:id="@+id/time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="10:00am"
android:paddingHorizontal="10dp"
android:textSize="8sp" />
</LinearLayout>
<FrameLayout
android:id="@+id/line"
android:layout_height="0dp"
android:layout_width="3dp"
android:layout_marginHorizontal="8dp"
android:layout_alignTop="@+id/linearLayout"
android:layout_alignBottom="@+id/linearLayout"
android:background="@color/orange" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:gravity="center"
android:layout_margin="8dp"
android:background="@color/orange"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:textSize="14sp"
android:text="Name"
android:textColor="@android:color/white" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginLeft="@dimen/textview_padding"
android:layout_toLeftOf="@+id/date"
android:layout_centerVertical="true"
android:background="@android:color/darker_gray" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginRight="@dimen/textview_padding"
android:layout_toRightOf="@+id/date"
android:layout_centerVertical="true"
android:background="@android:color/darker_gray" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Yesterday"
android:layout_centerInParent="true"
android:padding="@dimen/textview_padding"
android:textSize="12sp" />
</RelativeLayout>
package sasliderdemo.salmaan.ahmsal.com.flagchatadapter
import android.content.Context
import android.support.v4.content.ContextCompat
import android.view.View
import android.widget.Toast
import java.util.*
/**
* Created by salmaanahmed on 04/09/2018.
* Chat Adapter extended by FlagChatAdapter
*/
class ChatAdapter(context: Context, private var list: ArrayList<Any>) : FlagChatAdapter(context) {
/**
* Name of the person user is chatting with
*/
override val otherName: String get() = "John"
/**
* Size of list i.e. number of messages
*/
override val listSize: Int get() = list.size
/**
* Message on this index
*/
override fun chatMessage(position: Int): String {
return (list[position] as ChatModel).message
}
/**
* Message time on this index
*/
override fun messageTime(position: Int): String {
return (list[position] as ChatModel).time
}
/**
* Message sender on this index
*/
override fun isMe(position: Int): Boolean {
return (list[position] as ChatModel).isMe
}
/**
* Animation on this index
*/
override fun animation(position: Int): Boolean {
return (list[position] as ChatModel).animate
}
/**
* Set animation status on this index
*/
override fun setAnimationStatus(position: Int, animationStatus: Boolean) {
(list[position] as ChatModel).animate = animationStatus
}
/**
* Object type on this index
*/
override fun isChatModel(position: Int): Boolean {
return list[position] is ChatModel
}
/**
* Date string on this index
*/
override fun date(position: Int): String {
val item = list[position] as Calendar
return when {
item.isToday() -> "Today"
item.isYesterday() -> "Yesterday"
else -> item.toDateLong()
}
}
/**
* Bubble color of other person
*/
override fun colorOther(context: Context): Int {
return ContextCompat.getColor(context, R.color.red)
}
/**
* Bubble color of user
*/
override fun colorMe(context: Context): Int {
return ContextCompat.getColor(context, R.color.cyan)
}
/**
* Handle long click event
*/
override fun onMessageLongClicked(position: Int) {
Toast.makeText(context, "Long clicked on position $position", Toast.LENGTH_LONG).show()
}
/**
* If you want to change show time logic, you can override this method.
* I prefer the logic currently implemented in adapter
*/
override fun showTime(position: Int): Boolean {
return super.showTime(position)
}
}
package sasliderdemo.salmaan.ahmsal.com.flagchatadapter
import android.content.Context
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.LinearLayout
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.cell_chat.view.*
import kotlinx.android.synthetic.main.cell_chat_date.view.*
/**
* Created by salmaanahmed on 04/09/2018.
* Extend your recycler view adapter with this adapter and voila!
* You have your animated chat adapter working.
*/
abstract class FlagChatAdapter(val context: Context) : RecyclerView.Adapter<FlagChatAdapter.ViewHolder>() {
private val chatView = 0 //View types - Chat View
private val dateView = 1 //View types - Date View
/**
* return chat message on the position passed as parameter
*/
abstract fun chatMessage(position: Int): String
/**
* return time of message as string format on the position passed as parameter
*/
abstract fun messageTime(position: Int): String
/**
* return message sender on the position passed as parameter
* if its you, return true
*/
abstract fun isMe(position: Int): Boolean
/**
* you must have a variable of animation in the object i.e. if you want to animate or not
*/
abstract fun animation(position: Int): Boolean
/**
* the animation variable must be set to false when animation is performed once
* otherwise flags will animate on every scroll
*/
abstract fun setAnimationStatus(position: Int, animationStatus: Boolean)
/**
* You can implement whatever you want onLongClick event
*/
abstract fun onMessageLongClicked(position: Int)
/**
* Name of the sender
*/
abstract val otherName: String
/**
* You shall simply return list.size
*/
abstract val listSize: Int
/**
* OPTIONAL:
* you can change flag color of your chat message
*/
open fun colorMe(context: Context): Int {
return ContextCompat.getColor(context, R.color.orange)
}
/**
* OPTIONAL:
* you can change flag color of other person's chat message
*/
open fun colorOther(context: Context): Int {
return ContextCompat.getColor(context, R.color.green)
}
/**
* OPTIONAL:
* If your list contains some other data type from chat model i.e. date
* you must return false in that case so adapter can check for date and display the date
* chat message functions will not be called on the position if !isChatModel
*/
open fun isChatModel(position: Int): Boolean {
return true
}
/**
* OPTIONAL:
* If !isChatModel adapter will display date place holder
* it will be populated with the string returned by this function
* you shall return the date or strings such as TODAY or YESTERDAY in this function
*/
open fun date(position: Int): String {
return ""
}
/**
* OPTIONAL:
* This current code will not display time if same user sends message in same time
* You may override this method to change the logic or simply return true if you always want to show time
*/
open fun showTime(position: Int): Boolean {
if (position > 0) {
if (isChatModel(position) && isChatModel(position - 1)) {
if (isMe(position) && isMe(position - 1) && messageTime(position) == messageTime(position - 1)) {
return false
}
}
}
return true
}
/**
* Overriden function to bind view holder
* Decide to show the chat view or date view
* Populate data in views and show animation
*/
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (isChatModel(position)) { // If it is chat model
val chatMessage = chatMessage(position)
val showTime = showTime(position)
val messageTime = messageTime(position)
val isMe = isMe(position)
val animate = animation(position)
// Bind data with viewholder
holder.bindData(chatMessage, showTime, messageTime, isMe, colorMe(context), colorOther(context), otherName)
// If previous message is from same person, hide the flag
if (position > 0 && isChatModel(position - 1) && isMe == isMe(position - 1)) { //Hide Flag
holder.itemView.name.visibility = View.GONE
holder.itemView.flagPadding.visibility = View.GONE
} else { // Otherwise show flag
if (animate) {// If flag is not animated before
holder.itemView.name.visibility = View.INVISIBLE
holder.itemView.flagPadding.visibility = View.VISIBLE
flagAnimation(holder.itemView, position)
} else { // Do not animate flag if its animated before i.e. if user is scrolling list
holder.itemView.name.visibility = View.VISIBLE
holder.itemView.flagPadding.visibility = View.VISIBLE
}
}
holder.itemView.rootView.setOnLongClickListener {
onMessageLongClicked(position)
return@setOnLongClickListener true
}
} else {
// If it is date, populate the textview
holder.itemView.date.text = date(position)
}
}
/**
* Item view type on basis of chat model
*/
override fun getItemViewType(position: Int): Int {
return if (isChatModel(position)) chatView else dateView
}
/**
* Create view holder for both type of objects
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == chatView) {
val v = LayoutInflater.from(parent.context).inflate(R.layout.cell_chat, parent, false)
ViewHolder(v)
} else {
val v = LayoutInflater.from(parent.context).inflate(R.layout.cell_chat_date, parent, false)
ViewHolder(v)
}
}
/**
* Number of messages
*/
override fun getItemCount(): Int {
return listSize
}
/**
* ViewHolder to cache the cells to optimize performance
* Populate the data, set colors, flags, animation etc.
*/
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindData(message: String, showTime: Boolean = true, time: String, isMe: Boolean, colorMe: Int, colorOther: Int, otherName: String) {
setSides(isMe, colorMe, colorOther)
setMessage(message, showTime, time, isMe, otherName)
}
// Set sides, colors, and views according to sender
private fun setSides(isMe: Boolean, colorMe: Int, colorOther: Int) {
if (isMe) {
(itemView.line.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
(itemView.name.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
(itemView.message.layoutParams as LinearLayout.LayoutParams).gravity = Gravity.RIGHT
(itemView.message.layoutParams as LinearLayout.LayoutParams).leftMargin = itemView.context.resources.getDimensionPixelSize(R.dimen.message_padding)
(itemView.message.layoutParams as LinearLayout.LayoutParams).rightMargin = 0
itemView.line.setBackgroundColor(colorMe)
itemView.name.setBackgroundColor(colorMe)
itemView.linearLayout.gravity = Gravity.RIGHT
itemView.name.gravity = Gravity.RIGHT
itemView.time.gravity = Gravity.RIGHT
} else {
(itemView.line.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0)
(itemView.name.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0)
(itemView.message.layoutParams as LinearLayout.LayoutParams).gravity = Gravity.LEFT
(itemView.message.layoutParams as LinearLayout.LayoutParams).rightMargin = itemView.context.resources.getDimensionPixelSize(R.dimen.message_padding)
(itemView.message.layoutParams as LinearLayout.LayoutParams).leftMargin = 0
itemView.line.setBackgroundColor(colorOther)
itemView.name.setBackgroundColor(colorOther)
itemView.linearLayout.gravity = Gravity.LEFT
itemView.name.gravity = Gravity.LEFT
itemView.time.gravity = Gravity.LEFT
}
}
// Populate message, date, sender in the views
private fun setMessage(message: String, showTime: Boolean, time: String, isMe: Boolean, otherName: String) {
itemView.message.text = message
itemView.time.text = time
itemView.name.text = if (isMe) "Me" else otherName
if (showTime) itemView.time.visibility = View.VISIBLE
else itemView.time.visibility = View.GONE
}
}
/**
* Flag animation on the views
*/
private fun flagAnimation(itemView: View, position: Int) {
val translateAnim = AnimationUtils.loadAnimation(itemView.context, R.anim.item_animation_from_bottom)
translateAnim.fillAfter = true
translateAnim.isFillEnabled = true
translateAnim.fillBefore = false
translateAnim.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
itemView.linearLayout.alpha = 0f
itemView.name.visibility = View.VISIBLE
}
override fun onAnimationEnd(animation: Animation) {
itemView.linearLayout.animate().alpha(1.0f).duration = 1000
setAnimationStatus(position, false)
}
override fun onAnimationRepeat(animation: Animation) {}
})
itemView.name.startAnimation(translateAnim)
}
}
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fillAfter="true"
android:fromYDelta="99%p"
android:toYDelta="0%p" />
</set>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment