1. 程式人生 > >recycleView的位置調換和側滑刪除

recycleView的位置調換和側滑刪除

轉載:https://blog.csdn.net/Android_Study_OK/article/details/51545172

效果圖

這裡寫圖片描述

參考

RecyclerView的拖動和滑動 第一部分 :基本的ItemTouchHelper示例

概述:

這裡寫圖片描述

ItemTouchHelperAdapter:(★)

RecyclerListAdapter需要實現這個介面, 
RecyclerListAdapter通過ItemTouchHelperAdapter的onItemMove和onItemDismiss方法來完成條目移動、條目消除的操作,並且通過notifyItemMoved()和notifyItemRemoved()來通知adapter資料的改變

ItemTouchHelperViewHolder:(★)

RecyclerListAdapter的內部類ItemViewHolder需要實現這個介面ItemViewHolder,通過呼叫ItemTouchHelperViewHolder的onItemSelected()和onItemClear()來完成條目被選中時(底色變灰色)、條目動畫完成時(底色變透明)的操作

SimpleItemTouchHelperCallback:(★★★★★)

該類繼承於抽象類ItemTouchHelper.Callback,ItemTouchHelper.Callback已經封裝了RecyclerView拖動、側滑的效果,我們只需要重寫其暴露出來的方法來控制拖拽即可,具體涉及到的方法有:(詳細介紹見原始碼) 
1. 建構函式

 
2. isLongPressDragEnabled() 
3. isItemViewSwipeEnabled() 
4. getMovementFlags() 
5. onMove() 
6. onSwiped() 
7. onSelectedChanged() 
8. clearView()

MainActivity/RecyclerListFragment(★)

外界如何呼叫

RecyclerListAdapter(★★★★★)

  • 該類實現了ItemTouchHelperAdapter介面

    RecyclerListAdapter通過ItemTouchHelperAdapter的onItemMove和onItemDismiss方法來完成條目移動、條目消除的操作,並且通過notifyItemMoved()和notifyItemRemoved()來通知adapter資料的改變

  • 其內部類ItemViewHolder實現這個介面ItemTouchHelperViewHolder

通過呼叫ItemTouchHelperViewHolder的onItemSelected()和onItemClear()來完成條目被選中時(底色變灰色)、條目動畫完成時(底色變透明)的操作

外部使用Activity/Fragment

package co.paulburke.android.itemtouchhelperdemo;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import co.paulburke.android.itemtouchhelperdemo.helper.SimpleItemTouchHelperCallback;

/**
 * @author Paul Burke (ipaulpro)
 */
public class RecyclerListFragment extends Fragment {

    private ItemTouchHelper mItemTouchHelper;

    public RecyclerListFragment() {
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
//         ★★★關鍵之處在於RecyclerListAdapter的寫法
        RecyclerListAdapter adapter = new RecyclerListAdapter();

        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
//        setHasFixedSize()方法用來使RecyclerView保持固定的大小,該資訊被用於自身的優化。
        recyclerView.setHasFixedSize(true);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        /**
         * ★★★★★★★★★★★★★★★★★★★★★★★
         * 要使用ItemTouchHelper,你需要建立一個ItemTouchHelper.Callback。
         * 這個介面可以讓你監聽“move(上下移動)”與 “swipe(左右滑動)”事件。這裡還是
         * ★控制view被選中
         * 的狀態以及★重寫預設動畫的地方。
         *
         * 如果你只是想要一個基本的實現,有一個
         * 幫助類可以使用:SimpleCallback,但是為了瞭解其工作機制,我們還是自己實現
         */
        ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter);
        mItemTouchHelper = new ItemTouchHelper(callback);
//        將定義好的mItemTouchHelper應用於我們的recyclerView,使得recyclerView獲得move和swipe的效果
        mItemTouchHelper.attachToRecyclerView(recyclerView);


    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

SimpleItemTouchHelperCallback

package co.paulburke.android.itemtouchhelperdemo.helper;

import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;

/**
 * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
 * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
 * </br/>
 * Expects the <code>RecyclerView.Adapter</code> to react to {@link
 * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement
 * {@link ItemTouchHelperViewHolder}.
 *
 * 接下來的兩個是onMove()和onSwiped(),用於通知底層資料的更新。
 *
 * @author Paul Burke (ipaulpro)
 */
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;

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

