美文网首页 Android知识进阶(遥远的重头开始)高级UI
Android-RecyclerView侧滑删除和拖拽操作,最底

Android-RecyclerView侧滑删除和拖拽操作,最底

作者: MonkeyLei | 来源:发表于2019-07-21 16:47 被阅读1次

    ===滑动拖拽===

    Look, ItemTouchHelper | Android Developers

    ItemTouchHelper
    public class ItemTouchHelper 
    extends RecyclerView.ItemDecoration implements RecyclerView.OnChildAttachStateChangeListener
    
    java.lang.Object
       ↳    android.support.v7.widget.RecyclerView.ItemDecoration
           ↳    android.support.v7.widget.helper.ItemTouchHelper
    
    This 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.
    
    Depending on which functionality you support, you should override onMove(RecyclerView, ViewHolder, ViewHolder) and / or onSwiped(ViewHolder, int).
    
    This class is designed to work with any LayoutManager but for certain situations, it can be optimized for your custom LayoutManager by extending methods in the ItemTouchHelper.Callback class or implementing ItemTouchHelper.ViewDropHandler interface in your LayoutManager.
    
    By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. You can customize these behaviors by overriding onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, boolean) or onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean).
    
    Most of the time you only need to override onChildDraw.
    

    解释:就是说这是一个支持RecyclerView的滑动删除和拖拽的实体类。

    它靠一个回调来实现,也就是ItemTouchHelper.Callback | Android Developers

    基于这个支持,你还需要实现[onMove(RecyclerView, ViewHolder, ViewHolder)](https://link.zhihu.com/?target=https%3A//developer.android.google.cn/reference/android/support/v7/widget/helper/ItemTouchHelper.Callback.html%3Fhl%3Dzh-cn%23onMove%28android.support.v7.widget.RecyclerView%2C%2520android.support.v7.widget.RecyclerView.ViewHolder%2C%2520android.support.v7.widget.RecyclerView.ViewHolder%29)and / or[onSwiped(ViewHolder, int)](https://link.zhihu.com/?target=https%3A//developer.android.google.cn/reference/android/support/v7/widget/helper/ItemTouchHelper.Callback.html%3Fhl%3Dzh-cn%23onSwiped%28android.support.v7.widget.RecyclerView.ViewHolder%2C%2520int%29) 方法。

    这个类还支持特定解决方案的所有LayoutManager,但是需要你继承ItemTouchHelper.Callback或者实现ItemTouchHelper.ViewDropHandler 接口。

    另外你通过重写onChildDraw 方法可以实现移动属性的自定义。

    1. 所以创建一个这样的实体对象就是像这样:

        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
                @Override
                public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
                    return 0;
                }
    
                @Override
                public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
                    return false;
                }
    
                @Override
                public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
    
                }
            }); 
    

    2. 调用就是如下的方法咯, attach一下RecyclerView就可以啦...

      void  attachToRecyclerView(RecyclerView recyclerView)
    Attaches the ItemTouchHelper to the provided RecyclerView.
    

    还有其他方法如下,先不管嘛,慢慢来

    image

    Look2, 所以重点来了,就是这个 ItemTouchHelper.Callback | Android Developers 如果不想自定义,其实有个官方简单版ItemTouchHelper.SimpleCallback | Android Developers<u style="text-decoration: none; border-bottom: 1px dashed grey;"> 这个可能就不能针对某个条目单独处理了咯....</u>

    不过人家已经把移动操作弄好了。另外滑动效果也处理了,你只需要将滑动的条目从adapter中删除即可!参数就是滑动的方向和拖拽的方法处理(比如只能向上拖动,左滑右滑啥的),如下:

         ItemTouchHelper itemTouchHelper2 = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                    ItemTouchHelper.LEFT) {
                @Override
                public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
                    return true;
                }
    
                @Override
                public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
                    // remove from adapter
                }
            });
    

    Then,如果我们希望列表类似如下:(底部的条目不能被滑动删除,也不能被移动,同时也不能被拖动的条目改变位置...)

    image

    1. 这个时候就需要我们自定义ItemTouchHelper.Callback | Android Developers

    然后重点重写如下三个方法,基本上就可以进行相关控制了。当然有些也可以简单重写,比如isLongPressDragEnabled()这些,可以控制状态。

     abstract int   getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
    
      abstract boolean  onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
    Called when ItemTouchHelper wants to move the dragged item from its old position to the new position.
    
    abstract void   onSwiped(RecyclerView.ViewHolder viewHolder, int direction)
    Called when a ViewHolder is swiped by the user.
    

    2. 我们为了控制最后的条目不能操作的情况,我们重点关注下getMovementFlags方法

    getMovementFlags
    added in version 24.1.0
    int getMovementFlags (RecyclerView recyclerView, 
                    RecyclerView.ViewHolder viewHolder)
    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).
    
    This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next 8 bits are for SWIPE state and third 8 bits are for DRAG state. Each 8 bit sections can be constructed by simply OR'ing direction flags defined in ItemTouchHelper.
    
    For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to swipe by swiping RIGHT, you can return:
    
          makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
    
    This means, allow right movement while IDLE and allow right and left movement while swiping.
    

    这个属性返回值就意味着你对该条目的操作状态,比如我们获取当前位置是最后一个条目,进行如下处理:(mList.size()需要你外部传入链表mList哟)

      @Override
        public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
            //int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            //int swipeFlags = ItemTouchHelper.LEFT;
            int[] flags;
            if (viewHolder.getLayoutPosition() == (mList.size() - 1)){
                flags =  new int[]{0, 0};
            }else{
                flags =  new int[]{ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT};
            }
            return makeMovementFlags(flags[0], flags[1]);
        }         
    

    也就是说不管是滑动还是拖拽,只要是最后一个条目,那么标志都为0,也就是什么都不能干!其他情况,正常左滑,拖拽即可!

    当然其实我们可以搞一个回调来返回想要的处理,如果说很多页面都是同样的操作,倒是可以再次封装一下,最后我们贴上我的自定义:

    SimpleItemTouchHelperCallback.java

    import android.graphics.Canvas;
    import android.support.annotation.NonNull;
    import android.support.v7.widget.RecyclerView;
    import android.support.v7.widget.helper.ItemTouchHelper;
    
    /*
     *@Description: 侧滑删除辅助类
     *@Author: hl
     *@Time: 2019/1/4 15:52
     */
    public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
        private Sthc_Movement sthc_movement;
    
        public SimpleItemTouchHelperCallback(Sthc_Movement sthc_movement){
            this.sthc_movement = sthc_movement;
        }
    
        /**
         * 控制每个条目的可操作状态 - 滑动,拖拽等  来源->getFlag
         * @param recyclerView
         * @param viewHolder
         * @return
         */
        @Override
        public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
            //int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            //int swipeFlags = ItemTouchHelper.LEFT;
            int[] flags = sthc_movement.getFlag(viewHolder.getLayoutPosition());
            return makeMovementFlags(flags[0], flags[1]);
        }
    
        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
            sthc_movement.onMove(viewHolder, target);
            return true;
        }
    
        @Override
        public boolean isLongPressDragEnabled() {
            return true;
        }
    
        @Override
        public boolean isItemViewSwipeEnabled() {
            return true;
        }
    
        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
            sthc_movement.onSwiped(viewHolder, i);
        }
    
        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                                float dX, float dY, int actionState, boolean isCurrentlyActive) {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
                viewHolder.itemView.setAlpha(alpha);
                viewHolder.itemView.setTranslationX(dX);
            }
        }
    
        public interface Sthc_Movement{
            public void onMove(RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target);
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction);
            public int[] getFlag(int postion);
        }
    }
    
    

    LookEnd,具体使用如下(注意移动条目交换的操作就行)

        ///< 侧滑删除和拖拽排序
            SimpleItemTouchHelper itemTouchHelper = new SimpleItemTouchHelper(new SimpleItemTouchHelperCallback(new SimpleItemTouchHelperCallback.Sthc_Movement() {
                @Override
                public void onMove(RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                    int fromPosition = viewHolder.getAdapterPosition();
                    int toPosition = target.getAdapterPosition();
                    ///< 禁止拖动到新增菜单的底部
                    if (toPosition >= (mList.size() - 1)){
                        return;
                    }
                    if (fromPosition < toPosition) {
                        for (int i = fromPosition; i < toPosition; i++) {
                            Collections.swap(mList, i, i + 1);
                        }
                    } else {
                        for (int i = fromPosition; i > toPosition; i--) {
                            Collections.swap(mList, i, i - 1);
                        }
                    }
                    baseAdapter.notifyItemMoved(fromPosition, toPosition);
                }
    
                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    int position = viewHolder.getAdapterPosition();
                    mList.remove(position);
                    baseAdapter.notifyItemRemoved(position);
                }
    
                /**
                 * 控制每个条目的可操作状态 - 滑动,拖拽等  对应->getMovementFlags
                 * @param position
                 * @return
                 */
                @Override
                public int[] getFlag(int position) {
                    if (position == (mList.size() - 1)){
                        return new int[]{0, 0};
                    }else{
                        return new int[]{ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT};
                    }
                }
            }));
            itemTouchHelper.attachToRecyclerView(dailyItemsRv);
    

    其中注意不允许其他条目在底部菜单下面,所以我增加了如下判断:

    image

    基本上简单需求是满足了。对了,1. onChildDrawif (actionState == ItemTouchHelper.ACTION_STATE_SWIPE**) 的处理,是滑动过程alpha透明度的变化。滑动慢点可以看到效果。。。

    2. SimpleItemTouchHelper没什么东东,就简单继承了下ItemTouchHelper | Android Developers 后面或许还可以增加额外自定义处理。

    这个先简单这样认识下,毕竟小萌新都没接触过,有时候就是需要项目多实战。把常用的都搞搞,然后深入,然后源码剖析,这样应该才能掌握的更好!

    ===上拉加载更多===

    说起这个,一般小萌新都是搬砖的,只会用人家的框架 - 下拉刷新,上拉加载更多!我们来看看官方的下拉刷新SwipeRefreshLayout | Android Developers 这个简单入门使用还好,先不搞特别复杂的效果:

            // 设置颜色属性的时候一定要注意是引用了资源文件还是直接设置16进制的颜色,因为都是int值容易搞混
            // 设置下拉进度的背景颜色,默认就是白色的
            dailyHisItemsSwr.setProgressBackgroundColorSchemeResource(android.R.color.white);
            // 设置下拉进度的主题颜色
            dailyHisItemsSwr.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark);
            // 下拉时触发SwipeRefreshLayout的下拉动画,动画完毕之后就会回调这个方法
            dailyHisItemsSwr.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    // 开始刷新,设置当前为刷新状态
                    //swipeRefreshLayout.setRefreshing(true);
                    //                if (startload){
                    //                    return;
                    //                }
    
                    // 模拟下: 
                    //  这里是主线程
                    // 一些比较耗时的操作,比如联网获取数据,需要放到子线程去执行
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            //  加载数据
                            //  刷新数据 baseAdapter.notifyDataSetChanged();
                            //  加载完数据设置为不刷新状态,将下拉进度收起来
                            dailyHisItemsSwr.setRefreshing(false);
                        }
                    }, 500);
    
                    // 这个不能写在外边,不然会直接收起来
                    //swipeRefreshLayout.setRefreshing(false);
                }
            });
    

    基本就能看到下拉刷新的效果啦....

    然后接着我们快速入门下上拉加载更多吧,这个有点点麻烦,另外如果SwipeRefreshLayout + RecyclerView实现下拉刷新/上拉加载更多 - 可能还会遇到很多问题需要处理(比如下拉时禁止上拉,上拉加载过程中禁止下拉刷新,还有就是上拉加载触发的条件-当前如果没有数据,可能就不能上拉加载了,可能就是全屏显示无数据,点击刷新获取了, 总之要实现一个效果体验好的上拉加载/下拉刷新框架不是这么容易的)。 从目前小萌新的感觉来看,实现上拉加载更多动画,上拉停止动画,回弹,这个过程是需要监听持续touch事件的,也就是说单纯的靠监听RecyclerView的addOnScrollListener貌似不太行,必须有持续监听。 后面要专门搞搞这个上拉加载更多的效果....

    说这么多,开始吧...**上拉加载更多。 **目前的方案基本都是监听RecyclerView的滚动,然后判断是上拉加载更多的话,则Adapter底部条目对应显示加载更多,以及还可以扩展“没有更多了”等效果。

    1. 先看下Adapter的扩展处理吧 - 看关键点就可以了!每个人的适配器不同!

    1.1 首先就是如果是需要展示底部加载状态的情况下,返回的item个数多一个

    image

    1.2 然后就是getItemViewType返回不同的类型

    image

    1.3 接着就是布局加载以及数据绑定,小萌新是分别搞了多个不同状态的布局。绑定数据的时候就处理正常条目数据即可,加载状态的布局不做绑定操作!!

    image

    1.4 额外提供一些数据刷新以及状态判断的方法

    /**
         * 添加更多数据
         * @param _baseMulDataModelList
         */
        public void addMoreItem(List<BaseDataModel> _baseMulDataModelList) {
            state = STATE.HIDE;
            baseMulDataModelList.addAll(_baseMulDataModelList);
            notifyDataSetChanged();
        }
    
        /**
         * 开始加载
         */
        public void startLoad() {
            state = STATE.START_LOAD;
            notifyDataSetChanged();
        }
    
        /**
         * 加载中
         */
        public void startLoading() {
            state = STATE.LOADING;
            notifyDataSetChanged();
        }
    
        /**
         * 加载结束,无更多数据了
         */
        public void finishNoMoreData() {
            state = STATE.NO_MOREDATA;
            notifyDataSetChanged();
        }
    
        /**
         * 是否开始加载了
         * @return
         */
        public boolean bIsStart(){
            return state == STATE.START_LOAD;
        }
    
        /**
         * 是否正在加载
         * @return
         */
        public boolean bIsLoading(){
            return state == STATE.LOADING || state == STATE.NO_MOREDATA;
        }
    
        public boolean bJustLoading(){
            return state == STATE.LOADING;
        }
    

    2. 以上就基本构建好了。接下来就是我们适配器创建RecyclerView.OnScrollListener | Android Developers ,上拉加载处理了...

    2.1 你网上搜有些“SwipeRefreshLayout和RecyclerView实现下拉刷新上拉加载更多”,基本方案都类似。就是细节处理不太一样。有些是在onScrollStateChanged中请求上拉加载,有些是在onScrolled处理上拉加载,大部分还是在onScrollStateChanged中进行了处理。

    2.2 需要注意, 如果你的条目不满一屏的话,上拉加载不会触发onScrolled回调,所以你要注意在onScrolled处理的地方哟!

    按小萌新想法,这种情况可以不用触发上拉加载更多,本来就那么多数据,正常的逻辑!哈哈。。。

    2.3 所以小萌新的总结是:onScrolled中进行判断是否上拉加载更多,并且满足条件,开启加载;然后当滑动停止时, 在onScrollStateChanged中进行数据加载(需要判断是否开启了加载,这样可以避免不满一屏也上拉的操作;同时还要判断是否正在下拉刷新这些情况....)

    看下两个方法和一些属性介绍...

    onScrollStateChanged
    added in version 22.1.0
    void onScrollStateChanged (RecyclerView recyclerView, 
                    int newState)
    Callback method to be invoked when RecyclerView's scroll state changes.
    
    Parameters
    recyclerView    RecyclerView: The RecyclerView whose scroll state has changed.
    newState    int: The updated scroll state. One of SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING or SCROLL_STATE_SETTLING.
    
    onScrolled
    added in version 22.1.0
    void onScrolled (RecyclerView recyclerView, 
                    int dx, 
                    int dy)
    Callback method to be invoked when the RecyclerView has been scrolled. This will be called after the scroll has completed.
    
    This callback will also be called if visible item range changes after a layout calculation. In that case, dx and dy will be 0.
    
    Parameters
    recyclerView    RecyclerView: The RecyclerView which scrolled.
    dx  int: The amount of horizontal scroll.
    dy  int: The amount of vertical scroll.
    

    dy可以用来判断是下滑还是上滑的,目前按照我的逻辑,暂时不需要这个处理,我只需要判断上拉加载更多时,是否达到了底部?- 靠获取底部条目的bottom位置,然后跟RecyclerView做差值,小于10基本就是了。此时就可以开始加载...

       /**初始化界面***/
       private void initView(){
            ///< 上拉加载更多监听
            adh_dailyHisItemsRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    Log.e("test", "newState=" + newState);
    
                    ///< 正在刷新,则直接返回
                    if (dailyHisItemsSwr.isRefreshing() || baseAdapter.bIsLoading()) return;
    
                    ///< 加载更多 onScrolled中上拉加载条件满足时进行加载更多的操作??其他优化?
                    if (baseAdapter.bIsStart() &&
                            !baseAdapter.bIsLoading() &&
                            newState == RecyclerView.SCROLL_STATE_IDLE) {
                        baseAdapter.startLoading();
                        loadMoreDate();
                    }
                    //Log.e("test", "startload=" + startload + " newState=" + newState);
                }
    
                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
    
                    //lastVisibleItem = layoutManager.findLastVisibleItemPosition();
                    //bUpLoad = dy > 0;
                    Log.e("test", "dy=" + dy);
    
                    if (baseAdapter.bIsLoading()) return;
    
                    View viewlast = layoutManager.findViewByPosition(baseAdapter.getItemCount() - 1);
                    Log.e("test", "viewlast=" + viewlast);
                    ///< 当滑动到底部item的时候(此时底部bottom基本就是高度),设置startload = true,展示加载中(可以修改为动画显示)
                    if (null != viewlast && (recyclerView.getHeight() - viewlast.getBottom()) < 10) {
                        //Log.e("test", "getHeight=" + recyclerView.getHeight());
                        //Log.e("test", "getTop=" + viewlast.getTop());
                        //Log.e("test", "getBottom=" + viewlast.getBottom());
                        baseAdapter.startLoad();
                    }
                }
            });
       }      
    
       /**
         * 加载更多
         */
        private void loadMoreDate() {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    List<BaseDataModel> mListTemp = new ArrayList<>();
                    for (int i = 0; i < 20; i++) {
                        DailyHisAdapterItemBean dailyHisAdapterItemBean = new DailyHisAdapterItemBean();
                        dailyHisAdapterItemBean.setDate("2019.11.11");
                        dailyHisAdapterItemBean.setNames("杀猪刀一把/青椒炒苹果/萝卜抄西瓜/萝卜抄西瓜");
                        dailyHisAdapterItemBean.setTotal_price(12.28);
                        dailyHisAdapterItemBean.setTotal_weight(0.5);
                        mListTemp.add(dailyHisAdapterItemBean);
                    }
                    baseAdapter.addMoreItem(mListTemp);
                    baseAdapter.finishNoMoreData();
                }
            }, 2000);
        }
    

    基本就ojbk了。。。

    image image

    总之,小萌新又接触了一些知识。这块后面要加强,是需要看下第三方框架源码,然后学习下。 虽然不用总是造轮子,但是还是知道多点比较好吧....

    心情好,放松,开心的学习就好 - 小萌新

    相关文章

      网友评论

        本文标题:Android-RecyclerView侧滑删除和拖拽操作,最底

        本文链接:https://www.haomeiwen.com/subject/nrcvlctx.html