zoukankan      html  css  js  c++  java
  • 如何在RecyclerView上面实现"拖放"和"滑动删除"-2

    拖动手柄

    在设计一个支持"拖放"的列表时, 通常提供一个在触摸时初始化拖拽的"拖动手柄". 因其可发现性和可用性而被Material Guidelines所推荐, 尤其是列表处于"可编辑模式"时.

    首先更新item的布局(item_main.xml): 

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/item"
        android:layout_width="match_parent"
        android:layout_height="?listPreferredItemHeight"
        android:clickable="true"
        android:focusable="true"
        android:foreground="?selectableItemBackground">
    
        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="16dp"
            android:textAppearance="?android:attr/textAppearanceMedium" />
    
        <ImageView
            android:id="@+id/handle"
            android:layout_width="?listPreferredItemHeight"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical|right"
            android:scaleType="center"
            android:src="@drawable/ic_reorder_grey_500_24dp" />
    </FrameLayout>

    用作"拖放手柄"的图片可以在Material Design Icon找到, 也可以方便地通过Android Material Design Icon Generator Plugin添加.

    我们曾经提到过, 可以通过代码ItemTouchHelper.startDrag(RecyclerView.ViewHolder)来开启拖动. 所以我们要做的就是更新ViewHolder来包含新的手柄视图, 并设置一个简单的触摸事件接口, 以触发startDrag()方法. 

    我们需要定义一个接口来传递拖动事件.

    public interface OnStartDragListener {
    
        /**
         * Called when a view is requesting a start of a drag.
         *
         * @param viewHolder The holder of the view to drag.
         */
        void onStartDrag(RecyclerView.ViewHolder viewHolder);
    }

    然后, 在ItemViewHolder中实现化手柄视图.

    public final ImageView handleView;
    public ItemViewHolder(View itemView) {
        super(itemView);
        // ...
        handleView = (ImageView) itemView.findViewById(R.id.handle);
    }

    并且更新Adapter.

    private final OnStartDragListener mDragStartListener;
    
    public RecyclerListAdapter(OnStartDragListener dragStartListener) {
        mDragStartListener = dragStartListener;
        // ...
    }
    @Override
    public void onBindViewHolder(final ItemViewHolder holder, 
            int position) {
        // ...
        holder.handleView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (MotionEventCompat.getActionMasked(event) == 
                        MotionEvent.ACTION_DOWN) {
                    mDragStartListener.onStartDrag(holder);
                }
                return false;
            }
        });
    }

    完整的Adapter应该看起来像这个.

    剩下的是把OnStartDragListener添加到Fragment.

    public class RecyclerListFragment extends Fragment implements 
            OnStartDragListener {
        
        // ...
        @Override
        public void onViewCreated(View view, Bundle icicle) {
            super.onViewCreated(view, icicle);
    
            RecyclerListAdapter a = new RecyclerListAdapter(this);
            // ...
        }
        @Override
        public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
            mItemTouchHelper.startDrag(viewHolder);
        }
    }

    但你运行之后, 可以看到这样的效果:

    标示选中视图

    在上一篇的基础示例中, 被拖拽的item事实上是被选中的, 但是没有可视化的标示. 由于显著的理由, 这是不受欢迎的, 但也很容易修复. 事实上, 在ItemTouchHelper的帮助下, 只要你的ViewHolder的itemView设置了background集合(selector), 就会得到相应的效果. 在Lollipop及之后的版本, item view的elevation在拖拽和滑动期间会增加. 而在之前的版本中, 滑动时会有fade效果. 看起来就像:

    效果看起来不错, 但你可能想要更多的控制. 一种方法是, 无论任何时候ViewHolder被选中或者清空, 让item自己处理这种改变. 由此, ItemTouchHelper.Callback提供了两种回调.

    onSelectedChanged(RecyclerView.ViewHolder, int). 每一次ViewHolder的状态, 变成drag(ACTION_STATE_DRAG)或者swipe(ACTION_STATE_SWIPE)时, 该方法就会被调用. 这时候是将ViewHolder的状态变成active的完美时刻.

    clearView(RecyclerView, RecyclerView.ViewHolder). 在被拖拽的ViewHolder放下时, 或者是滑动操作取消或者完成时(ACTION_STATE_IDLE), 这里会是将ItemView状态设置为idle的最好的地方.

    那么, 我们就把两者绑定在一起实现.

    首先, 创建一个接口, 让目标ViewHolder实现:

    /**
     * Notifies a View Holder of relevant callbacks from 
     * {@link ItemTouchHelper.Callback}.
     */
    public interface ItemTouchHelperViewHolder {
    
        /**
         * Called when the {@link ItemTouchHelper} first registers an 
         * item as being moved or swiped.
         * Implementations should update the item view to indicate 
         * it's active state.
         */
        void onItemSelected();
    
    
        /**
         * Called when the {@link ItemTouchHelper} has completed the 
         * move or swipe, and the active item state should be cleared.
         */
        void onItemClear();
    }

    接着, 触发相应的回调方法:

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, 
            int actionState) {
       // We only want the active item
       if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ItemTouchHelperViewHolder) {
                ItemTouchHelperViewHolder itemViewHolder = 
                        (ItemTouchHelperViewHolder) viewHolder;
                itemViewHolder.onItemSelected();
            }
        }
    
        super.onSelectedChanged(viewHolder, actionState);
    }
    @Override
    public void clearView(RecyclerView recyclerView, 
            RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
    
        if (viewHolder instanceof ItemTouchHelperViewHolder) {
            ItemTouchHelperViewHolder itemViewHolder = 
                    (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }

    现在, 剩下的是让ItemViewHolder实现ItemTouchHelperViewHolder:

    public class ItemViewHolder extends RecyclerView.ViewHolder 
            implements ItemTouchHelperViewHolder {
    
        // ...
        @Override
        public void onItemSelected() {
            itemView.setBackgroundColor(Color.LTGRAY);
        }
    
        @Override
        public void onItemClear() {
            itemView.setBackgroundColor(0);
        }
    }

    考虑到这仅仅是一个示例, 我们仅仅是在item处于active状态时设置了灰色背景, 当item被清空时, 把灰色背景删除了. 如果你的Adapter和ItemTouchHelper紧密结对的话, 轻易地放弃这个设置也行, 转而可以直接在ItemTouchHelper.Callback转变item的状态. 

    Grid布局

    如果你想在本项目中转而使用GridLayoutManager, 你会发现并没有正常地起作用. 原因以及修复原因都很简单: 必须告诉ItemTouchHelper我们想要支持左右拖拽. 在SimpleTouchHelperCallback中, 我们已经声明:

    @Override
    public int getMovementFlags(RecyclerView recyclerView, 
            RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    支持Grid布局所要求的唯一改变是dragFlags中添加左右方向: 

    int dragFlags = ItemTouchHelper.UP   | ItemTouchHelper.DOWN | 
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

    但是, 对于grid而言, "滑动删除"并不是一个天然的功能模式, 所以你也许会写一些如下的代码: 

    @Override
    public int getMovementFlags(RecyclerView recyclerView, 
            RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | 
                        ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        int swipeFlags = 0;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    Grid上面的上下左右"拖放"效果看起来如下: 

    自定义滑动动画

    对我们而言, 在viewHolder拖拽和滑动的时候, ItemTouchHelper.Callback为我们提供了一个十分方便的方式来完全控制ViewHolder的动画. 因为ItemTouchHelper是一个ItemDecoration, 我们可以用类似的方式, 详细地了解视图的绘制(hook into the View drawing). 

    下面是一个简单的例子, 来覆盖默认的滑动动画, 来展示一个线性的fade效果.

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, 
            ViewHolder viewHolder, float dX, float dY, 
            int actionState, boolean isCurrentlyActive) {
    
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            float width = (float) viewHolder.itemView.getWidth();
            float alpha = 1.0f - Math.abs(dX) / width;
            viewHolder.itemView.setAlpha(alpha);
            viewHolder.itemView.setTranslationX(dX);    
        } else {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, 
                    actionState, isCurrentlyActive);
        }
    }

    参数dX和dY代表选中视图的当前位置, 

    • -1.0f表示完完全全的从ItemTouchHelper.END到ItemTouchHelper.START的滑动.
    • 1.0f表示完完全全的从ItemTouchHelper.START到ItemTouchHelper.END的滑动.

     对于任何没有处理的actionState你都可以调用super的方法, 从而使用默认的动画.

    总结

    我们仅仅了解了自定义ItemTouchHelper所能做的事情中有趣的部分, 我希望能够在一篇文章中包含更多内容, 但是考虑到文章长度, 还是分开比较好.

  • 相关阅读:
    CodeForces 150E: Freezing with Style
    CodeForces 407E: k-d-sequence
    CodeForces 809E: Surprise me!
    CodeForces 1178G: The Awesomest Vertex
    LOJ 3158: 「NOI2019」序列
    LOJ 3160: 「NOI2019」斗主地
    LOJ 3159: 「NOI2019」弹跳
    LOJ 3156: 「NOI2019」回家路线
    【比赛游记】NOI2019打铁记
    LOJ 2085: 洛谷 P1587: bzoj 4652: 「NOI2016」循环之美
  • 原文地址:https://www.cnblogs.com/littlepanpc/p/6604138.html
Copyright © 2011-2022 走看看