    /**
     * 要支援長按RecyclerView item進入拖動操作,
     * 你必須在isLongPressDragEnabled()方法中返回true。
     * 或者,也可以呼叫ItemTouchHelper.startDrag(RecyclerView.ViewHolder)
     * 方法來開始一個拖動。
     */
    @Override
    public boolean isLongPressDragEnabled() {

        return true;//返回true表示支援長按開始拖拽
    }

    /**
     * 而要在view任意位置觸控事件發生時啟用滑動操作,
     * 則直接在sItemViewSwipeEnabled()中返回true就可以了。
     * 或者,你也主動呼叫ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)
     * 來開始滑動操作。
     */
    @Override
    public boolean isItemViewSwipeEnabled() {

        return true;//返回true表示支援左右滑動
    }

    /**
     * ItemTouchHelper可以讓你輕易得到一個事件的方向。
     * 你需要重寫getMovementFlags()方法來指定可以支援
     * 的拖放和滑動的方向。
     * 使用helperItemTouchHelper.makeMovementFlags(int, int)
     * 來構造返回的flag。這裡我們啟用了上下左右兩種方向。
     * 注:上下為拖動(drag),左右為滑動(swipe)。
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);//表示支援上下拖拽和左右滑動
//        return makeMovementFlags(dragFlags, 0);//第二項為0,表示你支援拖拽,不支援左右滑動
    }

    /**
     * Called when ItemTouchHelper wants to move the dragged item from its old position to
     * the new position.
     * 當移動一個條目時被呼叫
     * <p>
     * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
     * to the adapter position of {@code target} ViewHolder
     * ({@link RecyclerView.ViewHolder#getAdapterPosition()
     * ViewHolder#getAdapterPosition()}).
     * 如果該方法返回true,認定條目已經移動到了新的位置
     * <p>
     * If you don't support drag & drop, this method will never be called.
     * 如果你不支援拖拽,此方法不會被呼叫
     *
     * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
     *                     ItemTouchHelper所附著的RecyclerView
     * @param source   The ViewHolder which is being dragged by the user.
     *                 正在被使用者拖拽的位置
     * @param target       The ViewHolder over which the currently active item is being
     *                     dragged.
     *                     當前正在被拖拽的條目所經過的位置
     * @return True if the {@code viewHolder} has been moved to the adapter position of
     * {@code target}.
     * @see #onMoved(RecyclerView, RecyclerView.ViewHolder, int, RecyclerView.ViewHolder, int, int, int)
     */


    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
        mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }
    /**
     * Called when a ViewHolder is swiped by the user.
     * 當被左右滑動時,呼叫該方法
     * 注意:這裡的ViewHolder統一翻譯成條目,通過ViewHolder可以得到關於條目的所有屬性(如:位置等等)
     * <p>
     * If you are returning relative directions ( {START} , {END}) from the
     * {@link #getMovementFlags(RecyclerView, RecyclerView.ViewHolder)} method, this method
     * will also use relative directions. Otherwise, it will use absolute directions.
     * 當你返回一個相對方向時(由getMovementFlags()所返回的方向),該方法使用的是相對方向,否則使用絕對方向
     * <p>
     * If you don't support swiping, this method will never be called.
     * 如果你不支援左右滑動,該方法不會被呼叫
     * <p>
     * ItemTouchHelper will keep a reference to the View until it is detached from
     * RecyclerView.
     * ItemTouchHelper將會持有View的引用,直到ItemTouchHelper不再附著在RecyclerView上時
     * As soon as it is detached, ItemTouchHelper will call
     * {@link #clearView(RecyclerView, RecyclerView.ViewHolder)}.
     * 當不再附著時,ItemTouchHelper會呼叫clearView方法
     *
     * @param viewHolder The ViewHolder which has been swiped by the user.
     *                   使用者拖拽的條目
     * @param direction  The direction to which the ViewHolder is swiped. It is one of
     *                   {UP}, {DOWN},
     *                   {LEFT} or {RIGHT}. If your
     *                   {@link #getMovementFlags(RecyclerView, RecyclerView.ViewHolder)}
     *                   method
     *                   returned relative flags instead of {LEFT} / {RIGHT};
     *                   `direction` will be relative as well. ({START} or {
     *                   END}).
     *                   條目被左右滑動的方向,他是上、下、左、右中的一個,如果getMovementFlags返回了
     *                   相對標誌flags代替了左、右方向,將使用這個返回的方向
     *
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }
    /**
     * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
     * 當條目被改變(拖拽、側滑)時,該方法被呼叫
     * <p/>
     * If you override this method, you should call super.
     * 如果你重寫該方法,你應該呼叫super
     *
     * @param viewHolder  The new ViewHolder that is being swiped or dragged. Might be null if
     *                    it is cleared.
     *                    正在被拖拽/側滑的條目,可能為null,如果他被清除掉
     * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
     *                    {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
     *                    {@link ItemTouchHelper#ACTION_STATE_DRAG}.
     *                    活動狀態:空閒、側滑、拖拽,根據這個判斷不同狀態下的操作
     *
     * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {//只要這個條目不是空閒狀態(即:拖拽或者側滑)
            ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemSelected();//這裡改變選中時的條目的狀態
        }

        super.onSelectedChanged(viewHolder, actionState);
    }
    /**
     * Called by the ItemTouchHelper when the user interaction with an element is over and it
     * also completed its animation.
     * 當ItemTouchHelper已經完成他的動畫時(即使用者與條目互動完成後),該方法被呼叫
     * <p>
     * This is a good place to clear all changes on the View that was done in
     * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
     * {@link #onChildDraw(Canvas, RecyclerView, RecyclerView.ViewHolder, float, float, int,
     * boolean)} or
     * {@link #onChildDrawOver(Canvas, RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}.
     * 這是一個很好的地方,用來清除所有條目動畫完成後的效果(可能你在onSelectedChanged()、onChildDraw()
     * onChildDrawOver()時賦予了條目很多效果,在這個方法裡,統統把這些效果清除掉)
     *
     * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
     * @param viewHolder   The View that was interacted by the user.
     */
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);

        ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
        itemViewHolder.onItemClear();//在這裡處理清除掉條目效果
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187

