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

前端 未结 7 504
遥遥无期
遥遥无期 2020-12-02 07:37

EDIT: The real problem was that my LinearLayout was wrapped in another layout, which caused the incorrect behavior. The accepted answer by Sanvywell has

相关标签:
7条回答
  • 2020-12-02 07:50

    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)
    }
    }
    
    0 讨论(0)
  • 2020-12-02 07:51

    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));
    }
    
    0 讨论(0)
  • 2020-12-02 07:54

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-02 07:54

    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

    0 讨论(0)
  • 2020-12-02 08:02

    For people still finding this default, this is the simplest way.

    A simple utility class to add a background, an icon and a label to a RecyclerView item while swiping it left or right.

    insert to Gradle

    implementation 'it.xabaras.android:recyclerview-swipedecorator:1.1'
    

    Override onChildDraw method of ItemTouchHelper class

    @Override
    public void onChildDraw (Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,float dX, float dY,int actionState, boolean isCurrentlyActive){
        new RecyclerViewSwipeDecorator.Builder(MainActivity.this, c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
                .addBackgroundColor(ContextCompat.getColor(MainActivity.this, R.color.my_background))
                .addActionIcon(R.drawable.my_icon)
                .create()
                .decorate();
    
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }
    

    for more info -> https://github.com/xabaras/RecyclerViewSwipeDecorator

    0 讨论(0)
  • 2020-12-02 08:04

    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);
                }
            }
    
    0 讨论(0)
提交回复
热议问题