Android应用中实现拖拽排序及添加阴影的方式

来源:互联网 发布:常见最流行的网络用语 编辑:程序博客网 时间:2024/05/16 00:58

--- by Anwei.shi


背景介绍:

Android 应用开发中避免不了对容器中的各个Item的进行拖拽排序等操作,比如锤子手机中著名的OneStep功能(拖拽),以及Android 系统中语言设置菜单的快速排序功能。前段时间由于项目需求,需要对悬浮球中的菜单项实现如上的快速拖动排序删除等功能。

具体实现:

1.拖拽排序

参看AndroidSetting语言设置项源码,主要为LocaleDragAndDropAdapter.java和LocaleRecyclerView.java。在这两个类中主要是利用了RecycleView的辅助类ItemTouchHelper.class进行相关的拖拽排序等功能。该类的Google官方描述:

This is a utility class to add swipe to dismiss and drag& drop support to RecyclerView. It works with a RecyclerView and a Callbackclass, which configures what type of interaction are enabledand also receives events when user performs these actions. Depending on which functionality you support, you should override{@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or{@linkCallback#onSwiped(ViewHolder,int)}.

从以上可以看出实现拖拽必须重写onMove()和onSwiped()这两个方法。Android的布局方式为上下左右,该如何控制拖拽的方向呢?继续阅读Android源码,有getMovementFlags(RecyclerViewrecyclerView, RecyclerView.ViewHolder viewHolder)的抽象方法,其描述为

Should return a composite flag which defines the enabledmove directions in each state(idle, swiping, dragging).

大意是返回的是一个具有移动方向的值flag。根据以上内容,实现如下代码:

private ItemTouchHelper helper= new ItemTouchHelper(new ItemTouchHelper.Callback(){

        @Override

        public int getMovementFlags(RecyclerViewrecyclerView, RecyclerView.ViewHolder viewHolder){

            //首先回调的方法返回int表示是否监听该方向

            int dragFlags = ItemTouchHelper.UP| ItemTouchHelper.DOWN;//拖拽

            int swipeFlags = ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;//侧滑删除

            return makeMovementFlags(dragFlags, swipeFlags);

        }

        @Override

        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target){

            //滑动事件

Collections.swap(mMenuDataSugarList, viewHolder.getAdapterPosition(), target.getAdapterPosition());           mRecyAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());

            isDraySwapPosition =true;

            return false;

        }

        @Override

        public void onSwiped(RecyclerView.ViewHolder viewHolder,int direction){

            //侧滑事件

mMenuDataSugarList.remove(viewHolder.getAdapterPosition());            mRecyAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());

        }

        @Override

        public boolean isLongPressDragEnabled() {

            //是否可拖拽

            return true;

        }

        @Override

        public boolean isItemViewSwipeEnabled() {

            return true;

        }

}

将helper与相应的recycleview,采用如下代码进行关联,

helper.attachToRecyclerView(mMenuSettingRecyclerview);

则很容易实现recycleview的item上下拖动排序与左右滑动删除。可是实际测试却发现,每次需要拖动排序时,总需要按压一段时间才会有效果,体验不是很好。可不可以设置一个快速拖动按钮呢?继续查看ItemTouchHelper.class源码,发现startDrag(ViewHolderviewHolder)这个方法,google注释是这么说的:TheViewHolder to start dragging. It must be a direct child of RecyclerView.前半句说明这个方法就是用来立即执行拖动的,可是后半句说明这个holder必须由recycleview的子Item进行调用。如何将recycleview子Item的事件,传递给整个helper辅助类呢?带着疑问首先就想到了接口,所以定义一个接口:

public interface OnStartDragListener {

        void onStartDrag(RecyclerView.ViewHolder viewHolder);

    }

在Item快速拖动menu被触摸时,回调该接口

holder.mItemMenuHomeIcon.setOnTouchListener(new View.OnTouchListener(){

            @Override

            public boolean onTouch(View v, MotionEvent event){

            if (MotionEventCompat.getActionMasked(event)== MotionEvent.ACTION_DOWN)

            {

                    mDragStartListener.onStartDrag(holder);

                }

                return false;

            }

        });

然后在recycleView所在类实现该接口中的方法,

    @Override

    public void onStartDrag(RecyclerView.ViewHolder viewHolder){

        helper.startDrag(viewHolder);

}

