【问题标题】:Showing custom View under swiped RecyclerView item在刷过的 RecyclerView 项下显示自定义视图
【发布时间】:2020-09-14 23:22:03
【问题描述】:

首先看到这个问题:Adding a colored background with text/icon under swiped row when using Android's RecyclerView

但是,即使标题声明“带有文本/图标”,答案也仅涵盖使用Canvas 对象绘制矩形。

现在我已经绘制了一个绿色矩形;但是我想通过添加“完成”按钮来更清楚地表明会发生什么,就像下面的屏幕截图一样。

我创建了一个自定义视图:

public class ChoosePlanView extends LinearLayout{

    public ChoosePlanView(Context context) {
        super(context);
        init();
    }

    public ChoosePlanView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ChoosePlanView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(){
        View v = inflate(getContext(), R.layout.choose_plan_view, null);
        addView(v);
    }
}

而且布局非常简单,由绿色背景和图标组成:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" 
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/choose_green">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/doneIcon"
        android:layout_gravity="center_horizontal|right"
        android:src="@mipmap/done"/>

</LinearLayout>

我尝试了覆盖方法OnChildDraw

// ChoosePlanView choosePlanView; <- that was declared and initialized earlier, so I don't have to create a new ChoosePlanView everytime in OnChildDraw();

ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {    

/*
    onMove, onSwiped omitted
*/
  public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
       View itemView = viewHolder.itemView;

       if(dX < 0){
            choosePlanView.invalidate();
            //choosePlanView.setBackgroundResource(R.color.delete_red);
            choosePlanView.measure(itemView.getWidth(), itemView.getHeight());
            choosePlanView.layout(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
            c.save();
            c.translate(choosePlanView.getRight() + (int) dX, viewHolder.getAdapterPosition()*itemView.getHeight());

            choosePlanView.draw(c);
            c.restore();
       }

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

而且它有点工作(画我的ChoosePlanView),但有两个问题。

首先它只画了一个绿色的小方块包裹着我的“完成”图标(下面屏幕截图中的红色部分来自注释行choosePlanView.setBackgroundResource(R.color.delete_red);)。但是,在调试器中检查时,视图具有正确的宽度。

第二件事是放置图标的位置。我指定我希望它水平居中并始终“粘”在视图的右侧。但是它移动时,当RecyclerView 项目被滑动时,红色背景表明它也没有放在中心。

我尝试添加行choosePlanView.invalidate(),因为我认为会发生这种情况,因为视图是较早创建的,但重绘似乎没有任何改变。

【问题讨论】:

  • 在您的 onChildDraw 方法中,您正在为复选标记图像绘制背景?如果是这样,图像有 wrap_content/wrap_content 大小......因此背景只是小方块
  • 定位使用相对布局和子级:android:layout_centerVertical="true" android:layout_alignParentRight="true"
  • @VizGhar 在我的 onChildDraw 中,choosePlanView.setBackgroundResource(R.color.delete_red); 仅用于调试目的,以在布局中显示错误的复选标记图像位置。当我评论这一行时,该图像仍然具有 wrap_content/wrap_content 大小,并且根本没有背景。将 LinearLayout 更改为 RelativeLayout 不会更改图像位置。
  • "我试过重写方法 OnChildDraw" 什么是父类?我只是不确定你为什么以编程方式显示 ChoosePlanView
  • @VizGhar 啊,对不起,我已经编辑过了。父类为ItemTouchHelper.SimpleCallback,用于实现刷卡。

标签: android android-recyclerview


【解决方案1】:

使用ItemTouchUiUtil 接口提供了一个强大的解决方案。它允许您在 ViewHolder 中拥有前景和背景视图,并将所有滑动处理委托给前景视图。这是我用于向右滑动删除的示例。

在你的ItemTouchHelper.Callback

public class ClipItemTouchHelper extends ItemTouchHelper.SimpleCallback {

    ...

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (viewHolder != null){
            final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

            getDefaultUIUtil().onSelected(foregroundView);
        }
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView,
                            RecyclerView.ViewHolder viewHolder, float dX, float dY,
                            int actionState, boolean isCurrentlyActive) {
        final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

        drawBackground(viewHolder, dX, actionState);

        getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY,
                actionState, isCurrentlyActive);
     }

    @Override
    public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
                                RecyclerView.ViewHolder viewHolder, float dX, float dY,
                                int actionState, boolean isCurrentlyActive) {
        final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

        drawBackground(viewHolder, dX, actionState);

        getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY,
                actionState, isCurrentlyActive);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
        final View backgroundView = ((ClipViewHolder) viewHolder).clipBackground;
        final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

        // TODO: should animate out instead. how?
        backgroundView.setRight(0);

        getDefaultUIUtil().clearView(foregroundView);
    }

    private static void drawBackground(RecyclerView.ViewHolder viewHolder, float dX, int actionState) {
        final View backgroundView = ((ClipViewHolder) viewHolder).clipBackground;

        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            //noinspection NumericCastThatLosesPrecision
            backgroundView.setRight((int) Math.max(dX, 0));
        }
    }
}

