Adding a colored background with text/icon under swiped row when using Android's RecyclerView

swipe recyclerview android github
android recyclerview swipe item programmatically
android custom swipe view with recyclerview
android recyclerview row swipe
delete row from recyclerview android
swipe one item at a time recyclerview
cardview swipe android
swipe to show delete button android recyclerview github

EDIT: The real problem was that my LinearLayout was wrapped in another layout, which caused the incorrect behavior. The accepted answer by Sanvywell has a better, more complete example of how to draw a color under swiped view than the code snippet I provided in the question.

Now that RecyclerView widget has native support for row swiping with the help of ItemTouchHelper class, I'm attempting to use it in an app where rows will behave similarly to Google's Inbox app. That is, swiping to the left side performs one action and swiping to the right does another.

Implementing the actions themselves was easy using ItemTouchHelper.SimpleCallback's onSwiped method. However, I was unable to find a simple way to set color and icon that should appear under the view that's currently being swiped (like in Google's Inbox app).

To do that, I'm trying to override ItemTouchHelper.SimpleCallback's onChildDraw method like this:

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
                        RecyclerView.ViewHolder viewHolder, float dX, float dY,
                        int actionState, boolean isCurrentlyActive) {
    RecyclerViewAdapter.ViewHolder vh = (RecyclerViewAdapter.ViewHolder) viewHolder;
    LinearLayout ll = vh.linearLayout;

    Paint p = new Paint();
    if(dX > 0) {
        p.setARGB(255, 255, 0, 0);
    } else {
        p.setARGB(255, 0, 255, 0);
    }

    c.drawRect(ll.getLeft(), ll.getTop(), ll.getRight(), ll.getBottom(), p);

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

Determining the swipe direction from dX and setting the appropriate color works as intended, but the coordinates I get from the ViewHolder always correspond to the place where the first LinearLayout was inflated.

How do I get the correct coordinates for the LinearLayout that's in the currently swiped row? Is there an easier way (that doesn't require to override onChildDraw) to set the background color and icon?

I was struggling to implement this feature as well, but you steered me in the right direction.

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        // Get RecyclerView item from the ViewHolder
        View itemView = viewHolder.itemView;

        Paint p = new Paint();
        if (dX > 0) {
            /* Set your color for positive displacement */

            // Draw Rect with varying right side, equal to displacement dX
            c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
                    (float) itemView.getBottom(), p);
        } else {
            /* Set your color for negative displacement */

            // Draw Rect with varying left side, equal to the item's right side plus negative displacement dX
            c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
                    (float) itemView.getRight(), (float) itemView.getBottom(), p);
        }

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

android: Showing custom View under swiped RecyclerView item, First of all, I saw this question: Adding a colored background with text/icon under swiped row when using Android's RecyclerViewHowever, even though the title  If swiped left you will see Red background with delete icon. If swiped right you will see Green background with edit icon. On left swipe the adapter removeItem() method is called. On right swipe the AlertDialog is displayed to edit the data.

The accepted answer does a great job of coloring the background, but did not address drawing the icon.

This worked for me because it both set the background color and drew the icon, without the icon being stretched during the swipe, or leaving a gap between the previous and next items after the swipe.

public static final float ALPHA_FULL = 1.0f;

public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        // Get RecyclerView item from the ViewHolder
        View itemView = viewHolder.itemView;

        Paint p = new Paint();
        Bitmap icon;

        if (dX > 0) {
            /* Note, ApplicationManager is a helper class I created 
               myself to get a context outside an Activity class - 
               feel free to use your own method */

            icon = BitmapFactory.decodeResource(
                    ApplicationManager.getContext().getResources(), R.drawable.myleftdrawable);

            /* Set your color for positive displacement */
            p.setARGB(255, 255, 0, 0);

            // Draw Rect with varying right side, equal to displacement dX
            c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
                    (float) itemView.getBottom(), p);

            // Set the image icon for Right swipe
            c.drawBitmap(icon,
                    (float) itemView.getLeft() + convertDpToPx(16),
                    (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
                    p);
        } else {
            icon = BitmapFactory.decodeResource(
                    ApplicationManager.getContext().getResources(), R.drawable.myrightdrawable);

            /* Set your color for negative displacement */
            p.setARGB(255, 0, 255, 0);

            // Draw Rect with varying left side, equal to the item's right side
            // plus negative displacement dX
            c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
                    (float) itemView.getRight(), (float) itemView.getBottom(), p);

            //Set the image icon for Left swipe
            c.drawBitmap(icon,
                    (float) itemView.getRight() - convertDpToPx(16) - icon.getWidth(),
                    (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
                    p);
        }

        // Fade out the view as it is swiped out of the parent's bounds
        final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
        viewHolder.itemView.setAlpha(alpha);
        viewHolder.itemView.setTranslationX(dX);

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

