RecyclerView實(shí)現(xiàn)條目Item長按拖拽排序與滑動(dòng)刪除

效果演示

<img src="http://upload-images.jianshu.io/upload_images/2918620-eb4210464ce2c4b4.gif?imageMogr2/auto-orient/strip"/>

需求和技術(shù)分析

  1. RecyclerView Item拖拽排序:長按RecyclerView的Item或者觸摸Item的某個(gè)按鈕饵蒂。
  2. RecyclerView Item滑動(dòng)刪除:RecyclerView Item滑動(dòng)刪除:RecyclerView的Item滑動(dòng)刪除墓怀。

實(shí)現(xiàn)方案與技術(shù)

利用ItemTouchHelper綁定RecyclerView细卧、ItemTouchHelper.Callback來實(shí)現(xiàn)UI更新,并且實(shí)現(xiàn)動(dòng)態(tài)控制是否開啟拖拽功能和滑動(dòng)刪除功能丐一。

實(shí)現(xiàn)步驟

  1. 繼承抽象類ItemTouchHelper铃岔,并在構(gòu)造方法傳入實(shí)現(xiàn)的ItemTouchHelper.Callback江咳。
  2. recyclerView綁定ItemTouchHelper:itemTouchHelper.attachToRecyclerView(recyclerView)。
  3. 自定義ItemTouchHelper.Callback的實(shí)現(xiàn)接口OnItemTouchCallbackListener钥庇,由外部更新RecyclerView的Item牍鞠。

幾個(gè)主要的布局

實(shí)現(xiàn)ItemTouchHelperCallback并繼承自ItemTouchHelper.Callback

public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;
    /**
     * 是否可以拖拽
     */
    private boolean isCanDrag = false;
    /**
     * 是否可以被滑動(dòng)
     */
    private boolean isCanSwipe = false;

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        mAdapter = adapter;
    }

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter,boolean canDrag,boolean canSwipe) {
        mAdapter = adapter;
        this.isCanDrag = canDrag;
        this.isCanSwipe = canSwipe;
    }

    /**
     * 設(shè)置是否可以被拖拽
     *
     * @param canDrag 是true,否false
     */
    public void setDragEnable(boolean canDrag) {
        isCanDrag = canDrag;
    }

    /**
     * 設(shè)置是否可以被滑動(dòng)
     *
     * @param canSwipe 是true评姨,否false
     */
    public void setSwipeEnable(boolean canSwipe) {
        isCanSwipe = canSwipe;
    }

    /**
     * 當(dāng)Item被長按的時(shí)候是否可以被拖拽
     * @return
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return isCanDrag;
    }

    /**
     * Item是否可以被滑動(dòng)(H:左右滑動(dòng)难述,V:上下滑動(dòng))
     * @return
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return isCanSwipe;
    }

    /**
     * 當(dāng)用戶拖拽或者滑動(dòng)Item的時(shí)候需要我們告訴系統(tǒng)滑動(dòng)或者拖拽的方向
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager
            // flag如果值是0,相當(dāng)于這個(gè)功能被關(guān)閉
            int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            int swipeFlag = 0;
            // create make
            return makeMovementFlags(dragFlag, swipeFlag);
        } else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            int orientation = linearLayoutManager.getOrientation();

            int dragFlag = 0;
            int swipeFlag = 0;

            // 為了方便理解吐句,相當(dāng)于分為橫著的ListView和豎著的ListView
            if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是橫向的布局
                swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            } else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是豎向的布局胁后,相當(dāng)于ListView
                dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            }
            return makeMovementFlags(dragFlag, swipeFlag);
        }
        return 0;
    }

    /**
     * 當(dāng)Item被拖拽的時(shí)候被回調(diào)
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        /**
         * 回調(diào)
         */
        mAdapter.onMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
        /**
         * 回調(diào)
         */
        mAdapter.onSwipe(viewHolder.getAdapterPosition());
    }
}

上面主要就五個(gè)方法比較重要

/**
 * 是否可以長按拖拽排序。
 */
@Override
public boolean isLongPressDragEnabled() {}
/**
 * Item是否可以被滑動(dòng)(H:左右滑動(dòng)嗦枢,V:上下滑動(dòng))
 */
@Override
public boolean isItemViewSwipeEnabled() {}
/**
 * 當(dāng)用戶拖拽或者滑動(dòng)Item的時(shí)候需要我們告訴系統(tǒng)滑動(dòng)或者拖拽的方向
 */
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {}
/**
 * 當(dāng)Item被拖拽的時(shí)候被回調(diào)
 */
@Override
public boolean onMove(RecyclerView r, ViewHolder rholer, ViewHolder tholder) {}

/**
 * 當(dāng)View被滑動(dòng)刪除的時(shí)候
 */
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}

isItemViewSwipeEnabled()返回值是否可以拖拽排序攀芯,true可以,false不可以文虏,isItemViewSwipeEnabled()是否可以滑動(dòng)刪除侣诺,true可以,false不可以择葡;這兩個(gè)方法都是配置是否可以操作的紧武。我們上面的代碼中返回了一個(gè)成員變量值,并且這個(gè)值通過外部可以修改敏储,所以提供了外部控制的方法阻星。

onMove()當(dāng)Item被拖拽排序移動(dòng)到另一個(gè)Item的位置的時(shí)候被回調(diào),onSwiped()當(dāng)Item被滑動(dòng)刪除到不見;這兩個(gè)方法是當(dāng)用戶操作了妥箕,來回調(diào)我們滥酥,我們就該去更新UI了。這里我們提供了一個(gè)Listener去通知外部畦幢,并且返回出去了必要的值坎吻,來降低代碼耦合度。

