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
    }

}



Read More »

Android RecyclerView Swipe to Delete Undo

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 of deleting items of recyclerView on swiping ,In this tutorial we will how to do for swiped items of recyclerView  .



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


ItemTouchHelper


ItemTouchHelper is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.

Add RecyclerView to your layout


Create XML Layout activity_main.xml , this will be set as content view for launcher Activity (MainActivity.kt) and add RecyclerView to your layout file .



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/recyclerView"/>

Data Class


file : Model.kt
package com.tutorialsbuzz.recyclerviewswipetodeleteundo

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"
  }
]


Image Resource 

Name field from json matches to respective png file name kept inside drawable .

Folder : app\src\main\res\drawable

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 .

Each Row Item Of RecyclerView Includes Regular-layout and swipe layout wrapped inside FrameLayout Please find below diagram and xml code .
  • Regular Layout is shown when items of recyclerView is loaded and when user clicks undo .
  • Swipe layout is shown when user swipes items of recyclerView.

file : row_item.xml
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="wrap_content">

    <!-- Swipe Layout-->
    <include layout="@layout/swipe_row_item" />

    <!-- Regular Layout-->
    <include layout="@layout/regular_row_item" />

</FrameLayout>

file : swipe_row_item.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/swipeLayout"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal"
              android:visibility="visible"
              android:background="@android:color/holo_red_dark"
              android:padding="30dp"
              android:weightSum="3">

    <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="archived"
            android:textColor="@android:color/white"
            android:textSize="24sp"/>


    <TextView
            android:id="@+id/undo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:gravity="end"
            android:paddingBottom="5dp"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:paddingTop="5dp"
            android:text="undo"
            android:textColor="@android:color/white"
            android:textSize="22sp"
            android:textStyle="bold"/>

</LinearLayout>


file : regular_row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:id="@+id/regularLayout"
        android:orientation="vertical">

    <ImageView android:layout_width="80dp"
               android:layout_height="80dp"
               android:id="@+id/img"
               android:contentDescription="@string/app_name"/>

    <TextView
            android:id="@+id/txt"
            android:textSize="22sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="bold"
            android:layout_toEndOf="@+id/img"
            android:layout_marginStart="20dp"/>

    <TextView
            android:id="@+id/sub_txt"
            android:textSize="18sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="italic"
            android:layout_toEndOf="@+id/img"
            android:layout_below="@+id/txt"
            android:layout_marginStart="20dp"/>

</RelativeLayout>

2. Adapter 

Create a Adapter That RecyclerView Can Use , Create a class CustomAdapter extend it to RecyclerView.Adapter .

3. ViewHolder 

Create Inner class ViewHolder extend it to RecyclerView.ViewHolder

file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerviewswipetodeleteundo

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.recyclerviewswipetodeleteundo.R
import kotlinx.android.synthetic.main.regular_row_item.view.*
import kotlinx.android.synthetic.main.swipe_row_item.view.*


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

    private var itemsPendingRemoval: MutableList<Model>?
    var PENDING_REMOVAL_TIMEOUT: Long = 3000
    var handler: Handler? = Handler()
    var pendingRunnables: HashMap<Model, Runnable>? = HashMap()

    init {
        itemsPendingRemoval = mutableListOf<Model>()
    }

    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 {

            if (itemsPendingRemoval!!.contains(model)) {
                //show swipe layout
                itemView.swipeLayout.visibility = View.VISIBLE
                itemView.regularLayout.visibility = View.GONE

                itemView.undo.setOnClickListener({ view ->
                    undoOpt(model)
                })

            } else {
                //show regular layout
                itemView.swipeLayout.visibility = View.GONE
                itemView.regularLayout.visibility = View.VISIBLE

                itemView.txt.text = model.name
                itemView.sub_txt.text = model.version
                val id = context.resources.getIdentifier(model.name.toLowerCase(), "drawable", context.packageName)
                itemView.img.setBackgroundResource(id)
            }
        }

        private fun undoOpt(model: Model) {
            val pendingRemovalRunnable: Runnable? = pendingRunnables?.get(model)
            pendingRunnables?.remove(model)
            if (pendingRemovalRunnable != null)
                handler?.removeCallbacks(pendingRemovalRunnable)
            itemsPendingRemoval?.remove(model)
            // this will rebind the row in "normal" state
            notifyItemChanged(modelList.indexOf(model))
        }

    }


    fun pendingRemoval(position: Int) {

        val data = modelList.get(position)
        if (!itemsPendingRemoval!!.contains(data)) {
            itemsPendingRemoval?.add(data)
            // this will redraw row in "undo" state
            notifyItemChanged(position)
            // let's create, store and post a runnable to remove the data
            val pendingRemovalRunnable = Runnable {
                remove(modelList.indexOf(data))
            }

            handler?.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT)
            // pendingRunnables!![data] = pendingRemovalRunnable
            pendingRunnables?.put(data, pendingRemovalRunnable)
        }
    }

    fun remove(position: Int) {
        val data = modelList.get(position)
        if (itemsPendingRemoval!!.contains(data)) {
            itemsPendingRemoval?.remove(data)
        }
        if (modelList.contains(data)) {
            //dataList.remove(position)
            modelList.removeAt(position)
            notifyItemRemoved(position)
        }
    }

    fun isPendingRemoval(position: Int): Boolean {
        val data = modelList.get(position)
        return itemsPendingRemoval!!.contains(data)
    }

}