private int convertDpToPx(int dp){
    return Math.round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}

RecyclerView: Swiping with style ;-) - Paolo Montalto, Implementing swipe to dismiss pattern on a RecyclerView is quite simple, you But how to add the amazing background/icon stuff just like Gmail and other background and icon directly on the canvas where the swiped row is being drawn. set drawable bounds using the viewHolder and dX (horizontal displacement)  Color - To change the color for a Clip Art or Text icon, click the field. In the Select Color dialog, specify a color and then click Choose. The new value appears in the field. Resize - Use the slider to specify a scaling factor in percent to resize an Image, Clip Art, or Text icon.

I'm not sure how these solutions (by @Sanvywell, @HappyKatz and @user2410066) are working for you guys but in my case I needed another check in the onChildDraw method.

Looks like ItemTouchHelper keeps ViewHolders of removed rows in case they need to be restored. It's also calling onChildDraw for those VHs in addition to the VH being swiped. Not sure about memory management implications of this behavior but I needed an additional check in the start of onChildDraw to avoid drawing for "fantom" rows.

if (viewHolder.getAdapterPosition() == -1) {
    return;
}

BONUS PART:

I've also wanted to continue drawing as other rows animate to their new positions after a row is swipe deleted, and I couldn't do it within ItemTouchHelper and onChildDraw. In the end I had to add another item decorator to do it. It goes along these lines:

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getItemAnimator().isRunning()) {
        // find first child with translationY > 0
        // draw from it's top to translationY whatever you want

        int top = 0;
        int bottom = 0;

        int childCount = parent.getLayoutManager().getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getLayoutManager().getChildAt(i);
            if (child.getTranslationY() != 0) {
                top = child.getTop();
                bottom = top + (int) child.getTranslationY();                    
                break;
            }
        }

        // draw whatever you want

        super.onDraw(c, parent, state);
    }
}

UPDATE: I wrote a blog post on recycler view swipe to delete feature. Someone might find it usefull. No 3rd party lib necessary.

blog post git repo

Android RecyclerView Partial Swipe with Delete Button, Adding a colored background with text/icon under swiped row when using Android's RecyclerView. EDIT: The real problem was that my LinearLayout was  How to Change the Text Color of a Substring - Android Studio Tutorial Sign in to add this video to a playlist. and BackGroundColorSpan to change the text color of specific parts of a

HappyKatz solution has a tricky bug. Is there any reason for drawing bitmap when dX==0?? In some cases this causes permanent icon visibility above list item. Also icons become visible above list item when you just touch list item and dX==1. To fix these:

        if (dX > rectOffset) {
            c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
                    (float) itemView.getBottom(), leftPaint);
            if (dX > iconOffset) {
                c.drawBitmap(leftBitmap,
                        (float) itemView.getLeft() + padding,
                        (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - leftBitmap.getHeight()) / 2,
                        leftPaint);
            }
        } else if (dX < -rectOffset) {
            c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
                    (float) itemView.getRight(), (float) itemView.getBottom(), rightPaint);
            if (dX < -iconOffset) {
                c.drawBitmap(rightBitmap,
                        (float) itemView.getRight() - padding - rightBitmap.getWidth(),
                        (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - rightBitmap.getHeight()) / 2,
                        rightPaint);
            }
        }

The Android Book, Here you can assign an action to be performed every time you swipe your finger from a shortcut to one of your most commonly used apps, such as email or text messaging. while choosing a background colour is a simple way of personalising your phone. You can choose how many icons you want per row and column  ItemTouchHelper.SimpleCallback provides certain callback methods like onMove(), onChildDraw(), onSwiped() when the row is swiped. Showing the background view, removing the item from adapter can be done using these callback methods. Below is the code snipped of ItemTouchHelper and attaching it to recycler view.

In order to implement I used the sample code created by Marcin Kitowicz here.

Benefits of this solution:

  1. Uses background view with layout bounds instead of creating a Rectangle which will show on top of any Bitmap or Drawable.
  2. Uses Drawable image opposed to Bitmap which is easier to implement than needing to convert a Drawable into a Bitmap.