在你的布局文件中,定义前景和背景视图:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/clipRow"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

        <View style="@style/Divider"/>

        <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/row_selector"
        >

        <RelativeLayout
            android:id="@+id/clipBackground"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:background="@color/swipe_bg"
            tools:layout_width="match_parent">

            <ImageView
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:layout_centerVertical="true"
                android:layout_marginLeft="12dp"
                android:layout_marginStart="12dp"
                android:focusable="false"
                android:src="@drawable/ic_delete_24dp"
                tools:ignore="ContentDescription"/>

        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/clipForeground"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingBottom="@dimen/clip_vertical_margin"
            android:paddingLeft="@dimen/clip_horizontal_margin"
            android:paddingRight="@dimen/clip_horizontal_margin"
            android:paddingTop="@dimen/clip_vertical_margin" >

            <CheckBox
                android:id="@+id/favCheckBox"
                android:layout_width="@dimen/view_image_size"
                android:layout_height="@dimen/view_image_size"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:background="@android:color/transparent"
                android:button="@drawable/ic_star_outline_24dp"
                android:clickable="true"
                android:contentDescription="@string/content_favorite"
                />

             ...

        </RelativeLayout>

    </FrameLayout>

</LinearLayout>

【讨论】:

  • @Micheal Updike 这在 API 10 上不起作用。为了让它在 API 10 上运行,我必须将 view.setRight() 移植到 LayoutParams 并设置视图的宽度。clipBackground 的工作原理是预期,但 clipForeground 视图跳转到 RecylerView 的顶部。有什么建议吗?
  • 嘿@passerby,你找到解决方案了吗?
  • @Michael Updike,您的代码运行良好,但如何绘制背景视图以向左滑动?
  • 为了让这个解决方案对我有用,我刚刚从 simpleItemTouchCallback 中删除了带有背景的整个逻辑,并且在布局中,背景是匹配父布局,始终在前景后面可见。
  • 感谢您的详细解释。非常有帮助。作为补充,我想提供这个,让背景在向左滑动时也显示:if (dX &gt; 0) backgroundView.setRight((int) dX); else backgroundView.setRight(backgroundView.getWidth() - (int) dX);
【解决方案2】:

我相信你应该使用 viewType 而不是绘制过来

在适配器中实现getItemViewType(int position),返回不同的类型,例如0 正常,1 刷卡

并更改 onCreateViewHolder 等以在不同的 viewType 上返回不同的布局

【讨论】:

  • 我很确定你没有理解我的意思。这样我可以修改列表中使用的视图,因此其中一些看起来可能与其他视图不同,但是在我的情况下我将如何使用它呢?我想显示我的自定义视图,当 ViewHolder 被滑动时,onCreateViewHolder 不会被调用。
  • 你要知道RecyclerView.Adapter的一些基本操作。 developer.android.com/reference/android/support/v7/widget/… 有多种方法名为notifyItemXXXXX 和一种方法notifyDataSetChanged,每当您更改适配器的内部数据时,都应调用此方法,RecyclerView 会相应地重新加载视图。例如:对于您的情况,刷卡后,您应该更新内部结构并将项目标记为removed,然后您可以调用notifyItemChanged(int position)
  • 是的,我知道这些方法,但这不是我的情况。我不想在滑动 之后 对视图执行任何操作(它们已经工作正常,包括在滑动时删除项目),我想在 滑动期间更改视图手势,因此用户可以看到 under 的视图并知道会发生什么。 Gmail 收件箱,滑动存档 - 这就是我想要实现的示例。
【解决方案3】:

对于仍然发现这个默认值的人来说,这是最简单的方法。

一个简单的实用程序类,用于向 RecyclerView 项目添加背景、图标和标签,同时向左或向右滑动。

插入到 Gradle

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

重写ItemTouchHelper类的onChildDraw方法

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

更多信息 -> https://github.com/xabaras/RecyclerViewSwipeDecorator

【讨论】:

    【解决方案4】:

    通过扩展 Sanvywell 在Adding a colored background with text/icon under swiped row when using Android's RecyclerView 上提供的答案,我设法使它与颜色和图像一起使用。

    我不会重新创建完整的帖子(您可以点击链接)。简而言之,我没有在滑动期间创建要渲染的完整视图,而只是在画布上添加了一个位图,并适当地定位到 RecyclerView 项。

    【讨论】:

    • 我还想绘制绿色背景,同时在删除滑动项目后,下面的项目动画到它们的新位置。有关如何做到这一点,请在此处查看我的答案stackoverflow.com/a/34687548/680050
    【解决方案5】:

    我正在调查同样的问题。我最终做的是,在包含 recyclerView 的布局中,我添加了一个简单的FrameLayout,名为“swipe_bg”,与我的RecyclerView.ViewHolder 项目之一高度相同。我将其可见性设置为“已消失”,并将其置于RecyclerView

    然后在我设置 ItemTouchHelper 的活动中,我像这样覆盖 onChildDraw..

    final ItemTouchHelper.SimpleCallback swipeCallback = new ItemTouchHelper.SimpleCallback(0,
                    ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT){
    
    
                @Override
                public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
                     View itemView = viewHolder.itemView;
                     swipe_bg.setY(itemView.getTop());
                     if(isCurrentlyActive) {
                         swipe_bg.setVisibility(View.VISIBLE);
                     }else{
                         swipe_bg.setVisibility(View.GONE);
                     }
                     super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
      };
    

    不知道这是否是最好的方法,但似乎是最简单的方法。

    【讨论】:

    • 这真的不能回答 OP 的问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多