ItemTouchHelper CallBack For RecyclerView


Create an abstract class SwipeToDeleteCallback extend this class to ItemTouchHelper.SimpleCallback
  • 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.
  • getSwipeDirs : This callback Returns the swipe directions for the provided ViewHolder.    (Note : To disable swipe at particular viewholder return to ItemTouchHelper.ACTION_STATE_IDLE ) .
file : SwipeToDeleteCallback.kt
package com.tutorialsbuzz.recylerviewswipetodelete

import android.content.Context
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView


abstract class SwipeToDeleteCallback(var context: Context, dragDir: Int, swipeDir: Int) :
    ItemTouchHelper.SimpleCallback(dragDir, swipeDir) {


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

Attaching ItemTouchHelper to RecyclerView .


Creates an instance of ItemTouchHelper that will work with the given Callback.
Call attachToRecyclerView on ItemTouchHelper instance by passing recyclerView .

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

  override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
   if (adapter.isPendingRemoval(viewHolder.adapterPosition)) {
    return ItemTouchHelper.ACTION_STATE_IDLE
   }
   return super.getSwipeDirs(recyclerView, viewHolder)
  }
 }

val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)

Swipe Undo


Pending Removals 
  • When user swipes add item to pending removal list  and show swipe layout and hide regular layout.
  • Using Handler Add Runnable to message queue , Runnable will execute after the specified amount of time elapses ,
  • Inside runnable remove the item at the swiped position .

Undo Remove / delete 
  • when undo is clicked remove the handler Callback  at swiped postion and hide swipe layout and show regular layout .

Disable Swipe at Position in RecyclerView
  • If user has already swiped once then disable swipe for that particular item of recyclerView , to do that Inside getSwipeDirs we will check if item is already added to pendingremoval list by return ItemTouchHelper.ACTION_STATE_IDLE else return super.getSwipeDirs(recyclerView, viewHolder)

Activity


file : MainActivity.kt
package com.tutorialsbuzz.recyclerviewswipetodeleteundo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
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)

        val layoutManager: RecyclerView.LayoutManager? = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
        recyclerView.layoutManager = layoutManager

        val modelList = readFromAsset();
        val adapter = CustomAdapter(modelList, this)
        recyclerView.adapter = adapter;

        recyclerView.addItemDecoration(SimpleDividerItemDecoration(this))

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

                override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
                    if (adapter.isPendingRemoval(viewHolder.adapterPosition)) {
                        return ItemTouchHelper.ACTION_STATE_IDLE
                    }
                    return super.getSwipeDirs(recyclerView, viewHolder)
                }
            }

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

    private fun readFromAsset(): MutableList<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
    }
}


Read More »

Android RecyclerView Swipe To Delete

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 basic example of recyclerView , In this tutorial deleting items of recyclerView on swiping .




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

ItemTouchHelper :

ItemTouchHelper is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.