The original implementation code can be found here. In order to implement left swipe I used the inverse left and right positioning logic.

override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
    var icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
    var iconLeft = 0
    var iconRight = 0

    val background: ColorDrawable
    val itemView = viewHolder.itemView
    val margin = convertDpToPx(32)
    val iconWidth = icon!!.intrinsicWidth
    val iconHeight = icon.intrinsicHeight
    val cellHeight = itemView.bottom - itemView.top
    val iconTop = itemView.top + (cellHeight - iconHeight) / 2
    val iconBottom = iconTop + iconHeight

    // Right swipe.
    if (dX > 0) {
        icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
        background = ColorDrawable(Color.RED)
        background.setBounds(0, itemView.getTop(), (itemView.getLeft() + dX).toInt(), itemView.getBottom())
        iconLeft = margin
        iconRight = margin + iconWidth
    } /*Left swipe.*/ else {
        icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
        background = ColorDrawable(Color.BLUE)
        background.setBounds((itemView.right - dX).toInt(), itemView.getTop(), 0, itemView.getBottom())
        iconLeft = itemView.right - margin - iconWidth
        iconRight = itemView.right - margin
    }
    background.draw(c)
    icon?.setBounds(iconLeft, iconTop, iconRight, iconBottom)
    icon?.draw(c)
    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}

Android adding RecyclerView Swipe to Delete and Undo, Android tutorial about adding swipe to delete and undo delete This can be achieved using ItemTouchHelper. Planning the layout is very important while adding background view of the row. Add the below resources to your strings.​xml, dimens.xml and colors.xml Application; import android.text. Parsing JSON with volley android custom listview Image and Text using Volley |android tut - Duration: 28:22. Adalia rose fc 32,305 views

101 Essential Android Tips &amp; Tricks, Here you can assign an action to be performed every time you swipe your finger to one of your most commonly used apps, such as email or text messaging. choosing a background colour is a simple way of personalising your phone. icons you want per row and column, as you did earlier with the home screen panels. Android RecyclerView Swipe To Delete Example Tutorial is explained here. We will make a recyclerview such that user swipe to show button, which will delete the row. Android RecyclerView Swipe to delete example is similar like gmail app. We will also implement an UNDO feature, which will restore the deleted row item.

Android swipe menu with RecyclerView, Very simple solution to create swipe menu with RecyclerView without any additional libraries — using ItemTouchHelper Callback for swiping items and Next step is to add layout (activity_main.xml) for our MainActivity: android:​background="?android:attr/selectableItemBackground"> tools:text="Bayern Munchen"/> On the next screen, you can then simply tap the text to select a different color for the font. Tap on any of the colors from the bottom of the display to set it as the color for your text. There are three rows of colors to choose from — swipe on one row to switch the next.

Create a Card-Based Layout, above their containing view group, so the system draws shadows below them. background color of a card, use the card_view:cardBackgroundColor attribute​  Requires an AXML layout file to render each row. This AXML file contains the text and image controls with custom font and color settings. Uses an optional custom selector XML file to set the appearance of the row when it is selected. The Adapter implementation returns a custom layout from the GetView override.

Comments
  • You should generally avoid instantiating objects in onDraw methods. That can impact performance due to number of times onDraw methods are called. You should probably cache the paint object instead of creating it every time anew. From the docs: "Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish." developer.android.com/training/custom-views/custom-drawing.html
  • The code for dX > 0 needs to add the getLeft() value to dX to correctly handle padding on the RecyclerView c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), itemView.getLeft() + dX, (float) itemView.getBottom(), p);
  • @Sanvywell wouldn't it be a good idea to call super.onChildDraw first?
  • @WilliMentzel This solution works by itself, but will not allow for icons and a color background. In order to achieve both I used the implementation outlined here: medium.com/@kitek/…
  • how to add click listener on these icons ? Like gmail app
  • @penguin There is a discussion on just this topic here stackoverflow.com/questions/6845129/… . If you need to be able to interact with the underlying elements, you are probably better served by creating a view underneath the swiped element.
  • Hello, what is here the value of your ALPHA_FULL variable?
  • @SaifBechan final float ALPHA_FULL = 1.0f;
  • @HappyKatz i'm using recyclerView.getContext() to get Context without using external methods.
  • I tried out multiple methods for this, and yours (in the blog) worked the best and also seemed to be the simplest!
  • your blog is not loading now.
  • fixed dead blog link
  • how can i draw icon with text