ItemTouchHelperAdapter

package co.paulburke.android.itemtouchhelperdemo.helper;

import android.support.v7.widget.RecyclerView;

/**
 * Interface to notify a {@link RecyclerView.Adapter} of moving and dismissal event from a {@link
 * android.support.v7.widget.helper.ItemTouchHelper.Callback}.
 *
 * @author Paul Burke (ipaulpro)
 */
public interface ItemTouchHelperAdapter {

    /**
     * Called when an item has been dragged far enough to trigger a move. This is called every time
     * an item is shifted, and not at the end of a "drop" event.
     *
     *當一個條目被拖拽移動時,該方法將被呼叫,當條目被移動時,該方法都會被呼叫,而不是在拖拽結尾才被呼叫
     *
     * @param fromPosition The start position of the moved item.
     * @param toPosition   Then end position of the moved item.
     * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
     * @see RecyclerView.ViewHolder#getAdapterPosition()
     */

    void onItemMove(int fromPosition, int toPosition);


    /**
     * Called when an item has been dismissed by a swipe.
     *
     * 當左右滑動條目時,呼叫該方法
     *
     * @param position The position of the item dismissed.
     * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
     * @see RecyclerView.ViewHolder#getAdapterPosition()
     */
    void onItemDismiss(int position);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

ItemTouchHelperViewHolder

package co.paulburke.android.itemtouchhelperdemo.helper;

import android.support.v7.widget.helper.ItemTouchHelper;

/**
 * Interface to notify an item ViewHolder of relevant callbacks from {@link
 * android.support.v7.widget.helper.ItemTouchHelper.Callback}.
 *
 * @author Paul Burke (ipaulpro)
 */
public interface ItemTouchHelperViewHolder {