以上分别基本实现了recycleview的正常拖动与快速拖动排序。补充一点,由于本人所负责项目中的数据需要实时的保存到数据库,及时更新到悬浮球item上中,而且大多数的项目也都离不开数据库操作。原本我是在onMove以及onSwiped中进行list数据的保存。慢速拖动时,数据库还能较及时的写入新的数据,可是在快速拖动时,一秒钟可能来回拖动数十次,onMove方法被回调了数百次甚至更多,频繁的写入数据库对系统性能有着较明显的影响。后来我将数据库的保存操作放在FragmentonStop周期方法内,这样避免了数据库的频繁写入,可是只有该fragment销毁之后,悬浮球的菜单更改项才能生效,这个会对实时性有所影响。有没有什么折中的办法呢?后来在同事的提醒下,可以将数据保存操作放在拖动松开的时刻。重写RecycleViewonTouch 方法:

mMenuSettingRecyclerview.setOnTouchListener(new View.OnTouchListener(){

            @Override

            public boolean onTouch(View v, MotionEvent event){

                if (event.getAction()== MotionEvent.ACTION_UP&& isDraySwapPosition){

                    saveMenuDataSugar();//保存拖动之后的数据到数据库

                    isDraySwapPosition =false;//用来标示recycleview是否被拖动排序

                }

                return false;

            }

        });

按以上方法,着实较好的解决了性能与时效性的均衡性。

2.添加阴影

最后,来实现一下,如何在拖动某个Item时,添加阴影效果。开始的时候,我按照传统的添加阴影效果再布局之上使用selector选择器:

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_selected="true" android:drawable="@drawable/button_bg_press"/>

    <item android:state_focused="true" android:drawable="@drawable/button_bg_press"/>

    <item android:state_pressed="true" android:drawable="@drawable/button_bg_press" />

    <item android:drawable="@drawable/button_bg_normol" />

</selector>

实际的效果的为在刚刚touch到recycleView的Item项时就会有相应的阴影,可是此时并没有拖动啊?显然,这与实际需求效果不一致。必须在拖动开始时添加阴影,结束之后,移除相应的效果。在ItemTouchHelper.class类中拖动与删除有onMove和onSwiped方法,选中有onSelectedChanged方法,我们可以将其阴影效果的添加放在该方法。但是在该类中却没有发现onUnSelectedChanged方法,继续查看源码,有这么一个方法clearView,其描述为:Thisis a good place to clear all changes on the View that was done in,大意为:如果我们需要清除一个view的改变,则需要在该处进行实现。综上,可以在这两个方法的内部进行阴影的添加与删除。

首先定义一个接口:(该接口的主要作用实现抽象类,并不是真正意义的回调)

 public interface ItemTouchHelperViewHolder{

        void onItemSelected();

        void onItemClear();

}

重写ItemTouchHelper.class中以下两个方法:

@Override

public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,int actionState){

    if (actionState != ItemTouchHelper.ACTION_STATE_IDLE){

        if (viewHolder instanceof ItemTouchHelperViewHolder){

            ItemTouchHelperViewHolderitemViewHolder =(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){

        ItemTouchHelperViewHolderitemViewHolder =(ItemTouchHelperViewHolder) viewHolder;

        itemViewHolder.onItemClear();

    }

}

最后在ItemViewHolder中实现该方法。

@Override

public void onItemSelected() {

     itemView.setElevation(30);

    itemView.setBackgroundResource(R.drawable.bg_item_selected);

}

@Override

public void onItemClear() {

     itemView.setElevation(0);

     itemView.setBackgroundColor(0);

}

 

最终效果如下动图


总结:

本文主要是针对项目中使用到RecycleView拖动排序功能,基于Android原生行为的较为完整实现。在网上搜索RecycleView拖动排序,或许能够搜出很多,但大多都是只是简单的讲解一个普通排序,而快速排序和添加阴影等却很少有人提过。通过该此次项目也算是对Android源码的阅读和学习有着进一步的认识,当我们遇到问题没有思路时,通过网络等很容易了解到大致内容,但是,开发中遇到特别深入的内容时,我们还是需要阅读源码,不然即使知其然却不知其所以然。路漫漫其修远兮,吾将上下而求索,阅读源码之路还是很漫长的!
阅读全文
0 0