getMovementFlags()說明一:是當(dāng)用戶拖拽或者滑動(dòng)Item的時(shí)候需要我們告訴系統(tǒng)滑動(dòng)或者拖拽的方向宇葱,那我們又知道支持拖拽和滑動(dòng)刪除的無非就是LinearLayoutManagerGridLayoutManager了瘦真,相當(dāng)于我們老早的時(shí)候用的ListViewGridView了。所以我們根據(jù)布局管理器的不同做了響應(yīng)的區(qū)分黍瞧。

getMovementFlags()說明二:其他都好理解诸尽,就是這里的return makeMovementFlags(dragFlag, swipeFlag);這句話是最終的返回值,也就是它決定了我們的拖拽或者滑動(dòng)的方法印颤。第一個(gè)參數(shù)是拖拽flag您机,第二個(gè)是滑動(dòng)的flag。

實(shí)現(xiàn)ItemTouchHelperAdapter接口

用于監(jiān)聽長按拖拽或滑動(dòng)刪除時(shí)的事件

public interface ItemTouchHelperAdapter {

    /**
     * @param fromPosition 起始位置
     * @param toPosition 移動(dòng)的位置
     */
    void onMove(int fromPosition, int toPosition);
    void onSwipe(int position);
}

定義RecyclerViewAdapter并實(shí)現(xiàn)ItemTouchHelperAdapter 接口

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> implements ItemTouchHelperAdapter{
    private List<String> mDataList = new ArrayList<>();

    /**
     * 當(dāng)加載更多的時(shí)候可以使用
     *
     * @param dataList
     */
    public void addData(List<String> dataList) {
        if (dataList != null) {
            mDataList.addAll(dataList);
        }
        notifyDataSetChanged();
    }

    /**
     * 更新Adapter
     *
     * @param dataList
     */
    public void replaceData(List<String> dataList) {
        if (dataList != null) {
            mDataList.clear();
            addData(dataList);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String data = mDataList.get(position);
        holder.tv.setText(data);
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    @Override
    public void onMove(int fromPosition, int toPosition) {
        /**
         * 在這里進(jìn)行給原數(shù)組數(shù)據(jù)的移動(dòng)
         */
        Collections.swap(mDataList, fromPosition, toPosition);
        /**
         * 通知數(shù)據(jù)移動(dòng)
         */
        notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onSwipe(int position) {
        /**
         * 原數(shù)據(jù)移除數(shù)據(jù)
         */
        mDataList.remove(position);
        /**
         * 通知移除
         */
        notifyItemRemoved(position);
    }

    class ViewHolder extends RecyclerView.ViewHolder{
        private TextView tv;
        private ImageView iv;
        public ViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.text);
            iv = (ImageView) itemView.findViewById(R.id.handle);
        }
    }
}

最后讓RecyclerView綁定ItemTouchHelper

public class MainActivity extends AppCompatActivity {
    private RecyclerViewAdapter mAdapter;
    private RecyclerView mRecyclerView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
//        mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
        mAdapter = new RecyclerViewAdapter();
        mRecyclerView.setAdapter(mAdapter);
        initData();
    }

    private void initData() {
        List<String> list = new ArrayList<>();
        for(int i=0;i<100;i++){
            list.add("第"+i+"個(gè)");
        }
        mAdapter.addData(list);

        ItemTouchHelperCallback helperCallback = new ItemTouchHelperCallback(mAdapter);
        helperCallback.setSwipeEnable(true);
        helperCallback.setDragEnable(true);
        ItemTouchHelper helper = new ItemTouchHelper(helperCallback);
        helper.attachToRecyclerView(mRecyclerView);
    }
}

到此結(jié)束
<a >源碼下載</a>
申明:以上有些解釋是借鑒網(wǎng)上的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末年局,一起剝皮案震驚了整個(gè)濱河市际看,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矢否,老刑警劉巖仲闽,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異僵朗,居然都是意外死亡蔼囊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門衣迷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畏鼓,“玉大人,你說我怎么就攤上這事壶谒≡平茫” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵汗菜,是天一觀的道長让禀。 經(jīng)常有香客問我,道長陨界,這世上最難降的妖魔是什么巡揍? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮菌瘪,結(jié)果婚禮上腮敌,老公的妹妹穿的比我還像新娘阱当。我一直安慰自己,他們只是感情好糜工,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布弊添。 她就那樣靜靜地躺著,像睡著了一般捌木。 火紅的嫁衣襯著肌膚如雪油坝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天刨裆,我揣著相機(jī)與錄音澈圈,去河邊找鬼。 笑死帆啃,一個(gè)胖子當(dāng)著我的面吹牛极舔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播链瓦,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盯桦!你這毒婦竟也來了慈俯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤拥峦,失蹤者是張志新(化名)和其女友劉穎贴膘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體略号,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刑峡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玄柠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突梦。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖羽利,靈堂內(nèi)的尸體忽然破棺而出宫患,到底是詐尸還是另有隱情,我是刑警寧澤这弧,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布娃闲,位于F島的核電站,受9級特大地震影響匾浪,放射性物質(zhì)發(fā)生泄漏皇帮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一蛋辈、第九天 我趴在偏房一處隱蔽的房頂上張望属拾。 院中可真熱鬧,春花似錦、人聲如沸捌年。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽礼预。三九已至眠砾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間托酸,已是汗流浹背褒颈。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留励堡,地道東北人谷丸。 一個(gè)月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像应结,于是被迫代替她去往敵國和親刨疼。 傳聞我的和親對象是個(gè)殘疾皇子蜻懦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容