Android RecyclerView Interactive Swipe Like Gmail

RecyclerView is a ViewGroup ,that display a scrolling list of elements based on large data sets (or data that frequently changes) .  RecyclerView widget is more flexible and efficient version of ListView.

In the previous tutorial we have seen example
In this tutorial we will see how to fill swiped Portion using canvas by drawing text and svg image resource .



Project Detail
Project Name RecyclerViewSwipeCanvas
Package com.tutorialsbuzz.recyclerviewswipecanvas
Min Sdk Version 22
Target Sdk Version 29
Compile Sdk Version 29
Theme Theme.AppCompat.Light.DarkActionBar

Add RecyclerView to your layout



file : activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rcv"/>

Data Class


file : Model.kt
package com.tutorialsbuzz.recyclerview

data class Model(val name: String, val version: String) {}

To Load Data Into RecyclerView , We will read JSON File Kept Inside Asset folder and map it to above defined data class .

file : main\assets\android_version.json
[
  {
    "name": "Cupcake",
    "version": "Android 1.5"
  },
  {
    "name": "Donut",
    "version": "Android 1.6"
  },
  {
    "name": "Eclairs",
    "version": "Android 2.0-2.1"
  },
  {
    "name": "Froyo",
    "version": "Android 2.2-2.3"
  },
  {
    "name": "Gingerbread",
    "version": "Android 2.3-2.3.7"
  },
  {
    "name": "Honeycomb",
    "version": "Android 3.0-3.2.6"
  },
  {
    "name": "Icecream",
    "version": "Android 4.0-4.0.4"
  },
  {
    "name": "Jellybean",
    "version": "Android 4.1-4.3.1"
  },
  {
    "name": "Kitkat",
    "version": "Android 4.4-4.4.4"
  },
  {
    "name": "Lolipop",
    "version": "Android 5.0-5.1.1"
  },
  {
    "name": "Marshmallow",
    "version": "Android 6.0-6.0.1"
  },
  {
    "name": "Nougat",
    "version": "Android 7.0-7.1.2"
  },
  {
    "name": "Oreo",
    "version": "Android 8.0-8.1"
  },
  {
    "name": "Pie",
    "version": "Android 9.0"
  }
]


Adapter and ViewHolder For RecyclerView .


1. XML Layout For RecyclerView Item

Create  XML Layout file in res/layout and name it row_item.xml , This Layout defines the layout for Items of RecyclerView . Here In this example we have two TextView inside LinearLayout

file : row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:padding="20dp"
        android:layout_height="wrap_content">

    <TextView
            android:id="@+id/txt"
            android:textSize="22sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textStyle="bold"/>

    <TextView
            android:id="@+id/sub_txt"
            android:textSize="18sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textStyle="italic"/>

</LinearLayout>

2.  Adapter and ViewHolder 
  • Create a Adapter That RecyclerView Can Use , Create a class CustomAdapter extend it to RecyclerView.Adapter .
  • Create Inner class ViewHolder extend it to RecyclerView.ViewHolder
file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerview

import android.content.Context
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.tutorialsbuzz.recyclerviewswipecanvas.R
import kotlinx.android.synthetic.main.row_item.view.*

class CustomAdapter(val modelList: List<Model>, val context: Context) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var handler: Handler? = Handler()

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as ViewHolder).bind(modelList.get(position));
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        return ViewHolder(layoutInflater.inflate(R.layout.row_item, parent, false))
    }

    override fun getItemCount(): Int {
        return modelList.size;
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bind(model: Model): Unit {
            itemView.txt.text = model.name
            itemView.sub_txt.text = model.version
        }

    }

    fun undoView(position: Int) {
        handler?.postDelayed({
            notifyItemChanged(position)
        }, 1000)
    }

}

ItemTouchHelper CallBack For RecyclerView


Create an abstract class SwipeToDeleteCallback extend this class to ItemTouchHelper.SimpleCallback .
  1. onSwiped :  Called when a ViewHolder is swiped by the user. At this point, you should update your adapter (e.g. remove the item) and call related Adapter notify event.
  2. getSwipeDirs : This callback Returns the swipe directions for the provided ViewHolder.
    (To disable swipe at particular viewholder return to ItemTouchHelper.ACTION_STATE_IDLE)
  3. onChildDraw : Called by ItemTouchHelper on RecyclerView's onDraw callback.If you would like to customize how your View's respond to user interactions, this is a good place to override.