    /**
     * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
     * Implementations should update the item view to indicate it's active state.
     * 當ItemTouchHelper將拖拽/側滑註冊(作用)到條目上時,實現該方法去更新這個條目的檢視效果,
     * 以暗示該條目正在被操作
     *
     */
    void onItemSelected();


    /**
     * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
     * state should be cleared.
     * 當ItemTouchHelper完成了拖拽/側滑時,啟用的條目效果應該被消除
     */
    void onItemClear();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

主要的程式碼:RecyclerListAdapter

package co.paulburke.android.itemtouchhelperdemo;

import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import co.paulburke.android.itemtouchhelperdemo.helper.ItemTouchHelperAdapter;
import co.paulburke.android.itemtouchhelperdemo.helper.ItemTouchHelperViewHolder;

/**
 * @author Paul Burke (ipaulpro)
 * ★ RecyclerListAdapter要實現ItemTouchHelperAdapter
 *    重寫onItemDismiss(刪除條目)和onItemMove(移動條目)
 *
 * ★ RecyclerView.ViewHolder實現ItemTouchHelperViewHolder
 *    重寫onItemSelected(條目被選中時)和onItemClear(條目被拖拽之後)
 */
public class RecyclerListAdapter extends RecyclerView.Adapter<RecyclerListAdapter.ItemViewHolder>
        implements ItemTouchHelperAdapter {

    private static final String[] STRINGS = new String[]{
            "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"
    };

    private final List<String> mItems = new ArrayList<>();

    public RecyclerListAdapter() {
        mItems.addAll(Arrays.asList(STRINGS));
    }

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

    @Override
    public void onBindViewHolder(final ItemViewHolder holder, int position) {
        holder.textView.setText(mItems.get(position));
    }

//   當條目被刪除時的操作(實現ItemTouchHelperAdapter重寫onItemDismiss方法)
    @Override
    public void onItemDismiss(int position) {
        mItems.remove(position);
//        通知條目已經被刪除,與adapter互動
        notifyItemRemoved(position);
    }

    //   當條目被移動時的操作(實現ItemTouchHelperAdapter重寫onItemMove方法)
    @Override
    public void onItemMove(int fromPosition, int toPosition) {
//        ★移動一個條目分兩步:首先把該條目先移除,再考慮在對應要移動到的地方增加這個條目
        String prev = mItems.remove(fromPosition);
//        toPosition > fromPosition ? toPosition - 1 : toPosition,
//        即將拖動到的位置是否比開始拖動的位置大,
//        如果大,則在集合裡在toPosition - 1處填寫該條目,否則在toPosition處新增該條目
        mItems.add(toPosition > fromPosition ? toPosition - 1 : toPosition, prev);
//        通知條目被移動,與adapter互動
        notifyItemMoved(fromPosition, toPosition);
    }

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

    public static class ItemViewHolder extends RecyclerView.ViewHolder implements
            ItemTouchHelperViewHolder {

        public final TextView textView;

        public ItemViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView;
        }

        @Override
        public void onItemSelected() {
//            當選中時,背景色為灰色
            itemView.setBackgroundColor(Color.LTGRAY);
        }

        @Override
        public void onItemClear() {
//            當拖拽完畢後,背景色為透明
            itemView.setBackgroundColor(0);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

完整原始碼:

github

可參考:https://www.toutiao.com/a6504214042020676110/?tt_from=dingtalk&utm_campaign=client_share&timestamp=1536198599&app=news_article&utm_source=dingtalk&iid=43255874374&utm_medium=toutiao_ios&dtshare_count=1&group_id=6504214042020676110