RecyclerView使用(四)

這篇文章會講解RecyclerView的Item拖動排序與滑動刪除。
ItemTouchHelper

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
ItemTouchHelper是一個支持RecyclerView的滑動刪除和拖拽的工具類。也就是說我們可以通過它來簡單的完成RecyclerView的Item拖動排序與滑動刪除。

效果如圖

拖動和滑動.gif

一、使用ItemTouchHelper.Callback

要使用ItemTouchHelper需要創建一個類繼承 ItemTouchHelper.Callback 抽象類,通過這個類可以讓你監聽“move”與 “swipe”事件,而且還可以控制view被選中的狀態以及重寫默認動畫的地方。如果你只是想要一個基本的實現,有一個系統封裝好的可以使用:SimpleCallback。但是為了了解其工作機制,我們還是自己實現。
這是這個類需要些的全部代碼

public class ItemTouchHelperCallback extends ItemTouchHelper.Callback{

    private ItemTouchHelperAdapter itemTouchHelperAdapter;//RecyclerView的適配器

    /**
     * 多參的構造方法
     * @param itemTouchHelperAdapter 需要適配器來改變數據的位置,以及通知它來刷新
     */
    public ItemTouchHelperCallback(ItemTouchHelperAdapter itemTouchHelperAdapter) {
        this.itemTouchHelperAdapter = itemTouchHelperAdapter;

    }

    /**
     * RecyclerView item支持長按進入拖動操作
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    /**
     * RecyclerView item任意位置觸發啟用滑動操作
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    /**
     * 指定可以支持的拖動和滑動的方向,上下為拖動(drag),左右為滑動(swipe)
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        //判斷布局管理器是否為GridLayoutManager
        if (recyclerView.getLayoutManager() instanceof GridLayoutManager || recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
            final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            //不需要滑動
            final int swipeFlags = 0;
            return makeMovementFlags(dragFlags, swipeFlags);
        } else {
            final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
            return makeMovementFlags(dragFlags, swipeFlags);
        }
    }

    /**
     * 拖動操作會執行的方法
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //判斷如果viewHolder類型不一樣,返回False
        if (viewHolder.getItemViewType() != target.getItemViewType()) {
            return false;
        }
        //通知適配器改變數據 以及刷新
        itemTouchHelperAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    /**
     * 滑動操作會執行的方法
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        //通知適配器刪除數據 以及刷新
        itemTouchHelperAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }

    /**
     * 當動作開始的時候調用
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        //動作不是空閑狀態調用
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ItemTouchHelperAdapter.ItemTouchHelperViewHolder) {
                //獲取執行的item的ViewHolder
                ItemTouchHelperAdapter.ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperAdapter.ItemTouchHelperViewHolder) viewHolder;
                //選中狀態回調
                itemViewHolder.onItemSelected();
            }
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    /**
     * 當動作之中的時候調用
     */
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            //自定義滑動動畫(讓它根據滑動的距離與itemView的寬度的比例來改變透明度)
            final float alpha = 1.0f - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
            //設置透明度
            viewHolder.itemView.setAlpha(alpha);
            //設置平移動畫
            viewHolder.itemView.setTranslationX(dX);
        } else {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        }
    }

    /**
     * 當動作結束的時候調用
     */
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (viewHolder instanceof ItemTouchHelperAdapter.ItemTouchHelperViewHolder) {
            //獲取執行的item的ViewHolder
            ItemTouchHelperAdapter.ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperAdapter.ItemTouchHelperViewHolder) viewHolder;
            //未選中狀態回調
            itemViewHolder.onItemNormal();
        }
    }
}
  1. 繼承ItemTouchHelper.Callback的抽象類之后會讓你實現這三個方法
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) 
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 
  • getMovementFlags在這個方法里面寫通過item的移動方向來判斷是拖動或滑動操作。
  • onMove在這個方法里面寫拖動操作后的執行方法,通過適配器來改變數據位置,以及刷新
  • onSwiped在這個方法里面寫滑動操作后的執行方法,通過適配器來刪除數據位置,以及刷新
  1. 之后還需要些兩個輔助動作的方法
@Override
public boolean isLongPressDragEnabled()
@Override
public boolean isItemViewSwipeEnabled()
  • isLongPressDragEnabled這個方法默認返回ture,支持長按進入拖動操作
  • isItemViewSwipeEnabled這個方法指的是在view任意位置觸摸事件發生時啟用滑動操作
  1. 如果寫一些在有關操作的動畫,復寫下面三個方法
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) 
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
  • onSelectedChanged指的是你在開始操作(也就是說選中item)的時候希望做的對item的改變,比如說改變item的背景顏色,還可以通過參數actionState來判斷什么操作
  • onChildDraw指的是你在操作(一般指的是滑動)途中希望做的對item的改變,比如寫了一個透明度改變的動畫
  • clearView操作結束后的做的對item的改變,讓它恢復原有狀態