Drawing to Canvas On Left Right Swipe


Inside onChildDraw Callback of ItemTouchHelper.SimpleCallback , We will fill swiped portion using canvas .
override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,   dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean){
        ................
        ...........
}

We will use the below two parameter for drawing to canvas and to decide on which direction (left or right).

  1. If dx < 0 then it is considered as left swipe .
  2. If dx > 0 then it is considered as right swipe .
  3. colorCanavas : call this to add background to canvas by passing colorCode and proper bound co-ordinates(left,top,right,bottom).
  4. drawTextOnCanvas : call this to add text to canvas by passing string and x-coordinate,y-coordinate 
  5. drawIconOnCanVas : call this to add icon to canvas by passing bitmap and and proper bound co-ordinates(left,top,right,bottom).
file : SwipeToDeleteCallback.kt
package com.tutorialsbuzz.recylerviewswipetodelete

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

abstract class SwipeToDeleteCallback : ItemTouchHelper.SimpleCallback {

    //configure left swipe params
    var leftBG: Int = Color.LTGRAY
    var leftLabel: String = ""
    var leftIcon: Drawable? = null

    //configure right swipe params
    var rightBG: Int = Color.LTGRAY;
    var rightLabel: String = ""
    var rightIcon: Drawable? = null

    var context: Context;

    constructor(context: Context, dragDir: Int, swipeDir: Int) : super(dragDir, swipeDir) {
        this.context = context

    }

    private lateinit var background: Drawable

    var initiated: Boolean = false
    //Setting Swipe Text
    val paint = Paint()

    fun initSwipeView(): Unit {
        paint.setColor(Color.WHITE)
        paint.setTextSize(48f)
        paint.setTextAlign(Paint.Align.CENTER)
        background = ColorDrawable();
        initiated = true;
    }

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        return false
    }


    override fun onChildDraw(
        c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
        dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
    ) {

        Log.d("onChildDraw", "dx: " + dX)

        val itemView = viewHolder.itemView
        if (!initiated) {
            initSwipeView()
        }


        if (dX != 0.0f) {

            if (dX > 0) {
                //right swipe
                val intrinsicHeight = (rightIcon?.getIntrinsicWidth() ?: 0)
                val xMarkTop = itemView.top + ((itemView.bottom - itemView.top) - intrinsicHeight) / 2
                val xMarkBottom = xMarkTop + intrinsicHeight

                colorCanavas(c, rightBG, itemView.left + dX.toInt(), itemView.top, itemView.left, itemView.bottom)
                drawTextOnCanvas(c, rightLabel, (itemView.left + 200).toFloat(), (xMarkTop + 10).toFloat())
                drawIconOnCanVas(
                    c, rightIcon, itemView.left + (rightIcon?.getIntrinsicWidth() ?: 0) + 50,
                    xMarkTop + 20,
                    itemView.left + 2 * (rightIcon?.getIntrinsicWidth() ?: 0) + 50,
                    xMarkBottom + 20
                )

            } else {
                //left swipe
                val intrinsicHeight = (leftIcon?.getIntrinsicWidth() ?: 0)
                val xMarkTop = itemView.top + ((itemView.bottom - itemView.top) - intrinsicHeight) / 2
                val xMarkBottom = xMarkTop + intrinsicHeight

                colorCanavas(
                    c,
                    leftBG,
                    itemView.right + dX.toInt(),
                    itemView.top,
                    itemView.right,
                    itemView.bottom
                )
                drawTextOnCanvas(c, leftLabel, (itemView.right - 200).toFloat(), (xMarkTop + 10).toFloat())
                drawIconOnCanVas(
                    c, leftIcon, itemView.right - 2 * (leftIcon?.getIntrinsicWidth() ?: 0) - 70,
                    xMarkTop + 20,
                    itemView.right - (leftIcon?.getIntrinsicWidth() ?: 0) - 70,
                    xMarkBottom + 20
                )
            }
        }

        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
    }


    fun colorCanavas(canvas: Canvas, canvasColor: Int, left: Int, top: Int, right: Int, bottom: Int): Unit {
        (background as ColorDrawable).color = canvasColor
        background.setBounds(left, top, right, bottom)
        background.draw(canvas)
    }

    fun drawTextOnCanvas(canvas: Canvas, label: String, x: Float, y: Float) {
        canvas.drawText(label, x, y, paint)
    }

    fun drawIconOnCanVas(
        canvas: Canvas, icon: Drawable?, left: Int, top: Int, right: Int, bottom: Int
    ): Unit {
        icon?.setBounds(left, top, right, bottom)
        icon?.draw(canvas)

    }
}