Lets see example of swiping (LEFT and RIGHT) items of recyclerView

Add RecyclerView To XML Layout


Create XML Layout activity_main.xml , this will be set as content view for launcher Activity (MainActivity.kt) and add RecyclerView to your layout file .


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/recyclerView"/>


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"
  }
]


Image Resource 

Name field from json matches to respective png file name kept inside drawable .

Folder : app\src\main\res\drawable

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"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:orientation="vertical">

    <ImageView android:layout_width="80dp"
               android:layout_height="80dp"
               android:id="@+id/img"
               android:contentDescription="@string/app_name"/>

    <TextView
            android:id="@+id/txt"
            android:textSize="22sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="bold"
            android:layout_toEndOf="@+id/img"
            android:layout_marginStart="20dp"/>

    <TextView
            android:id="@+id/sub_txt"
            android:textSize="18sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="italic"
            android:layout_toEndOf="@+id/img"
            android:layout_below="@+id/txt"
            android:layout_marginStart="20dp"/>

</RelativeLayout>

2. Adapter

Create a Adapter That RecyclerView Can Use , Create a class CustomAdapter extend it to RecyclerView.Adapter .

3. ViewHolder 

Create Inner class ViewHolder extend it to RecyclerView.ViewHolder

file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerview

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

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

    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

            val id = context.resources.getIdentifier(model.name.toLowerCase(), "drawable", context.packageName)
            itemView.img.setBackgroundResource(id)
        }

    }

}


ItemTouchHelper CallBack For RecyclerView 


Create an abstract class SwipeToDeleteCallback extend this class to ItemTouchHelper.Callback override the getMovementFlags , onMove method .
  1. getMovementFlags : Should return a composite flag which defines the enabled move directions in each state (idle, swiping, dragging).Instead of composing this flag manually, you can use makeMovementFlags(int, int) or makeFlag(int, int).For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to swipe by swiping RIGHT, you can return

    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
     val swipeFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
     return makeMovementFlags(0, swipeFlag)
    }

  2. onMove : Called when onMove(RecyclerView, ViewHolder, ViewHolder) returns true , Upon receiving this callback, you should move the item from the old position to target position .
  3. 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.

file : SwipeToDeleteCallback.kt
package com.tutorialsbuzz.recylerviewswipetodelete

import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

abstract class SwipeToDeleteCallback : ItemTouchHelper.Callback() {

    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        val swipeFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        return makeMovementFlags(0, swipeFlag)
    }

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


Attaching ItemTouchHelper to RecyclerView .

  1. Creates an instance of ItemTouchHelper that will work with the given Callback.
  2. Call attachToRecyclerView on ItemTouchHelper instance by passing recyclerView.attachToRecyclerView Attaches the ItemTouchHelper to the provided RecyclerView.
val swipeToDeleteCallback = object : SwipeToDeleteCallback() {
 override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
  val pos = viewHolder.adapterPosition
  modelList.removeAt(pos)
  adapter.notifyItemRemoved(pos)
 }
}

val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)

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.recylerviewswipetodelete

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
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 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)

        val modelList = readFromAsset();
        val adapter = CustomAdapter(modelList, this)

        recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
        recyclerView.adapter = adapter;
        recyclerView.addItemDecoration(SimpleDividerItemDecoration(this))

        val swipeToDeleteCallback = object : SwipeToDeleteCallback() {
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                val pos = viewHolder.adapterPosition
                modelList.removeAt(pos)
                adapter.notifyItemRemoved(pos)
            }
        }

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

    private fun readFromAsset(): MutableList<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
    }

}



Read More »

Android RecyclerView StaggeredGrid Layout

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 .

RecyclerView  StaggeredGridLayoutManager  allows to lay out items of recyclerView in a staggered grid format , Lets see example .



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

XML Layout



Create XML Layout activity_main.xml , this will be set as content view for launcher Activity (MainActivity.kt) and add RecyclerView to your layout file

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

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"
  }
]

Image Resource 

Name field from json matches to respective png file name kept inside drawable .

Folder : app\src\main\res\drawable

Adapter and ViewHolder For RecyclerView 


1. Create StaggeredGrid cell layout

Create XML Layout file in res/layout and name it row_item.xml .

