I am currently working on chat application. I need swipe to reply a particular message like WhatsApp in android programmatically. kindly help me to achieve this. thanks in advance.
link I am referring https://github.com/shainsingh89/SwipeToReply.
Well, here I will list the main challenges with the proposed solutions, you can find the entire project on the github over here:
Adding two new chat message layouts for the sender & receiver send_message_quoted_row
& received_message_quoted_row
->> The layout can just be better than that but it's not a big deal for now.
Modifying MessageAdapter
to accept them as a new type, and update the quote text in onBindViewHolder
:
private fun getItemViewType(message: Message): Int {
return if (message.type == MessageType.SEND)
if (message.quotePos == -1) R.layout.send_message_row
else R.layout.send_message_quoted_row
else
if (message.quotePos == -1) R.layout.received_message_row
else R.layout.received_message_quoted_row
}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val message = messageList[position]
holder.txtSendMsg.text = message.body
holder.txtQuotedMsg?.text = message.quote
}
class MessageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var txtSendMsg = view.txtBody!!
var txtQuotedMsg: TextView? = view.textQuote
}
Message
data class to accept the quote and the position of the original message (which is quoted in the current messagedata class Message(var body: String, var time: Long, var type: Int) {
var quote: String = ""
var quotePos: Int = -1
constructor(
body: String,
time: Long,
type: Int,
quote: String,
quotePos: Int
) : this(body, time, type) {
this.quote = quote
this.quotePos = quotePos
}
}
object MessageType {
const val SEND = 1
const val RECEIVED = 2
}
SampleMessages
In WhatsApp: the quote layout appears as a part of the reply layout, and it gradually comes from bottom to top behind the original layout. Also when the cancel button is pressed, it reverses the animation to the bottom.
TextView
, and then using View.Gone/Visible to show the layout.class ResizeAnim(var view: View, private val startHeight: Int, private val targetHeight: Int) :
Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
if (startHeight == 0 || targetHeight == 0) {
view.layoutParams.height =
(startHeight + (targetHeight - startHeight) * interpolatedTime).toInt()
} else {
view.layoutParams.height = (startHeight + targetHeight * interpolatedTime).toInt()
}
view.requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}
And handle this animation within the activity showQuotedMessage()
& hideReplyLayout()
private fun hideReplyLayout() {
val resizeAnim = ResizeAnim(reply_layout, mainActivityViewModel.currentMessageHeight, 0)
resizeAnim.duration = ANIMATION_DURATION
Handler().postDelayed({
reply_layout.layout(0, -reply_layout.height, reply_layout.width, 0)
reply_layout.requestLayout()
reply_layout.forceLayout()
reply_layout.visibility = View.GONE
}, ANIMATION_DURATION - 50)
reply_layout.startAnimation(resizeAnim)
mainActivityViewModel.currentMessageHeight = 0
resizeAnim.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
val params = reply_layout.layoutParams
params.height = 0
reply_layout.layoutParams = params
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
}
private fun showQuotedMessage(message: Message) {
edit_message.requestFocus()
val inputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(edit_message, InputMethodManager.SHOW_IMPLICIT)
textQuotedMessage.text = message.body
val height = textQuotedMessage.getActualHeight()
val startHeight = mainActivityViewModel.currentMessageHeight
if (height != startHeight) {
if (reply_layout.visibility == View.GONE)
Handler().postDelayed({
reply_layout.visibility = View.VISIBLE
}, 50)
val targetHeight = height - startHeight
val resizeAnim =
ResizeAnim(
reply_layout,
startHeight,
targetHeight
)
resizeAnim.duration = ANIMATION_DURATION
reply_layout.startAnimation(resizeAnim)
mainActivityViewModel.currentMessageHeight = height
}
}
private fun TextView.getActualHeight(): Int {
textQuotedMessage.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
return this.measuredHeight
}
Especially when the quoted text height need to be expanded/shrink when the user swipes another message of a different height while there is currently a quoted message.
Handled by using getHeight()
function to programmatically inflate the quoted TextView
and set its text to the new text, compare its height to the height of the old text, and manipulate the animation accordingly.
This already covered in the top methods, and I tracked the old height in the ViewModel using currentMessageHeight
integer.
OnClickListener
to the quoted messageMessage
class as a field, when it's -1, then it's not a quoted message; otherwise it is a quoted message.QuoteClickListener
interface in the MessageAdapter
Preview:
Adding a new Message: