• 网赌被黑找腾龙D哥出黑【微信:gm4927 QQ:861122225】

    前言

    在上一个章节中,通过简单的小案例我们熟悉了RecyclerView的基本使用,但是依然无法通过简单的demo了解到RecyclerView相对于listView或者GridView独一无二的优越性。

    那么RecyclerView真的和ListView更好用在哪里呢?我们通过一个小demo更深一步学习和了解它:

    仿百度贴吧首页列表:

    本章节内容如下:

    1.强大的RecyclerView!

    2.ItemTouchHelper了解:触摸拖拽item && 左右滑动移除item

    3.小案例:仿百度贴吧首页列表

    4.踩坑笔记:ItemTouchHelper切换layoutManager触摸效果异常的解决方案

    一.强大的RecyclerView!

    从上图我们可以看到,界面实现后的效果是列表中item实现了触摸拖拽效果,当切换视图模式后,列表布局改变,但依旧保持更改后的数据源中的元素排列顺序。

    抛开RecyclerView不谈,单纯以ListView+GridView能否实现?也可以,但是要单独实现切换ListView的显示和隐藏,2个adapter,2个对应的触摸监听,以及数据源改变后的ListView和GridView数据的传递,需要的代码量实在不敢想象。

    而对于RecyclerView,我们通过LayoutManager和RecyclerView绑定,寥寥数行代码就可以解决问题,实在是太方便了!而且对于item拖拽时的移动效果,也只需要调用adapter的

    adapter.notifyItemMoved(fromPosition, toPosition);
    

    方法就可实现,十分方便。

    实现本文中案例的demo需要用到的知识点:

    1.RecyclerView +RecyclerView.Adapter的使用

    2.LayoutManager实现ListView或GridView布局的切换

    3.ItemTouchHelper 绑定RecyclerView实现触摸拖拽的效果

    前两点在笔者的上一篇文章中都详细解释过了,本文主要对ItemTouchHelper进行详解。

    二.ItemTouchHelper实现:触摸拖拽item && 左右滑动移除item

    1.ItemTouchHelper简介

    android.support.v7.widget.helper.ItemTouchHelper
    

    ItemTouchHelper这个帮助类的主要作用就是帮助RecyclerView实现滑动消失和拖拽支持的,他是RecyclerView高度解耦的一部分,通过内部静态类Callback(回调机制)与RecyclerView进行通信,主要作用就是监听用户执行的手势行为。

    ItemOpenHelper的两个核心方法:

    public ItemTouchHelper(Callback callback):构造器,用自己定义的回调借口初始化该类对象

    public void attachToRecyclerView(RecyclerView recyclerView):将类对象与 RecyclerView绑定,自动过滤重复绑定、如果重新绑定recyclerview清楚之前的callback对象,重新设置新的callback属性

    也就是说,我们想要让RecyclerView监听到拖拽的行为,只需要new一个ItemTouchHelper的对象,然后通过attachToRecyclerView()方法绑定recyclerView,就可以了,helper对象会实时将触摸事件告知RecyclerView(高度解耦封装)。

    这种特性使得只需要两行代码就可以实现:

    ItemOpenHelper helper = new ItemTouchHelper(callback);//1.实例化helper对象
            //1.5   callBack:告诉helper监听到触摸事件后干什么
         helper.attachToRecyclerView(recyclerView);//2.绑定到recyclerView上

    完事了吗,当然不,我们知道helper和RecyclerView的使用,但是我们还需要给helper在创建时传一个CallBack的回调参数,这个回调的参数是告诉helper和recyclerView当监听到触摸事件所做出的对应的行为:比如案例中的item的拖拽移动效果。

    2.ItemTouchHelper.Callback

    从标题可以得知,我们需要实现的这个CallBack是ItemTouchHelper的抽象的静态内部类,它包含的最主要的3个方法需要我们实现:

    /**
         * 设置允许拖拽和删除的方向
         * @param recyclerView rv
         * @param viewHolder   holder
         * @return
         */
        public abstract int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder);
    
        /**
         * 拖拽后回调,一般通过接口暴露给adapter, 让adapter去处理数据的交换
         * @param recyclerView rv
         * @param viewHolder   从某个item
         * @param target       到哪个item
         * @return
         */
        public abstract boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) ;
    
        /**
         * 滑动删除后回调
         * @param viewHolder view
         */
        public abstract void onSwiped(RecyclerView.ViewHolder viewHolder, int direction);

    可以看到,我们需要实现最基本的三个抽象方法配置helper:

    1.getMovementFlags():我想让RecyclerView中item响应哪个方向的触摸事件

    2.onMove():helper监听到item移动事件后我想让recyclerView干什么

    3.onSwiped():helper监听到item滑动移出事件后我想让recyclerView干什么

    以本文中的demo为案例,笔者实现的CallBack部分代码如下:

    public class MySlideViewTouchHandler extends ItemTouchHelper.Callback {
    
            private ItemTouchAdapter adapter;
    
            public MySlideViewTouchHandler(@NonNull ItemTouchAdapter adapter) {
                this.adapter = adapter;
            }
    
            /**
             * 设置拖拽和删除的模式
             */
            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                int dragFlags, swipeFlags;  //拖拽方向,滑动消失方向
                RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
                if (manager instanceof GridLayoutManager || manager instanceof StaggeredGridLayoutManager) {
                    //若是gird或者瀑布流布局,可以上下左右滑动
                    dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                } else {
                    //线性布局只能上下滑动
                    dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                }
                //左右方向滑动消失
                swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
                //若值为0,说明不支持该功能
                return makeMovementFlags(dragFlags, swipeFlags);
            }
    
            /**
             * 拖拽后回调,一般通过接口暴露给adapter, 让adapter去处理数据的交换
             */
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                int fromPosition = viewHolder.getAdapterPosition();
                int toPosition = target.getAdapterPosition();   //getLayoutPosition()?
                //同一类型的item才可以
                if (viewHolder.getItemViewType() == target.getItemViewType()) {
    
                    if (fromPosition > toPosition) {
                        for (int i = fromPosition; i > toPosition; i--) {
                            adapter.onItemMove(i, i - 1);
                        }
                    } else {
                        for (int i = fromPosition; i < toPosition; i++) {
                            adapter.onItemMove(i, i + 1);
                        }
                    }
                }
                adapter.notifyItemMoved(fromPosition, toPosition);
                return false;
            }
    
            /**
             * 滑动删除后回调
             */
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            }
    
               ······
               ······
               ······
        }

    注释很全就不解释了。

    三 小案例:仿百度贴吧首页列表

    核心代码如下(本文最底部有demo源码地址):

    0.gradle依赖

    compile 'com.android.support:recyclerview-v7:25.0.1'
    compile 'com.android.support:cardview-v7:25.0.1'

    1.MainActivity

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
            private CardView mCvSortLevel;
            private TextView mTvLayout;
            private CardView mCvLayout;
            private LinearLayout mLlHide;
            private RecyclerView mRecyclerView;
            private LinearLayout mActivityMain;
    
            private boolean TYPE_MANAGER_GRID = true;
            private MyTiebaAdapter adapter;
            private ItemTouchHelper helper;
    
    
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                initView();
                setHideBarVisible(IsHideBarVisible);
                initData();
                initRecyclerView();
            }
    
            private void initData() {
                for (int i = 0; i < 20; i++) {
                    list.add("贴吧序列号:" + i);
                }
            }
    
            private ArrayList<String> list = new ArrayList<>();
    
            //默认等级排序和视图可视
            private boolean IsHideBarVisible = true;
    
            /**
             * 设置recyclerView编辑状态下的头布局是否可视
             *
             * @param IsHideBarVisible
             */
            private void setHideBarVisible(boolean IsHideBarVisible) {
                mTvLayout.setText(TYPE_MANAGER_GRID ? "单列视图" : "双列视图");
                mLlHide.setVisibility(IsHideBarVisible ? View.VISIBLE : View.GONE);
            }
    
            private void initView() {
                mCvSortLevel = (CardView) findViewById(R.id.cv_sort_level);
                mTvLayout = (TextView) findViewById(R.id.tv_layout);
                mCvLayout = (CardView) findViewById(R.id.cv_layout);
                mLlHide = (LinearLayout) findViewById(R.id.ll_hide);
                mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
                mActivityMain = (LinearLayout) findViewById(R.id.activity_main);
    
                mCvLayout.setOnClickListener(this);
                mCvSortLevel.setOnClickListener(this);
            }
    
            /**
             * 配置recyclerview
             */
            private void initRecyclerView() {
                RecyclerView.LayoutManager manager;
                if (TYPE_MANAGER_GRID) {
                    //流式布局
                    manager = new GridLayoutManager(this, 2, LinearLayoutManager.VERTICAL, false);
                } else {
                    //单列线性布局
                    manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
                }
                mRecyclerView.setLayoutManager(manager);  //配置manager
                //创建adapter并设置
                if (adapter == null) {
                    adapter = new MyTiebaAdapter(this, list);
                }
                mRecyclerView.setAdapter(adapter);
    
                //添加灰色的分割线,关于Recycler分割线将在接下来的章节提出,详情请见demo源码,这一行可注释
                mRecyclerView.addItemDecoration(new ItemTiebaDivider().setDividerWith(1).setDividerColor(Color.GRAY));
    
                //绑定ItemTouchHelper
                if (helper != null) {
                    helper.attachToRecyclerView(null);
                }
                helper = new ItemTouchHelper(new MySlideViewTouchHandler(adapter));
                helper.attachToRecyclerView(mRecyclerView);
            }
    
    
            @Override
            public void onClick(View view) {
                switch (view.getId()) {
                    case R.id.cv_layout:
                        TYPE_MANAGER_GRID = !TYPE_MANAGER_GRID;
                        initRecyclerView();
                        mTvLayout.setText(TYPE_MANAGER_GRID ? "单列视图" : "双列视图");
                        break;
                    case R.id.cv_sort_level:
                        Toast.makeText(this, "等级排序", Toast.LENGTH_SHORT).show();
                        break;
                }
            }   
        }

    2.ItemTouchHelper.Callback

    
        public class MySlideViewTouchHandler extends ItemTouchHelper.Callback {
    
            private ItemTouchAdapter adapter;
    
            public MySlideViewTouchHandler(@NonNull ItemTouchAdapter adapter) {
                this.adapter = adapter;
            }
    
            /**
             * 设置拖拽和删除的模式
             *
             * @param recyclerView rv
             * @param viewHolder   holder
             * @return
             */
            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                int dragFlags, swipeFlags;  //拖拽方向,滑动消失方向
                RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
                if (manager instanceof GridLayoutManager || manager instanceof StaggeredGridLayoutManager) {
                    //若是gird或者瀑布流布局,可以上下左右滑动
                    dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                } else {
                    //线性布局只能上下滑动
                    dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                }
                //左右方向滑动消失
                swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
                //若值为0,说明不支持该功能
                return makeMovementFlags(dragFlags, swipeFlags);
            }
    
            /**
             * 拖拽后回调,一般通过接口暴露给adapter, 让adapter去处理数据的交换
             *
             * @param recyclerView rv
             * @param viewHolder   从某个view
             * @param target       到哪个view
             * @return
             */
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                int fromPosition = viewHolder.getAdapterPosition();
                int toPosition = target.getAdapterPosition();   //getLayoutPosition()?
                //同一类型才可以
                if (viewHolder.getItemViewType() == target.getItemViewType()) {
    
                    if (fromPosition > toPosition) {
                        for (int i = fromPosition; i > toPosition; i--) {
                            adapter.onItemMove(i, i - 1);
                        }
                    } else {
                        for (int i = fromPosition; i < toPosition; i++) {
                            adapter.onItemMove(i, i + 1);
                        }
                    }
                }
                adapter.notifyItemMoved(fromPosition, toPosition);
                return false;
            }
    
            /**
             * 滑动删除后回调
             * @param viewHolder view
             */
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            }
    
            /**
             * item取消选中(取消长按)
             * 这里改变了 item的背景色, 也可以通过接口暴露, 让adapter去处理逻辑
             */
            @Override
            public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                viewHolder.itemView.setBackgroundColor(Color.TRANSPARENT);
                super.clearView(recyclerView, viewHolder);
            }
    
            /**
             * 是否支持长按开始拖拽,默认开启
             * 可以不开启,然后在长按 item 的时候,手动 调用 mItemTouchHelper.startDrag(myHolder) 开启,更加灵活
             */
            @Override
            public boolean isLongPressDragEnabled() {
                return adapter.autoOpenDrag();
            }
    
            /**
             * 是否支持滑动删除,默认开启
             * 可以不开启,然后在长按 item 的时候,手动 调用 mItemTouchHelper.startSwipe(myHolder) 开启,更加灵活
             */
            @Override
            public boolean isItemViewSwipeEnabled() {
                return adapter.autoOpenSwipe();
            }
    
    
    
            // 建议让 adapter 实现该接口
            public static abstract class ItemTouchAdapter extends RecyclerView.Adapter {
    
                public abstract void onItemMove(int fromPosition, int toPosition);
    
                public abstract void onItemRemove(int position);
    
                // 是否自动开启拖拽
                protected boolean autoOpenDrag() {
                    return true;
                }
    
                // 是否自动开启滑动删除
                protected boolean autoOpenSwipe() {
                    return false;
                }
    
            }
        }
    

    4.RecyclerView的Adapter

    值得一提的是这个adapter继承的是CallBack中的内部类,这样方便动态设定是否自动开启拖拽和滑动删除

    public class MyTiebaAdapter extends MySlideViewTouchHandler.ItemTouchAdapter{
    
            private Context ctx;
            private ArrayList<String> list;
    
            public MyTiebaAdapter(Context ctx, ArrayList<String> list) {
                this.ctx = ctx;
                this.list = list;
            }
    
            /**
             * item发生移动的回调,这时ui已经变化了,我们需要做的就是更新数据源
             * @param fromPosition
             * @param toPosition
             */
            @Override
            public void onItemMove(int fromPosition, int toPosition) {
                Collections.swap(list,fromPosition,toPosition);  //集合内元素交换
            }
    
            /**
             * item滑动移除的回调,同上我们只需要更新数据源就行了
             * @param position
             */
            @Override
            public void onItemRemove(int position) {
    
            }
    
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(ctx).inflate(R.layout.item_tieba_list, parent, false);
                return new MyViewHolder(view);
            }
    
            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                ((MyViewHolder)holder).tvTitle.setText(list.get(position));
            }
    
            @Override
            public int getItemCount() {
                return list.size();
            }
    
    
            public class  MyViewHolder  extends  RecyclerView.ViewHolder{
               // public ImageView ivCancle;//这个是item的取消按钮,没有用到.
                public TextView tvTitle;
    
                public MyViewHolder(View itemView) {
                    super(itemView);
                 //   ivCancle = (ImageView) itemView.findViewById(R.id.iv_cancel);
                    tvTitle=(TextView)itemView.findViewById(R.id.tv_title);
                }
            }
        }
    

    5*.item的布局

    这个其实。。也不重要啦,但还是放上去把

    
        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:orientation="horizontal">
    
            <ImageView
                android:id="@+id/iv_cancel"
                android:layout_width="15dp"
                android:layout_height="15dp"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="10dp"
                android:visibility="gone"
                android:background="@drawable/cancel" />
    
            <TextView
                android:id="@+id/tv_title"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:layout_marginLeft="10dp"
                android:text="12345"
                android:textColor="#1e1e1e"
                />
    
            <TextView
                android:id="@+id/tv_level"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:layout_marginLeft="10dp"
                android:text="10级"
                android:textSize="12sp"
                android:layout_gravity="center_vertical|right"
                android:layout_marginRight="10dp"
                />
        </LinearLayout>

    四.踩坑笔记:ItemTouchHelper切换layoutManager触摸效果异常的解决方案

    有心的同学可能在MainActivity的initRecyclerView()中会看到这几行代码:

    //绑定ItemTouchHelper
          if (helper != null) { //若存在
              helper.attachToRecyclerView(null); //解除绑定
          }
          helper = new ItemTouchHelper(new MySlideViewTouchHandler(adapter));
          helper.attachToRecyclerView(mRecyclerView);

    代码简单的5行,其实最后两行很好理解,创建helper并绑定recyclerView,那么if()条件中的helper.attachToRecyclerView(null);什么用呢?

    其实是笔者在写代码时遇到的一个bug,简单来说,在不切换视图的时候(LayoutManager),item的拖拽效果正常,当从GridLayoutManager切换为LinearLayoutManager后,出现了item拖拽卡顿的效果:

    显而易见,切换视图后,拖拽效果出现了卡顿,并且拖拽移动不成功。笔者尝试了数次之后,猜测可能是切换LayoutManager后,RecyclerView已经绑定了helper,再次绑定会出现这样的问题。

    那么笔者采用的解决方法是,切换LayoutManager后,先将RecyclerView和itemTouchHelper解除绑定,然后将两者重新绑定的方案,问题解决。

    为什么helper.attachToRecyclerView(null)是解除绑定呢,在源码我们可以看到:

     /**
         * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
         * attached to a RecyclerView, it will first detach from the previous one. You can call this
         * method with {@code null} to detach it from the current RecyclerView.
         * 
         *
         * @param recyclerView The RecyclerView instance to which you want to add this helper or
         *                     {@code null} if you want to remove ItemTouchHelper from the current
         *                     RecyclerView.
         */
        public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
            if (mRecyclerView == recyclerView) {
                return; // nothing to do
            }
            if (mRecyclerView != null) {
                destroyCallbacks();
            }
            mRecyclerView = recyclerView;
            if (mRecyclerView != null) {
                final Resources resources = recyclerView.getResources();
                mSwipeEscapeVelocity = resources
                        .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
                mMaxSwipeVelocity = resources
                        .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
                setupCallbacks();
            }
        }

    英语水平有限,但注释中大概意思是:将参数中的RecyclerView和helper绑定,如果已经绑定了其他的recyclerView,那么会首先和已经绑定的解绑,你也可以传入一个null调用该方法,以达到helper与当前RecyclerView解绑的目的。

    至此,本文到此结束,附上源码地址:

    源码戳我

    网赌被黑找腾龙D哥出黑【微信:gm4927 QQ:861122225】

    发布评论

    分享到:

    专业出黑网赌被黑追款

    我的微信号:18488351249 (左侧二维码扫一扫)欢迎添加!

    RecyclerView3-面向接口优雅地实现多类型列表
    你是第一个吃螃蟹的人
    发表评论

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。