file : row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="18dp"
        android:background="@drawable/circle_bg"
        android:gravity="center"
        android:orientation="vertical">

    <com.tutorialsbuzz.recyclerviewstaggeredgrid.CirleImageView
            android:layout_width="80dp"
            android:layout_height="90dp"
            android:id="@+id/img"
            app:civ_border_width="8dp"
            app:civ_border_color="#e5e5e5"
            android:contentDescription="@string/app_name"/>

    <TextView
            android:id="@+id/txt"
            android:textSize="18sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="bold"/>

    <TextView
            android:id="@+id/sub_txt"
            android:textSize="16sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="italic"/>

</LinearLayout>

2. Adapter class 

Create a Adapter That RecyclerView Can Use ,Create a class CustomAdapter extend it to RecyclerView.Adapter .

RecyclerView.Adapter has three abstract methods that we must override .
  1. onCreateViewHolder() : Inside this method we specify the layout that each item of the RecyclerView should use .onCreateViewHolder has return type of RecyclerView.ViewHolder which represent each row of recyclerView . Using Inflator get the view of above defined row_item and pass it to viewholder constructor and then return.
  2. onBindViewHolder() : Inside this method data will be displayed at the specified position .
  3. getItemCount() : Returns the total number of items in the data set held by the adapter.
3. ViewHolder

Create Inner class ViewHolder extend it to RecyclerView.ViewHolder , add bind function which takes above defined model data class object .

file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerview

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


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

    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;
    }

    lateinit var mClickListener: ClickListener

    fun setOnItemClickListener(aClickListener: ClickListener) {
        mClickListener = aClickListener
    }

    interface ClickListener {
        fun onClick(pos: Int, aView: View)
    }

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

        init {
            itemView.setOnClickListener(this)
        }

        override fun onClick(p0: View?) {
            mClickListener.onClick(adapterPosition, itemView)
        }

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

            val id = context.resources.getIdentifier(model.name.toLowerCase(), "drawable", context.packageName)
            itemView.img.setImageResource(id)
        }

    }

}

Applying StaggeredGridLayoutManager To RecyclerView 


StaggeredGridLayoutManager  : 

StaggeredGridLayoutManager is a subclass of RecyclerView.LayoutManager , A LayoutManager that lays out children in a staggered grid formation . It supports horizontal & vertical layout as well as an ability to layout children in reverse .

StaggeredGridLayoutManager(int spanCount, int orientation)

The Above  Constructor Creates Vertical StaggeredGridLayoutManager and it takes  two parameters
  1. spanCount : If orientation is vertical, spanCount is number of columns. If orientation is horizontal, spanCount is number of rows.
  2. orientation : Sets the orientation (VERTICAL HORIZONTAL).
Create an instance of StaggeredGridLayoutManager and set it to recyclerView layoutManager.

recyclerView.layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)

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 StaggeredGridLayoutManager to RecyclerView.
  5. Create Instance of Adapter and set to RecyclerView .
file : MainActivity.kt
package com.tutorialsbuzz.recyclerviewstaggeredgrid

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.tutorialsbuzz.recyclerview.CustomAdapter
import com.tutorialsbuzz.recyclerview.Model
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)

        val model = readFromAsset();

        val adapter = CustomAdapter(model, this)
        recyclerView.layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
        recyclerView.adapter = adapter;
        adapter.setOnItemClickListener(object : CustomAdapter.ClickListener {
            override fun onClick(pos: Int, aView: View) {
                Toast.makeText(this@MainActivity, model.get(pos).name, Toast.LENGTH_SHORT).show()
            }
        })

    }


    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
    }

}

Read More »

Android RecyclerView GridLayout

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 .

RecyclerView's GridLayoutManger allows to lay out items of recyclerView in grid format , Lets see example .



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

Add RecyclerView to your layout



Create XML Layout activity_main.xml , this will be set as content view for launcher Activity (MainActivity.kt) and add RecyclerView to your layout file

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

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"
  }
]


Image Resource 

Name field from json matches to respective png file name kept inside drawable .

Folder : app\src\main\res\drawable

Adapter and ViewHolder For RecyclerView .