Configure left swipe and right swipe


Inside SwipeToDeleteCallback declare variable for which is responsible drawing background ,label  ,icon inside canvas .

1. Left Swipe Configuration
//configure left swipe params
var leftBG: Int = Color.LTGRAY
var leftLabel: String = ""
var leftIcon: Drawable? = null

//configure left swipe
swipeToDeleteCallback.leftBG = ContextCompat.getColor(this,
                                       R.color.leftSwipeBG)
swipeToDeleteCallback.leftLabel = "Thumbs UP"
swipeToDeleteCallback.leftIcon = AppCompatResources.
      getDrawable(this, R.drawable.ic_thumb_up_black_24dp)


2. Right Swipe Configuration
//configure right swipe params
var rightBG: Int = Color.LTGRAY;
var rightLabel: String = ""
var rightIcon: Drawable? = null

//configure right swipe
swipeToDeleteCallback.rightBG = ContextCompat.getColor(this,
                                       R.color.rightSwipeBG)
swipeToDeleteCallback.rightLabel = "Thumbs Down"
swipeToDeleteCallback.rightIcon = AppCompatResources.
       getDrawable(this, R.drawable.ic_thumb_down_black_24dp)


Activity

  1. Create a koltin class MainActivity.kt and extend it to AppCompatActivity class .
  2. Override onCreate function and set the content of this MainActivity with above defined xml layout (activity_main.xml). 
  3. Read JSON Data From Asset Folder , this will be used for binding data into RecyclerView .
  4. Set Vertical Linear LayoutManager to RecyclerView.
  5. Create Instance of Adapter and set to RecyclerView .
  6. Add CustomItemDecortion to RecyclerView .
  7. Create an instance of ItemTouchHelper by passing SwipeToDeleteCallback as parameter to its constructor.
  8. Attach ItemTouchHelper to RecyclerView .

file : MainActivity.kt
package com.tutorialsbuzz.recyclerviewswipecanvas

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.tutorialsbuzz.recyclerview.CustomAdapter
import com.tutorialsbuzz.recyclerview.Model
import com.tutorialsbuzz.recylerviewswipetodelete.SwipeToDeleteCallback
import kotlinx.android.synthetic.main.activity_main.*
import org.json.JSONArray
import org.json.JSONObject

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        rcv.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)

        val model = readFromAsset();
        val adapter = CustomAdapter(model, this)
        rcv.adapter = adapter;

        rcv.addItemDecoration(SimpleDividerItemDecoration(this))

        val swipeToDeleteCallback =
            object : SwipeToDeleteCallback(this, 0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
                override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                    adapter.undoView(viewHolder.adapterPosition)
                }
            }

        //configure left swipe
        swipeToDeleteCallback.leftBG = ContextCompat.getColor(this, R.color.leftSwipeBG)
        swipeToDeleteCallback.leftLabel = "Thumbs UP"
        swipeToDeleteCallback.leftIcon = AppCompatResources.getDrawable(this, R.drawable.ic_thumb_up_black_24dp)

        //configure right swipe
        swipeToDeleteCallback.rightBG = ContextCompat.getColor(this, R.color.rightSwipeBG)
        swipeToDeleteCallback.rightLabel = "Thumbs Down"
        swipeToDeleteCallback.rightIcon = AppCompatResources.getDrawable(this, R.drawable.ic_thumb_down_black_24dp)

        val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback)
        itemTouchHelper.attachToRecyclerView(rcv)
    }


    private fun readFromAsset(): List<Model> {

        val modeList = mutableListOf<Model>()

        val bufferReader = application.assets.open("android_version.json").bufferedReader()
        val json_string = bufferReader.use {
            it.readText()
        }

        val jsonArray = JSONArray(json_string);
        for (i in 0..jsonArray.length() - 1) {
            val jsonObject: JSONObject = jsonArray.getJSONObject(i)
            val model = Model(jsonObject.getString("name"), jsonObject.getString("version"))
            modeList.add(model)
        }
        return modeList
    }

}



No comments:

Post a Comment