二、需要在Adapter寫的方法

全部的代碼

public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder> {

    private ArrayList<String> list;//要顯示的數據
    private Context context;//上下文
    private ImgViewTouchListener imgViewTouchListener;//需要外面實現的接口

    /*
    多參的構造方法
     */
    public ItemTouchHelperAdapter(ArrayList<String> list, Context context) {
        this.list = list;
        this.context = context;
    }

    /**
     * 創建視圖
     */
    @Override
    public ItemTouchHelperViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //創建視圖
        View view = LayoutInflater.from(context).inflate(R.layout.item_touch_helper_item,parent,false);
        //實例化MainViewHolder---- 傳View過去
        ItemTouchHelperViewHolder holder = new ItemTouchHelperViewHolder(view);
        return holder;
    }

    /**
     * 初始化控件
     */
    public class ItemTouchHelperViewHolder extends RecyclerView.ViewHolder{
        public TextView textView;
        public ImageView imageView;
        public ItemTouchHelperViewHolder(View itemView) {
            super(itemView);
            //初始化控件
            textView = (TextView) itemView.findViewById(R.id.text);
            imageView = (ImageView) itemView.findViewById(R.id.imgView);
        }

        /**
         * 操作開始時調用的方法
         */
        public void onItemSelected() {
            itemView.setBackgroundColor(Color.BLACK);
        }
        /**
         * 操作完成后調用的方法
         */
        public void onItemNormal() {
            itemView.setBackgroundColor(0);
        }
    }



    /**
     * 填充數據
     */
    @Override
    public void onBindViewHolder(final ItemTouchHelperViewHolder holder, int position) {
        //獲取要填充的值
        String content = list.get(position);
        //控件中設置值
        holder.textView.setText(content);
        holder.imageView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                imgViewTouchListener.imgViewOnTouch(event,holder);
                return false;
            }
        });

    }

    /**
     * 獲取item的總個數
     */
    @Override
    public int getItemCount() {
        return list.size();
    }

    /**
     * 拖動方法
     */
    public boolean onItemMove(int fromPosition, int toPosition) {
        //互換列表中指定位置的數據
        Collections.swap(list, fromPosition, toPosition);
        //通知改變
        notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    /**
     * 滑動刪除的方法
     */
    public void onItemDismiss(int position) {
        list.remove(position);
        notifyItemRemoved(position);
    }

    /*
    暴露出去的點擊事件的方法
     */
    public void setImgViewTouchListener(ImgViewTouchListener imgViewTouchListener){
        this.imgViewTouchListener = imgViewTouchListener;
    }

    /*
    點擊要實現的接口
     */
    public interface ImgViewTouchListener{
        public void imgViewOnTouch(MotionEvent event,ItemTouchHelperViewHolder holder);
    }

}
  • 寫一個拖動操作調用的方法和一個滑動刪除調用的方法,在上一個類的onMove和onSwiped方法中調用。
  • 在ViewHolder內部類寫開始操作和結束操作的方法,在上一個類的onSelectedChanged和clearView方法中調用
  • 最后我希望在按下圖片才開始拖拽操作,所有需要寫一個點擊方法(通過上一篇文章的方式)。

三、在Activity中

//實例化ItemTouchHelper對象
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback(adapter));
//itemTouchHelper與RecyclerView關聯
itemTouchHelper.attachToRecyclerView(rcTouch);
//設置ImgView的觸摸監聽事件
adapter.setImgViewTouchListener(new ItemTouchHelperAdapter.ImgViewTouchListener() {
    @Override
    public void imgViewOnTouch(MotionEvent event, ItemTouchHelperAdapter.ItemTouchHelperViewHolder holder) {
        //判斷是否按下圖片
        if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
            //按下則開始拖動
            itemTouchHelper.startDrag(holder);
        }
    }
});
  • 首先實例化一個ItemTouchHelper類,并且在參數之前寫的ItemTouchHelperCallback類的對象
  • 然后將其與RecyclerView關聯
  • 設置ImgView的觸摸監聽,在里面通過ItemTouchHelper類來調用開始拖動的方法

RecyclerView的系列的文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容