1. Create Grid Cell Layout
 
Create XML Layout file in res/layout and name it row_item.xml .

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

    <com.tutorialsbuzz.recyclerviewgrid.CirleImageView
            android:layout_width="60dp"
            android:layout_height="60dp"
            app:civ_border_width="8dp"
            app:civ_border_color="#e5e5e5"
            android:id="@+id/img"
            android:contentDescription="@string/app_name"/>

    <TextView
            android:id="@+id/txt"
            android:textSize="14sp"
            android:text="Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textStyle="bold"/>

    <TextView
            android:id="@+id/sub_txt"
            android:textSize="14sp"
            android:text="Title"
            android:visibility="gone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textStyle="italic"/>

</LinearLayout>

2. Adapter class

Create a Adapter That RecyclerView Can Use ,Create a class CustomAdapter extend it to RecyclerView.Adapter .

RecyclerView.Adapter has three abstract methods that we must override .
  1. onCreateViewHolder() : Inside this method we specify the layout that each item of the RecyclerView should use .onCreateViewHolder has return type of RecyclerView.ViewHolder which represent each row of recyclerView . Using Inflator get the view of above defined row_item and pass it to viewholder constructor and then return.
  2. onBindViewHolder() : Inside this method data will be displayed at the specified position .
  3. getItemCount() : Returns the total number of items in the data set held by the adapter.

3. ViewHolder

Create Inner class ViewHolder extend it to RecyclerView.ViewHolder , add bind function which takes above defined model data class object 

file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerview

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


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

    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;
    }

    lateinit var mClickListener: ClickListener

    fun setOnItemClickListener(aClickListener: ClickListener) {
        mClickListener = aClickListener
    }

    interface ClickListener {
        fun onClick(pos: Int, aView: View)
    }

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

        init {
            itemView.setOnClickListener(this)
        }

        override fun onClick(p0: View?) {
            mClickListener.onClick(adapterPosition, itemView)
        }

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

            val id = context.resources.getIdentifier(model.name.toLowerCase(), "drawable", context.packageName)
            itemView.img.setImageResource(id)
        }

    }

}

Applying GridLayoutManger To RecyclerView 


GridLayoutManger is a subclass of RecyclerView.LayoutManager , This class lay out items of recyclerView in grid format .
GridLayoutManager(Context context, int spanCount)

 The Above  Constructor Creates Vertical GridLayoutManager and it takes  two parameters   
  1.  Context: Current context, will be used to access resources.
  2.  int: The number of columns in the grid 
Create an instance of GridLayoutManager and set it to recyclerView layoutManager.

recyclerView.layoutManager = GridLayoutManager(this, 4)

Activity


  1. Override onCreate function and set the content of this MainActivity with above defined xml layout (activity_main.xml). 
  2. Read JSON Data From Asset Folder , this will be used for binding data into RecyclerView .
  3. Set GridLayoutManager to RecyclerView.
  4. Create Instance of Adapter and set to RecyclerView .

file : MainActivity.kt
package com.tutorialsbuzz.recyclerviewgrid

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.tutorialsbuzz.recyclerview.CustomAdapter
import com.tutorialsbuzz.recyclerview.Model
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)

        val model = readFromAsset();

        val adapter = CustomAdapter(model, this)

        recyclerView.layoutManager = GridLayoutManager(this, 4)
        recyclerView.adapter = adapter;
        adapter.setOnItemClickListener(object : CustomAdapter.ClickListener {
            override fun onClick(pos: Int, aView: View) {
                Toast.makeText(this@MainActivity, model.get(pos).name, Toast.LENGTH_SHORT).show()
            }
        })
    }


    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
    }

}


Read More »

Android Circular ImageView In Kotlin

Android ImageView class is used to display image resources for example bitmap or drawable resources . Circular image view is prefect for showing profile pic like in twitter , facebook ,youtube app . In this tutorial we will customize AppCompatImageView to show image in circular shape .



Custom Attritube


Create a xml file attrs.xml inside values and add custom attritube that will be used for circular imageview.

file : values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CirleImageView">
        <attr name="civ_border_width" format="dimension" />
        <attr name="civ_border_color" format="color" />
        <attr name="civ_border_overlay" format="boolean" />
        <attr name="civ_fill_color" format="color" />
        <attr name="civ_circle_background_color" format="color" />
    </declare-styleable>
</resources>

Custom CircleImageView class

Create a class CircleImageView and extend it to AppCompatImageView

file : CircleImageView.kt
package com.tutorialsbuzz.androidcircularimage

import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build

import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatImageView

class CirleImageView : AppCompatImageView {

    private val mDrawableRect = RectF()
    private val mBorderRect = RectF()

    private val mShaderMatrix = Matrix()
    private val mBitmapPaint = Paint()
    private val mBorderPaint = Paint()
    private val mCircleBackgroundPaint = Paint()

    private var mBorderColor = DEFAULT_BORDER_COLOR
    private var mBorderWidth = DEFAULT_BORDER_WIDTH
    private var mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR

    private var mBitmap: Bitmap? = null
    private var mBitmapShader: BitmapShader? = null
    private var mBitmapWidth: Int = 0
    private var mBitmapHeight: Int = 0

    private var mDrawableRadius: Float = 0.toFloat()
    private var mBorderRadius: Float = 0.toFloat()

    private var mColorFilter: ColorFilter? = null

    private var mReady: Boolean = false
    private var mSetupPending: Boolean = false
    private var mBorderOverlay: Boolean = false
    var isDisableCircularTransformation: Boolean = false
        set(disableCircularTransformation) {
            if (isDisableCircularTransformation == disableCircularTransformation) {
                return
            }

            field = disableCircularTransformation
            initializeBitmap()
        }

    var borderColor: Int
        get() = mBorderColor
        set(@ColorInt borderColor) {
            if (borderColor == mBorderColor) {
                return
            }

            mBorderColor = borderColor
            mBorderPaint.color = mBorderColor
            invalidate()
        }

    var circleBackgroundColor: Int
        get() = mCircleBackgroundColor
        set(@ColorInt circleBackgroundColor) {
            if (circleBackgroundColor == mCircleBackgroundColor) {
                return
            }

            mCircleBackgroundColor = circleBackgroundColor
            mCircleBackgroundPaint.color = circleBackgroundColor
            invalidate()
        }

    /**
     * Return the color drawn behind the circle-shaped drawable.
     *
     * @return The color drawn behind the drawable
     */
    /**
     * Set a color to be drawn behind the circle-shaped drawable. Note that
     * this has no effect if the drawable is opaque or no drawable is set.
     */
    var fillColor: Int
        @Deprecated("Use {@link #getCircleBackgroundColor()} instead.")
        get() = circleBackgroundColor
        @Deprecated("Use {@link #setCircleBackgroundColor(int)} instead.")
        set(@ColorInt fillColor) {
            circleBackgroundColor = fillColor
        }

    var borderWidth: Int
        get() = mBorderWidth
        set(borderWidth) {
            if (borderWidth == mBorderWidth) {
                return
            }

            mBorderWidth = borderWidth
            setup()
        }

    var isBorderOverlay: Boolean
        get() = mBorderOverlay
        set(borderOverlay) {
            if (borderOverlay == mBorderOverlay) {
                return
            }

            mBorderOverlay = borderOverlay
            setup()
        }

    constructor(context: Context) : super(context) {

        init()
    }

    @JvmOverloads
    constructor(context: Context, attrs: AttributeSet, defStyle: Int = 0) : super(context, attrs, defStyle) {

        val a = context.obtainStyledAttributes(attrs, R.styleable.CirleImageView, defStyle, 0)

        mBorderWidth = a.getDimensionPixelSize(R.styleable.CirleImageView_civ_border_width, DEFAULT_BORDER_WIDTH)
        mBorderColor = a.getColor(R.styleable.CirleImageView_civ_border_color, DEFAULT_BORDER_COLOR)
        mBorderOverlay = a.getBoolean(R.styleable.CirleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)

        // Look for deprecated civ_fill_color if civ_circle_background_color is not set
        if (a.hasValue(R.styleable.CirleImageView_civ_circle_background_color)) {
            mCircleBackgroundColor = a.getColor(
                R.styleable.CirleImageView_civ_circle_background_color,
                DEFAULT_CIRCLE_BACKGROUND_COLOR
            )
        } else if (a.hasValue(R.styleable.CirleImageView_civ_fill_color)) {
            mCircleBackgroundColor = a.getColor(
                R.styleable.CirleImageView_civ_fill_color,
                DEFAULT_CIRCLE_BACKGROUND_COLOR
            )
        }

        a.recycle()

        init()
    }

    private fun init() {
        super.setScaleType(SCALE_TYPE)
        mReady = true

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            outlineProvider = OutlineProvider()
        }

        if (mSetupPending) {
            setup()
            mSetupPending = false
        }
    }

    override fun getScaleType(): ImageView.ScaleType {
        return SCALE_TYPE
    }

    override fun setScaleType(scaleType: ImageView.ScaleType) {
        if (scaleType != SCALE_TYPE) {
            throw IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType))
        }
    }

    override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
        if (adjustViewBounds) {
            throw IllegalArgumentException("adjustViewBounds not supported.")
        }
    }

    override fun onDraw(canvas: Canvas) {
        if (isDisableCircularTransformation) {
            super.onDraw(canvas)
            return
        }

        if (mBitmap == null) {
            return
        }

        if (mCircleBackgroundColor != Color.TRANSPARENT) {
            canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint)
        }
        canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint)
        if (mBorderWidth > 0) {
            canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint)
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        setup()
    }

    override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
        super.setPadding(left, top, right, bottom)
        setup()
    }

    override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
        super.setPaddingRelative(start, top, end, bottom)
        setup()
    }

    @Suppress("MemberVisibilityCanBePrivate")
    @Deprecated(
        "Use {@link #setBorderColor(int)} instead",
        ReplaceWith("borderColor = context.resources.getColor(borderColorRes)")
    )
    fun setBorderColorResource(@ColorRes borderColorRes: Int) {
        borderColor = context.resources.getColor(borderColorRes)
    }

    @Suppress("MemberVisibilityCanBePrivate")
    fun setCircleBackgroundColorResource(@ColorRes circleBackgroundRes: Int) {
        circleBackgroundColor = context.resources.getColor(circleBackgroundRes)
    }

    /**
     * Set a color to be drawn behind the circle-shaped drawable. Note that
     * this has no effect if the drawable is opaque or no drawable is set.
     *
     * @param fillColorRes The color resource to be resolved to a color and
     * drawn behind the drawable
     */
    @Deprecated(
        "Use {@link #setCircleBackgroundColorResource(int)} instead.",
        ReplaceWith("setCircleBackgroundColorResource(fillColorRes)")
    )
    fun setFillColorResource(@ColorRes fillColorRes: Int) {
        setCircleBackgroundColorResource(fillColorRes)
    }

    override fun setImageBitmap(bm: Bitmap) {
        super.setImageBitmap(bm)
        initializeBitmap()
    }

    override fun setImageDrawable(drawable: Drawable?) {
        super.setImageDrawable(drawable)
        initializeBitmap()
    }

    override fun setImageResource(@DrawableRes resId: Int) {
        super.setImageResource(resId)
        initializeBitmap()
    }

    override fun setImageURI(uri: Uri?) {
        super.setImageURI(uri)
        initializeBitmap()
    }

    override fun setColorFilter(cf: ColorFilter) {
        if (cf === mColorFilter) {
            return
        }

        mColorFilter = cf
        applyColorFilter()
        invalidate()
    }

    override fun getColorFilter(): ColorFilter? {
        return mColorFilter
    }

    private fun applyColorFilter() {
        mBitmapPaint.colorFilter = mColorFilter
    }

    private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
        if (drawable == null) {
            return null
        }

        if (drawable is BitmapDrawable) {
            return drawable.bitmap
        }

        try {
            val bitmap: Bitmap

            if (drawable is ColorDrawable) {
                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG)
            } else {
                bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, BITMAP_CONFIG)
            }

            val canvas = Canvas(bitmap)
            drawable.setBounds(0, 0, canvas.width, canvas.height)
            drawable.draw(canvas)
            return bitmap
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }

    }

    private fun initializeBitmap() {
        mBitmap = if (isDisableCircularTransformation) {
            null
        } else {
            getBitmapFromDrawable(drawable)
        }
        setup()
    }

    private fun setup() {
        if (!mReady) {
            mSetupPending = true
            return
        }

        if (width == 0 && height == 0) {
            return
        }

        if (mBitmap == null) {
            invalidate()
            return
        }

        mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)

        mBitmapPaint.isAntiAlias = true
        mBitmapPaint.shader = mBitmapShader

        mBorderPaint.style = Paint.Style.STROKE
        mBorderPaint.isAntiAlias = true
        mBorderPaint.color = mBorderColor
        mBorderPaint.strokeWidth = mBorderWidth.toFloat()

        mCircleBackgroundPaint.style = Paint.Style.FILL
        mCircleBackgroundPaint.isAntiAlias = true
        mCircleBackgroundPaint.color = mCircleBackgroundColor

        mBitmapHeight = mBitmap!!.height
        mBitmapWidth = mBitmap!!.width

        mBorderRect.set(calculateBounds())
        mBorderRadius =
            Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f)

        mDrawableRect.set(mBorderRect)
        if (!mBorderOverlay && mBorderWidth > 0) {
            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f)
        }
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f)

        applyColorFilter()
        updateShaderMatrix()
        invalidate()
    }

    private fun calculateBounds(): RectF {
        val availableWidth = width - paddingLeft - paddingRight
        val availableHeight = height - paddingTop - paddingBottom

        val sideLength = Math.min(availableWidth, availableHeight)

        val left = paddingLeft + (availableWidth - sideLength) / 2f
        val top = paddingTop + (availableHeight - sideLength) / 2f

        return RectF(left, top, left + sideLength, top + sideLength)
    }

    private fun updateShaderMatrix() {
        val scale: Float
        var dx = 0f
        var dy = 0f

        mShaderMatrix.set(null)

        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / mBitmapHeight.toFloat()
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f
        } else {
            scale = mDrawableRect.width() / mBitmapWidth.toFloat()
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f
        }

        mShaderMatrix.setScale(scale, scale)
        mShaderMatrix.postTranslate((dx + 0.5f).toInt() + mDrawableRect.left, (dy + 0.5f).toInt() + mDrawableRect.top)

        mBitmapShader!!.setLocalMatrix(mShaderMatrix)
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private inner class OutlineProvider : ViewOutlineProvider() {

        override fun getOutline(view: View, outline: Outline) {
            val bounds = Rect()
            mBorderRect.roundOut(bounds)
            outline.setRoundRect(bounds, bounds.width() / 2.0f)
        }

    }

    companion object {

        private val SCALE_TYPE = ImageView.ScaleType.CENTER_CROP

        private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
        private const val COLORDRAWABLE_DIMENSION = 2

        private const val DEFAULT_BORDER_WIDTH = 0
        private const val DEFAULT_BORDER_COLOR = Color.BLACK
        private const val DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT
        private const val DEFAULT_BORDER_OVERLAY = false
    }

}

Adding CircleImageView To Layout 


<com.tutorialsbuzz.circularimageview.CircleImageView
 android:layout_width="150dp"
 android:layout_height="150dp"
 android:src="@drawable/user_dp" />


Adding Border to CircleImageView .


  • To add Border Width use civ_border_width of CirleImageView specify width in density pixel .
  • To add Border Color use civ_border_color of CircleImageView specify color code of choice .

<com.tutorialsbuzz.circularimageview.CircleImageView
 android:layout_width="150dp"
 android:layout_height="150dp"
 android:src="@drawable/user_dp"
 app:civ_border_color="@android:color/holo_blue_dark"
 app:civ_border_width="10dp" />


Adding a gap between image and image-border


  • Surround circular image inside  framelayout or linear layout , to create a gap adding padding .
  • Create a oval shape drawable and set it layout which surrounds circular image view .
file : circle_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    
    <stroke
        android:width="3dp"
        android:color="@android:color/holo_blue_dark" />

</shape>

<FrameLayout
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/circle_background"
  android:padding="10dp">

  <com.tutorialsbuzz.circularimageview.CircleImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/user_dp" />

</FrameLayout>



Read More »