一個實現item可手動拖拽的ListView和GridView

 

上篇博客分享了一個實現ListView中item交換動畫的控件(戳這裏查看),但是有些情況下我們的需求比這種效果要複雜。比如說需要手動拖拽item來完成item交換的交互。像這樣:

還有這樣:

 

這次分享的控件就實現這樣的功能,下面開始正文。

先說實現item拖拽功能的DragListView。上代碼:

public class DragListView extends ListView {

    /**
     * 速度模板,影響視圖移動時的速度變化
     * <p>
     * MODE_LINEAR                 // 線性變化模式
     * MODE_ACCELERATE             // 加速模式
     * MODE_DECELERATE             // 減速模式
     * MODE_ACCELERATE_DECELERATE  // 先加速後加速模式
     */
    public static final int MODE_LINEAR = 0x001;
    public static final int MODE_ACCELERATE = 0x002;
    public static final int MODE_DECELERATE = 0x003;
    public static final int MODE_ACCELERATE_DECELERATE = 0x004;

    private Context context;

    // 拖動時的視圖
    private View dragView;


    private WindowManager windowManager;
    private WindowManager.LayoutParams windowLayoutParams;
    private BaseDragAdapter adapter;


    /**
     * 可設置選項
     */
    // 移動動畫儲持續時間,單位毫秒
    private long duration = 300;

    // 速度模板
    private int speedMode = MODE_ACCELERATE_DECELERATE;

    // 自動滾動的速度
    private int scrollSpeed = 50;


    /**
     * 運行參數
     */
    // 拖動塊的原始座標
    private int originalPosition = -1;

    // 拖動塊當前所在座標
    private int currentPosition = -1;

    // 用於記錄上次點擊事件MotionEvent.getX();
    private int lastX;

    // 用於記錄上次點擊事件MotionEvent.getY();
    private int lastY;

    // 用於記錄上次點擊事件MotionEvent.getRawX();
    private int lastRawX;

    // 用於記錄上次點擊事件MotionEvent.getRawY();
    private int lastRawY;

    // 拖動塊中心點x座標,用於判斷拖動塊所處的列表位置
    private int dragCenterX;

    // 拖動塊中心點y座標,用於判斷拖動塊所處的列表位置
    private int dragCenterY;

    // 滑動上邊界,拖動塊中心超過該邊界時列表自動向下滑動
    private int upScrollBorder;

    // 滑動下邊界,拖動塊中心超過該邊界時列表自動向上滑動
    private int downScrollBorder;

    // 狀態欄高度
    private int statusHeight;

    // 拖動時的列表刷新標識符
    private boolean dragRefresh;

    // 拖動鎖定標記,爲false時選中塊可被拖動
    private boolean dragLock = true;

    // 動畫列表,存放當前屏幕上正在播放的所有滑動動畫的動畫對象
    private ArrayList<Animator> animatorList;

    // 視圖列表,存放當前屏幕上正在播放的所有滑動動畫的視圖對象
    private ArrayList<View> dragViews;


    /**
     * 可監聽接口
     */
    // 拖動塊視圖對象生成器,可通過設置該接口自定義一個拖動視圖的樣式,不設置時會有默認實現
    private DragViewCreator dragViewCreator;

    // 拖動監聽接口,拖動開始和結束時會在該接口回調
    private OnDragingListener dragingListener;

    // 當前拖動目標位置改變時,每次改變都會在該接口回調
    private OnDragTargetChangedListener targetChangedListener;


    // 內部接口,動畫觀察者,滑動動畫結束是回調
    private AnimatorObserver animatorObserver;

    private Handler handler = new Handler();

    // 列表自動滾動線程
    private Runnable scrollRunnable = new Runnable() {
        @Override
        public void run() {
            int scrollY;

            // 滾動到頂或到底時停止滾動
            if (getFirstVisiblePosition() == 0 || getLastVisiblePosition() == getCount() - 1) {
                handler.removeCallbacks(scrollRunnable);
            }

            // 觸控點y座標超過上邊界時,出發列表自動向下滾動
            if (lastY > upScrollBorder) {
                scrollY = scrollSpeed;
                handler.postDelayed(scrollRunnable, 25);
            }
            // 觸控點y座標超過下邊界時,出發列表自動向上滾動
            else if (lastY < downScrollBorder) {
                scrollY = -scrollSpeed;
                handler.postDelayed(scrollRunnable, 25);
            }
            // // 觸控點y座標處於上下邊界之間時,停止滾動
            else {
                scrollY = 0;
                handler.removeCallbacks(scrollRunnable);
            }

            smoothScrollBy(scrollY, 10);
        }
    };

    public DragListView(Context context) {
        super(context);
        init(context);
    }

    public DragListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public DragListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * 初始化方法
     *
     * @param context
     */
    private void init(Context context) {
        this.context = context;
        statusHeight = getStatusHeight();
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        animatorList = new ArrayList<>();
        dragViews = new ArrayList<>();

        // 拖動塊視圖對象生成器的默認實現,返回一個與被拖動項外觀一致的ImageView
        dragViewCreator = new DragViewCreator() {
            @Override
            public View createDragView(int width, int height, Bitmap viewCache) {
                ImageView imageView = new ImageView(DragListView.this.context);
                imageView.setImageBitmap(viewCache);

                return imageView;
            }
        };
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent motionEvent) {

        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:

                downScrollBorder = getHeight() / 5;
                upScrollBorder = getHeight() * 4 / 5;

                // 手指按下時記錄相關座標
                lastX = (int) motionEvent.getX();
                lastY = (int) motionEvent.getY();
                lastRawX = (int) motionEvent.getRawX();
                lastRawY = (int) motionEvent.getRawY();

                currentPosition = pointToPosition(lastRawX, lastRawY);

                if (currentPosition == AdapterView.INVALID_POSITION || !adapter.isDragAvailable(currentPosition)) {
                    return true;
                }

                originalPosition = currentPosition;

                break;
        }

        return super.dispatchTouchEvent(motionEvent);
    }

    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_MOVE:

                if (!dragLock) {
                    int currentRawX = (int) motionEvent.getRawX();
                    int currentRawY = (int) motionEvent.getRawY();

                    if (dragView == null) {
                        createDragImageView(getChildAt(pointToPosition(lastRawX, lastRawY) - getFirstVisiblePosition()));

                        getChildAt(originalPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);

                        if (dragingListener != null) {
                            dragingListener.onStart(originalPosition);
                        }
                    }
                    drag(currentRawY - lastRawY);
                    if (dragingListener != null) {
                        dragingListener.onDraging((int) motionEvent.getX(), (int) motionEvent.getY(), currentRawX, currentRawY);
                    }

                    int position = pointToPosition(dragCenterX, dragCenterY);

                    // 滿足交換條件時讓目標位置的原有視圖上滑或下滑
                    if (position != AdapterView.INVALID_POSITION && currentPosition != position && adapter.isDragAvailable(position)) {
                        translation(position, currentPosition);
                        currentPosition = position;

                        if (targetChangedListener != null) {
                            targetChangedListener.onTargetChanged(currentPosition);
                        }
                    }

                    // 更新點擊位置
                    lastX = (int) motionEvent.getX();
                    lastY = (int) motionEvent.getY();
                    lastRawX = currentRawX;
                    lastRawY = currentRawY;

                    // 返回true消耗掉這次點擊事件,防止ListView本身接收到這次點擊事件後觸發滾動
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                // 手指擡起時,如果所有滑動動畫都已播放完畢,則直接執行拖動完成邏輯
                if (animatorList.size() == 0) {
                    resetDataAndView();
                    if (dragingListener != null) {
                        dragingListener.onFinish(currentPosition);
                    }
                }
                // 如果還有未播放完成的滑動動畫,則註冊觀察者,延時執行拖動完成邏輯
                else {
                    animatorObserver = new AnimatorObserver() {
                        @Override
                        public void onAllAnimatorFinish() {
                            resetDataAndView();
                            if (dragingListener != null) {
                                dragingListener.onFinish(currentPosition);
                            }
                        }
                    };
                }
                break;
        }

        return super.onTouchEvent(motionEvent);
    }

    /**
     * 創建拖動塊視圖方法
     *
     * @param view 被拖動位置的視圖對象
     */
    private void createDragImageView(View view) {

        if (view == null) {
            return;
        }

        removeDragImageView();
        int[] location = new int[2];

        view.getLocationOnScreen(location);
        view.setDrawingCacheEnabled(true);
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
        view.destroyDrawingCache();

        windowLayoutParams = new WindowManager.LayoutParams();
        windowLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
        windowLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        windowLayoutParams.format = PixelFormat.TRANSPARENT;
        windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        windowLayoutParams.x = location[0];
        windowLayoutParams.y = location[1] - statusHeight;
        windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

        dragCenterX = windowLayoutParams.x + view.getWidth() / 2;
        dragCenterY = windowLayoutParams.y + view.getHeight() / 2;

        dragView = dragViewCreator.createDragView(view.getWidth(), view.getHeight(), bitmap);

        if (dragView == null) {
            throw new NullPointerException("dragView can not be null");
        } else {
            windowManager.addView(dragView, windowLayoutParams);
        }
    }

    /**
     * 移除拖動視圖方法
     */
    private void removeDragImageView() {
        if (dragView != null && windowManager != null) {
            windowManager.removeView(dragView);
            dragView = null;
            windowLayoutParams = null;
        }
    }

    /**
     * 拖動方法
     *
     * @param dy
     */
    private void drag(int dy) {
        dragCenterY += dy;
        windowLayoutParams.y += dy;
        windowManager.updateViewLayout(dragView, windowLayoutParams);

        handler.post(scrollRunnable);
    }

    /**
     * 移動指定位置視圖方法
     *
     * @param fromPosition 移動起始位置
     * @param toPosition   移動目標位置
     */
    private void translation(int fromPosition, int toPosition) {

        View fromView = getChildAt(fromPosition - getFirstVisiblePosition());
        View toView = getChildAt(toPosition - getFirstVisiblePosition());

        if (fromView == null || toView == null) {
            return;
        }

        float distance = (toView.getY() - toView.getTranslationY()) - (fromView.getY() - fromView.getTranslationY());

        ObjectAnimator animator = ObjectAnimator.ofFloat(fromView, "translationY", 0, distance);
        animator.setDuration(duration);
        animator.setInterpolator(getAnimatorInterpolator());
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                animatorList.remove(animation);
                // 所有滑動動畫都播放結束時,執行相關操作
                if (animatorList.size() == 0) {
                    // 重置所有滑動過的視圖的translateY,避免列表刷新後視圖重疊
                    resetTranslate(dragViews);
                    dragViews.clear();
                    adapter.exchangeData(originalPosition, currentPosition);
                    addOnLayoutChangeListener(new OnLayoutChangeListener() {
                        @Override
                        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                            if (dragRefresh) {
                                removeOnLayoutChangeListener(this);
                                resetChildVisibility();
                                getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
                                originalPosition = currentPosition;
                                dragRefresh = false;

                                if (animatorObserver != null) {
                                    animatorObserver.onAllAnimatorFinish();
                                    animatorObserver = null;
                                }
                            }
                        }
                    });
                    dragRefresh = true;
                    adapter.notifyDataSetChanged();
                }
            }
        });

        animatorList.add(animator);
        dragViews.add(fromView);
        animator.start();
    }

    /**
     * 重置列表所有項的可見性方法
     */
    private void resetChildVisibility() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child != null) {
                child.setVisibility(VISIBLE);
            }
        }
    }

    /**
     * 重置指定視圖的translateY屬性方法
     *
     * @param list
     */
    private void resetTranslate(ArrayList<View> list) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) != null) {
                list.get(i).setTranslationY(0);
            }
        }
    }

    /**
     * 重置數據和視圖相關數據方法
     */
    private void resetDataAndView() {
        if (currentPosition == -1) {
            return;
        }
        getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.VISIBLE);
        originalPosition = -1;
        currentPosition = -1;
        handler.removeCallbacks(scrollRunnable);
        removeDragImageView();
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (adapter instanceof BaseDragAdapter) {
            this.adapter = (BaseDragAdapter) adapter;
            super.setAdapter(adapter);
        } else {
            throw new IllegalStateException("the adapter must extends BaseDragAdapter");
        }
    }

    /**
     * 根據速度模板創建動畫迭代器
     *
     * @return
     */
    private Interpolator getAnimatorInterpolator() {
        switch (speedMode) {
            case MODE_LINEAR:
                return new LinearInterpolator();
            case MODE_ACCELERATE:
                return new AccelerateInterpolator();
            case MODE_DECELERATE:
                return new DecelerateInterpolator();
            case MODE_ACCELERATE_DECELERATE:
                return new AccelerateDecelerateInterpolator();
            default:
                return null;
        }
    }

    /**
     * 拖動解鎖方法,調用者需手動調用該方法後才能開啓列表拖動功能
     */
    public void unlockDrag() {
        dragLock = false;
    }

    /**
     * 拖動鎖定方法,調用者調用該方法後關閉列表拖動功能
     */
    public void lockDrag() {
        dragLock = true;
    }

    /**
     * 設置移動動畫持續時間
     *
     * @param duration 時間,單位毫秒
     */
    public void setDuration(long duration) {
        this.duration = duration;
    }

    /**
     * 設置速度模式,可選項:
     * MODE_LINEAR                線性變化模式
     * MODE_ACCELERATE            加速模式
     * MODE_DECELERATE            減速模式
     * MODE_ACCELERATE_DECELERATE 先加速後加速模式
     *
     * @param speedMode
     */
    public void setSpeedMode(int speedMode) {
        this.speedMode = speedMode;
    }

    /**
     * 設置自動滾動速度
     *
     * @param scrollSpeed 速度,單位:dp/10ms
     */
    public void setScrollSpeed(int scrollSpeed) {
        this.scrollSpeed = scrollSpeed;
    }

    /**
     * 設置拖動塊視圖對象生成器方法
     *
     * @param creator
     */
    public void setDragViewCreator(DragViewCreator creator) {
        if (creator == null) {
            return;
        }

        this.dragViewCreator = creator;
    }

    /**
     * 設置拖動監聽接口
     *
     * @param dragingListener
     */
    public void setOnDragingListener(OnDragingListener dragingListener) {
        this.dragingListener = dragingListener;
    }

    /**
     * 設置拖動目標位置改變監聽接口
     *
     * @param targetChangedListener
     */
    public void setOnDragTargetChangedListener(OnDragTargetChangedListener targetChangedListener) {
        this.targetChangedListener = targetChangedListener;
    }

    private int getStatusHeight() {
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            return context.getResources().getDimensionPixelSize(resourceId);
        }

        return 0;
    }

    /**
     * 動畫觀察者
     */
    private interface AnimatorObserver {

        /**
         * 滑動動畫播放結束時回調
         */
        void onAllAnimatorFinish();

    }

    /**
     * 拖動塊視圖對象生成器
     */
    public interface DragViewCreator {

        /**
         * 創建拖動塊視圖對象方法,可通過實現該方法自定義拖動塊樣式
         */
        View createDragView(int width, int height, Bitmap viewCache);

    }

    /**
     * 拖動監聽接口
     */
    public interface OnDragingListener {

        /**
         * 拖動開始時回調
         *
         * @param startPosition 拖動起始座標
         */
        void onStart(int startPosition);

        /**
         * 拖動過程中回調
         *
         * @param x    觸控點相對ListView的x座標
         * @param y    觸控點相對ListView的y座標
         * @param rawX 觸控點相對屏幕的x座標
         * @param rawY 觸控點相對屏幕的y座標
         */
        void onDraging(int x, int y, int rawX, int rawY);

        /**
         * 拖動結束時回調
         *
         * @param finalPosition 拖動終點座標
         */
        void onFinish(int finalPosition);

    }

    /**
     * 拖動目標位置改變監聽接口
     */
    public interface OnDragTargetChangedListener {

        /**
         * 拖動過程中,每次目標位置改變,會在該方法回調
         *
         * @param targetPosition 拖動目標位置座標
         */
        void onTargetChanged(int targetPosition);

    }
}

簡單講一下實現原理。手指按下時通過ListView的getChildAt方法獲得按下位置的item並獲取其視圖緩存,也就是這句話:

view.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
view.destroyDrawingCache();

然後新建一個View把這個緩存塞進去並置於屏幕之上,並隱藏原來的item,讓人看起來就好像是item被“拽”了下來,也就是這句話:

windowManager.addView(dragView, windowLayoutParams);

手指移動時,改變這個View的LayoutParams的y座標值,讓它跟隨手指移動,也就是這兩句話:

windowLayoutParams.y += dy;
windowManager.updateViewLayout(dragView, windowLayoutParams);

拖拽過程中,當判定交換行爲發生時,用一個屬性動畫不斷改變目標item的translationY屬性來實現交換效果,也就是這句話:

ObjectAnimator animator = ObjectAnimator.ofFloat(fromView, "translationY", 0, distance);

具體代碼大家可以看註釋,應該寫得比較清楚了。

要特別說明的是,DragListView的setAdapter方法被重寫了,只接收BaseDragAdapter的繼承類,BaseDragAdapter長這樣:
 

public abstract class BaseDragAdapter extends BaseAdapter {

    /**
     * 調用者需實現該方法,返回列表的所有數據集合
     *
     * @return
     */
    public abstract List getDataList();

    /**
     * 調用者可實現該方法自定義某一項是否可被拖動
     *
     * @param position
     * @return
     */
    public abstract boolean isDragAvailable(int position);

    /**
     * 實現數據交換方法
     *
     * @param oldPosition
     * @param newPosition
     */
    public void exchangeData(int oldPosition, int newPosition) {

        List list = getDataList();
        if (list == null) {
            return;
        }
        Object temp = list.get(oldPosition);
        if (oldPosition < newPosition) {
            for (int i = oldPosition; i < newPosition; i++) {
                Collections.swap(list, i, i + 1);
            }
        } else if (oldPosition > newPosition) {
            for (int i = oldPosition; i > newPosition; i--) {
                Collections.swap(list, i, i - 1);
            }
        }
        list.set(newPosition, temp);
    }
}

BaseDragAdapter的目的是替調用者封裝一些必要的操作,它給普通的BaseAdapter增加了兩個需要實現的抽象方法:getDataList()和isDragAvailable()。getDataList()返回ListView 的數據列表即可,isDragAvailable()用來讓調用者決定某個item是否可被拖拽,比如說需求是列表的第一項不可被拖拽,只需要實現isDragAvailable方法,在position=0時返回false即可。

然後就可以使用了。先寫一個item的佈局:
 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#FFFAFA"
    android:orientation="vertical">

    <TextView
        android:id="@+id/content_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="15dp"
        android:paddingTop="20dp"
        android:paddingRight="15dp"
        android:paddingBottom="20dp"
        android:text="我是內容"
        android:textSize="20sp" />

    <View
        android:id="@+id/divider_line"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginLeft="15dp"
        android:background="#eeeeee" />

</LinearLayout>

再簡單寫一個適配器TestListViewAdapter繼承自BaseDragAdapter:

public class TestListViewAdapter extends BaseDragAdapter {

    private Context context;
    private int resourceId;
    private ArrayList<String> list;

    private Vibrator vibrator;

    private OnItemLongClickListener listener;

    public TestListViewAdapter(Context context, int resourceId, ArrayList<String> list) {
        this.context = context;
        this.resourceId = resourceId;
        this.list = list;
        this.vibrator = (Vibrator) context.getSystemService(context.VIBRATOR_SERVICE);
    }

    @Override
    public List getDataList() {
        return list;
    }

    @Override
    public boolean isDragAvailable(int position) {
        return true;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId, null);
            viewHolder = new ViewHolder();
            viewHolder.itemLayout = view.findViewById(R.id.item_layout);
            viewHolder.contentTextView = view.findViewById(R.id.content_textview);
            viewHolder.dividerLine = view.findViewById(R.id.divider_line);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }

        viewHolder.contentTextView.setText(list.get(position));

        viewHolder.dividerLine.setVisibility(position != list.size() - 1 ? View.VISIBLE : View.INVISIBLE);

        viewHolder.itemLayout.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                vibrator.vibrate(100);

                if (listener != null) {
                    listener.onItemLongClick(position);
                }

                return false;
            }
        });

        return view;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        this.listener = listener;
    }

    public interface OnItemLongClickListener {

        void onItemLongClick(int position);

    }

    class ViewHolder {
        LinearLayout itemLayout;
        TextView contentTextView;
        View dividerLine;
    }
}

代碼很簡單,就不多說了。

最後就可以使用了,Activity裏這樣寫:
 

private DragListView dragListView;

private TestListViewAdapter adapter;

private ArrayList<String> list;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_drag_listview_test);

    initData();
    initView();
}

private void initData() {
    list = new ArrayList<>();

    for (int i = 1; i <= 40; i++) {
        list.add("我是第" + i + "條數據");
    }
}

private void initView() {
    dragListView = findViewById(R.id.drag_listview);

    dragListView.setOnDragingListener(new DragListView.OnDragingListener() {
        @Override
        public void onStart(int startPosition) {

        }

        @Override
        public void onDraging(int x, int y, int rawX, int rawY) {

        }

        @Override
        public void onFinish(int finalPosition) {
            dragListView.lockDrag();
        }
    });

    adapter = new TestListViewAdapter(this, R.layout.item_test_listview, list);

    adapter.setOnItemLongClickListener(new TestListViewAdapter.OnItemLongClickListener() {
        @Override
        public void onItemLongClick(int position) {
            dragListView.unlockDrag();
        }
    });

    dragListView.setAdapter(adapter);
}

用法和普通的ListView一樣,通過調用unlockDrag()來解鎖拖動(示例代碼中通過長按操作來解鎖),通過調用lockDrag()方法來鎖定拖動。之後還可以通過設置OnDragingListener來監聽拖拽過程。開啓和鎖定拖動操作的條件視項目需求而定,比如長安開啓,或者按編輯按鈕開啓等等。

最後運行一下就可以看到開頭的效果了。

控件支持自定義拖拽View的樣式。可以通過setDragViewCreator()方法來實現。比如說我想給拖拽的View加一個高亮效果,就可以這樣寫:

dragListView.setDragViewCreator(new DragListView.DragViewCreator() {
    @Override
    public View createDragView(int width, int height, Bitmap viewCache) {
        RelativeLayout layout = new RelativeLayout(DragListViewTestActivity.this);

        ImageView imageView = new ImageView(DragListViewTestActivity.this);
        imageView.setImageBitmap(viewCache);
        layout.addView(imageView, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));

        View view = new View(DragListViewTestActivity.this);
        view.setBackground(getDrawable(R.drawable.edging_red));
        layout.addView(view, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));

        return layout;
    }
});

其中高亮的資源edging_red.xml長這樣:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <stroke
        android:width="1dp"
        android:color="#FF6347" />

    <solid android:color="#70FFC0CB" />

</shape>

代碼很簡單,就是新建一個Layout,裏面放一張圖片,再在之上加一層高亮遮罩,並將這個layout返回給DragViewCreator接口即可。運行一下看一下效果:

 

同樣的原理再寫一個支持item拖拽的GridView,上源碼:

public class DragGridView extends GridView {

    /**
     * 速度模板,影響視圖移動時的速度變化
     * <p>
     * MODE_LINEAR                 // 線性變化模式
     * MODE_ACCELERATE             // 加速模式
     * MODE_DECELERATE             // 減速模式
     * MODE_ACCELERATE_DECELERATE  // 先加速後加速模式
     */
    public static final int MODE_LINEAR = 0x001;
    public static final int MODE_ACCELERATE = 0x002;
    public static final int MODE_DECELERATE = 0x003;
    public static final int MODE_ACCELERATE_DECELERATE = 0x004;

    private Context context;

    // 拖動時的視圖
    private View dragView;


    private WindowManager windowManager;
    private WindowManager.LayoutParams windowLayoutParams;
    private BaseDragAdapter adapter;

    /**
     * 可設置選項
     */
    // 移動動畫儲持續時間,單位毫秒
    private long duration = 300;

    // 速度模板
    private int speedMode = MODE_ACCELERATE_DECELERATE;

    // 自動滾動的速度
    private int scrollSpeed = 50;


    /**
     * 運行參數
     */
    // 拖動塊的原始座標
    private int originalPosition = -1;

    // 拖動塊當前所在座標
    private int currentPosition = -1;

    // 用於記錄上次點擊事件MotionEvent.getX();
    private int lastX;

    // 用於記錄上次點擊事件MotionEvent.getY();
    private int lastY;

    // 用於記錄上次點擊事件MotionEvent.getRawX();
    private int lastRawX;

    // 用於記錄上次點擊事件MotionEvent.getRawY();
    private int lastRawY;

    // 拖動塊中心點x座標,用於判斷拖動塊所處的列表位置
    private int dragCenterX;

    // 拖動塊中心點y座標,用於判斷拖動塊所處的列表位置
    private int dragCenterY;

    // 滑動上邊界,拖動塊中心超過該邊界時列表自動向下滑動
    private int upScrollBorder;

    // 滑動下邊界,拖動塊中心超過該邊界時列表自動向上滑動
    private int downScrollBorder;

    // 狀態欄高度
    private int statusHeight;

    // 拖動時的列表刷新標識符
    private boolean dragRefresh;

    // 拖動鎖定標記,爲false時選中塊可被拖動
    private boolean dragLock = true;

    // 動畫列表,存放當前屏幕上正在播放的所有滑動動畫的動畫對象
    private ArrayList<Animator> animatorList;

    // 視圖列表,存放當前屏幕上正在播放的所有滑動動畫的視圖對象
    private ArrayList<View> dragViews;

    /**
     * 可監聽接口
     */
    // 拖動塊視圖對象生成器,可通過設置該接口自定義一個拖動視圖的樣式,不設置時會有默認實現
    private DragViewCreator dragViewCreator;

    // 拖動監聽接口,拖動開始和結束時會在該接口回調
    private OnDragingListener dragingListener;

    // 當前拖動目標位置改變時,每次改變都會在該接口回調
    private OnDragTargetChangedListener targetChangedListener;


    // 內部接口,動畫觀察者,滑動動畫結束是回調
    private AnimatorObserver animatorObserver;

    private Handler handler = new Handler();

    // 列表自動滾動線程
    private Runnable scrollRunnable = new Runnable() {
        @Override
        public void run() {
            int scrollY;

            // 滾動到頂或到底時停止滾動
            if (getFirstVisiblePosition() == 0 || getLastVisiblePosition() == getCount() - 1) {
                handler.removeCallbacks(scrollRunnable);
            }

            // 觸控點y座標超過上邊界時,出發列表自動向下滾動
            if (lastY > upScrollBorder) {
                scrollY = scrollSpeed;
                handler.postDelayed(scrollRunnable, 25);
            }
            // 觸控點y座標超過下邊界時,出發列表自動向上滾動
            else if (lastY < downScrollBorder) {
                scrollY = -scrollSpeed;
                handler.postDelayed(scrollRunnable, 25);
            }
            // // 觸控點y座標處於上下邊界之間時,停止滾動
            else {
                scrollY = 0;
                handler.removeCallbacks(scrollRunnable);
            }

            smoothScrollBy(scrollY, 10);
        }
    };

    public DragGridView(Context context) {
        super(context);
        init(context);
    }

    public DragGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * 初始化方法
     *
     * @param context
     */
    private void init(Context context) {
        this.context = context;
        statusHeight = getStatusHeight();
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        animatorList = new ArrayList<>();
        dragViews = new ArrayList<>();

        // 拖動塊視圖對象生成器的默認實現,返回一個與被拖動項外觀一致的ImageView
        dragViewCreator = new DragGridView.DragViewCreator() {
            @Override
            public View createDragView(int width, int height, Bitmap viewCache) {
                ImageView imageView = new ImageView(DragGridView.this.context);
                imageView.setImageBitmap(viewCache);

                return imageView;
            }
        };
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent motionEvent) {

        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:

                downScrollBorder = getHeight() / 5;
                upScrollBorder = getHeight() * 4 / 5;

                // 手指按下時記錄相關座標
                lastX = (int) motionEvent.getX();
                lastY = (int) motionEvent.getY();
                lastRawX = (int) motionEvent.getRawX();
                lastRawY = (int) motionEvent.getRawY();

                currentPosition = pointToPosition(lastRawX, lastRawY);

                if (currentPosition == AdapterView.INVALID_POSITION || !adapter.isDragAvailable(currentPosition)) {
                    return true;
                }

                originalPosition = currentPosition;

                break;
        }

        return super.dispatchTouchEvent(motionEvent);
    }

    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (!dragLock) {
                    int currentRawX = (int) motionEvent.getRawX();
                    int currentRawY = (int) motionEvent.getRawY();

                    if (dragView == null) {
                        createDragImageView(getChildAt(pointToPosition(lastRawX, lastRawY) - getFirstVisiblePosition()));

                        getChildAt(originalPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);

                        if (dragingListener != null) {
                            dragingListener.onStart(originalPosition);
                        }
                    }
                    drag(currentRawX - lastRawX, currentRawY - lastRawY);
                    if (dragingListener != null) {
                        dragingListener.onDraging((int) motionEvent.getX(), (int) motionEvent.getY(), currentRawX, currentRawY);
                    }

                    int position = pointToPosition(dragCenterX, dragCenterY);

                    if (position != AdapterView.INVALID_POSITION
                            && currentPosition != position
                            && adapter.isDragAvailable(position)
                            && animatorList.size() == 0) {

                        translation(position, currentPosition);

                        currentPosition = position;

                        if (targetChangedListener != null) {
                            targetChangedListener.onTargetChanged(currentPosition);
                        }
                    }

                    // 更新點擊位置
                    lastX = (int) motionEvent.getX();
                    lastY = (int) motionEvent.getY();
                    lastRawX = currentRawX;
                    lastRawY = currentRawY;

                    // 返回true消耗掉這次點擊事件,防止ListView本身接收到這次點擊事件後觸發滾動
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                // 手指擡起時,如果所有滑動動畫都已播放完畢,則直接執行拖動完成邏輯
                if (animatorList.size() == 0) {
                    resetDataAndView();
                    if (dragingListener != null) {
                        dragingListener.onFinish(currentPosition);
                    }
                }
                // 如果還有未播放完成的滑動動畫,則註冊觀察者,延時執行拖動完成邏輯
                else {
                    animatorObserver = new AnimatorObserver() {
                        @Override
                        public void onAllAnimatorFinish() {
                            resetDataAndView();
                            if (dragingListener != null) {
                                dragingListener.onFinish(currentPosition);
                            }
                        }
                    };
                }
                break;
        }

        return super.onTouchEvent(motionEvent);
    }

    /**
     * 創建拖動塊視圖方法
     *
     * @param view 被拖動位置的視圖對象
     */
    private void createDragImageView(View view) {

        if (view == null) {
            return;
        }

        removeDragImageView();
        int[] location = new int[2];

        view.getLocationOnScreen(location);
        view.setDrawingCacheEnabled(true);
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
        view.destroyDrawingCache();

        windowLayoutParams = new WindowManager.LayoutParams();
        windowLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
        windowLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        windowLayoutParams.format = PixelFormat.TRANSPARENT;
        windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        windowLayoutParams.x = location[0];
        windowLayoutParams.y = location[1] - statusHeight;
        windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

        dragCenterX = windowLayoutParams.x + view.getWidth() / 2;
        dragCenterY = windowLayoutParams.y + view.getHeight() / 2;

        dragView = dragViewCreator.createDragView(view.getWidth(), view.getHeight(), bitmap);

        if (dragView == null) {
            throw new NullPointerException("dragView can not be null");
        } else {
            windowManager.addView(dragView, windowLayoutParams);
        }
    }

    /**
     * 移除拖動視圖方法
     */
    private void removeDragImageView() {
        if (dragView != null && windowManager != null) {
            windowManager.removeView(dragView);
            dragView = null;
            windowLayoutParams = null;
        }
    }

    /**
     * 拖動方法
     *
     * @param dx
     * @param dy
     */
    private void drag(int dx, int dy) {

        dragCenterX += dx;
        dragCenterY += dy;
        windowLayoutParams.x += dx;
        windowLayoutParams.y += dy;
        windowManager.updateViewLayout(dragView, windowLayoutParams);

        handler.post(scrollRunnable);
    }

    /**
     * 移動指定位置視圖方法
     *
     * @param fromPosition 移動起始位置
     * @param toPosition   移動目標位置
     */
    private void translation(int fromPosition, int toPosition) {
        ArrayList<Animator> list = new ArrayList<>();
        if (toPosition > fromPosition) {
            for (int position = fromPosition; position < toPosition; position++) {
                View view = getChildAt(position - getFirstVisiblePosition());
                dragViews.add(view);
                if ((position + 1) % getNumColumns() == 0) {
                    list.add(createTranslationAnimations(view,
                            0,
                            -(view.getWidth() + getVerticalSpacing()) * (getNumColumns() - 1),
                            0,
                            view.getHeight() + getHorizontalSpacing()));
                } else {
                    list.add(createTranslationAnimations(view,
                            0,
                            view.getWidth() + getVerticalSpacing(),
                            0,
                            0));
                }
            }
        } else {
            for (int position = fromPosition; position > toPosition; position--) {
                View view = getChildAt(position - getFirstVisiblePosition());
                dragViews.add(view);
                if (position % getNumColumns() == 0) {
                    list.add(createTranslationAnimations(view,
                            0,
                            (view.getWidth() + getVerticalSpacing()) * (getNumColumns() - 1),
                            0,
                            -(view.getHeight() + getHorizontalSpacing())));
                } else {
                    list.add(createTranslationAnimations(view,
                            0,
                            -(view.getWidth() + getVerticalSpacing()),
                            0,
                            0));
                }
            }
        }

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(list);
        animatorSet.setDuration(duration);
        animatorSet.setInterpolator(getAnimatorInterpolator());
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                animatorList.remove(animation);
                // 所有滑動動畫都播放結束時,執行相關操作
                if (animatorList.size() == 0) {
                    // 重置所有滑動過的視圖的translateY,避免列表刷新後視圖重疊
                    resetTranslate(dragViews);
                    dragViews.clear();
                    adapter.exchangeData(originalPosition, currentPosition);
                    addOnLayoutChangeListener(new OnLayoutChangeListener() {
                        @Override
                        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                            if (dragRefresh) {
                                removeOnLayoutChangeListener(this);
                                resetChildVisibility();
                                getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
                                originalPosition = currentPosition;
                                dragRefresh = false;

                                if (animatorObserver != null) {
                                    animatorObserver.onAllAnimatorFinish();
                                    animatorObserver = null;
                                }
                            }
                        }
                    });
                    dragRefresh = true;
                    adapter.notifyDataSetChanged();
                }
            }
        });

        animatorList.add(animatorSet);
        animatorSet.start();
    }

    /**
     * 生成移動動畫方法
     *
     * @param view   需要移動的視圖
     * @param startX 移動起始x座標
     * @param endX   移動終點x座標
     * @param startY 移動起始y座標
     * @param endY   移動終點y座標
     * @return
     */
    private Animator createTranslationAnimations(View view, float startX, float endX, float startY, float endY) {
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", startX, endX);
        ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", startY, endY);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(animatorX, animatorY);
        return animatorSet;
    }

    /**
     * 重置列表所有項的可見性方法
     */
    private void resetChildVisibility() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child != null) {
                child.setVisibility(VISIBLE);
            }
        }
    }

    /**
     * 重置指定視圖的translateY屬性方法
     *
     * @param list
     */
    private void resetTranslate(ArrayList<View> list) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) != null) {
                list.get(i).setTranslationX(0);
                list.get(i).setTranslationY(0);
            }
        }
    }

    /**
     * 重置數據和視圖相關數據方法
     */
    private void resetDataAndView() {
        if (currentPosition == -1) {
            return;
        }
        getChildAt(currentPosition - getFirstVisiblePosition()).setVisibility(View.VISIBLE);
        originalPosition = -1;
        currentPosition = -1;
        dragLock = true;
        handler.removeCallbacks(scrollRunnable);
        removeDragImageView();
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (adapter instanceof BaseDragAdapter) {
            this.adapter = (BaseDragAdapter) adapter;
            super.setAdapter(adapter);
        } else {
            throw new IllegalStateException("the adapter must extends BaseDragAdapter");
        }
    }

    /**
     * 根據速度模板創建動畫迭代器
     *
     * @return
     */
    private Interpolator getAnimatorInterpolator() {
        switch (speedMode) {
            case MODE_LINEAR:
                return new LinearInterpolator();
            case MODE_ACCELERATE:
                return new AccelerateInterpolator();
            case MODE_DECELERATE:
                return new DecelerateInterpolator();
            case MODE_ACCELERATE_DECELERATE:
                return new AccelerateDecelerateInterpolator();
            default:
                return null;
        }
    }

    /**
     * 拖動解鎖方法,調用者需手動調用該方法後才能開啓列表拖動功能
     */
    public void unlockDrag() {
        dragLock = false;
    }

    /**
     * 拖動鎖定方法,調用者調用該方法後關閉列表拖動功能
     */
    public void lockDrag() {
        dragLock = true;
    }

    /**
     * 設置移動動畫持續時間
     *
     * @param duration 時間,單位毫秒
     */
    public void setDuration(long duration) {
        this.duration = duration;
    }

    /**
     * 設置速度模式,可選項:
     * MODE_LINEAR                線性變化模式
     * MODE_ACCELERATE            加速模式
     * MODE_DECELERATE            減速模式
     * MODE_ACCELERATE_DECELERATE 先加速後加速模式
     *
     * @param speedMode
     */
    public void setSpeedMode(int speedMode) {
        this.speedMode = speedMode;
    }

    /**
     * 設置自動滾動速度
     *
     * @param scrollSpeed 速度,單位:dp/10ms
     */
    public void setScrollSpeed(int scrollSpeed) {
        this.scrollSpeed = scrollSpeed;
    }

    /**
     * 設置拖動塊視圖對象生成器方法
     *
     * @param creator
     */
    public void setDragViewCreator(DragViewCreator creator) {
        if (creator == null) {
            return;
        }

        this.dragViewCreator = creator;
    }

    /**
     * 設置拖動監聽接口
     *
     * @param dragingListener
     */
    public void setOnDragingListener(OnDragingListener dragingListener) {
        this.dragingListener = dragingListener;
    }

    /**
     * 設置拖動目標位置改變監聽接口
     *
     * @param targetChangedListener
     */
    public void setOnDragTargetChangedListener(OnDragTargetChangedListener targetChangedListener) {
        this.targetChangedListener = targetChangedListener;
    }

    private int getStatusHeight() {
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            return context.getResources().getDimensionPixelSize(resourceId);
        }

        return 0;
    }

    /**
     * 動畫觀察者
     */
    private interface AnimatorObserver {

        /**
         * 滑動動畫播放結束時回調
         */
        void onAllAnimatorFinish();

    }

    /**
     * 拖動塊視圖對象生成器
     */
    public interface DragViewCreator {

        /**
         * 創建拖動塊視圖對象方法,可通過實現該方法自定義拖動塊樣式
         */
        View createDragView(int width, int height, Bitmap viewCache);

    }

    /**
     * 拖動監聽接口
     */
    public interface OnDragingListener {

        /**
         * 拖動開始時回調
         *
         * @param startPosition 拖動起始座標
         */
        void onStart(int startPosition);

        /**
         * 拖動過程中回調
         *
         * @param x    觸控點相對ListView的x座標
         * @param y    觸控點相對ListView的y座標
         * @param rawX 觸控點相對屏幕的x座標
         * @param rawY 觸控點相對屏幕的y座標
         */
        void onDraging(int x, int y, int rawX, int rawY);

        /**
         * 拖動結束時回調
         *
         * @param finalPosition 拖動終點座標
         */
        void onFinish(int finalPosition);

    }

    /**
     * 拖動目標位置改變監聽接口
     */
    public interface OnDragTargetChangedListener {

        /**
         * 拖動過程中,每次目標位置改變,會在該方法回調
         *
         * @param targetPosition 拖動目標位置座標
         */
        void onTargetChanged(int targetPosition);

    }
}

實現原理和DragListView差不多,就不多做解釋了。DragGridView的setAdapter方法同樣只接收BaseDragAdapter的繼承類,用法和DragListView一樣。

簡單使用一下,先寫一個item佈局item_test_gridview.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_layout"
    android:layout_width="match_parent"
    android:minHeight="130dp"
    android:layout_height="match_parent"
    android:background="#FFFAFA"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <TextView
        android:id="@+id/content_textview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:padding="15dp"
        android:text="我是內容"
        android:textSize="20sp" />

</LinearLayout>

再寫一個適配器TestGridViewAdapter:

public class TestGridViewAdapter extends BaseDragAdapter {

    private Context context;
    private int resourceId;
    private ArrayList<String> list;

    private Vibrator vibrator;

    private OnItemLongClickListener listener;

    public TestGridViewAdapter(Context context, int resourceId, ArrayList<String> list) {
        this.context = context;
        this.resourceId = resourceId;
        this.list = list;
        this.vibrator = (Vibrator) context.getSystemService(context.VIBRATOR_SERVICE);
    }

    @Override
    public List getDataList() {
        return list;
    }

    @Override
    public boolean isDragAvailable(int position) {
        return true;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId, null);
            viewHolder = new ViewHolder();
            viewHolder.itemLayout = view.findViewById(R.id.item_layout);
            viewHolder.contentTextView = view.findViewById(R.id.content_textview);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }

        viewHolder.contentTextView.setText(list.get(position));

        viewHolder.itemLayout.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                vibrator.vibrate(100);

                if (listener != null) {
                    listener.onItemLongClick(position);
                }

                return false;
            }
        });

        return view;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        this.listener = listener;
    }

    public interface OnItemLongClickListener {

        void onItemLongClick(int position);

    }

    class ViewHolder {
        LinearLayout itemLayout;
        TextView contentTextView;
    }
}

最後Activity這樣寫:

    private DragGridView dragGridView;

    private TestGridViewAdapter adapter;

    private ArrayList<String> list;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drag_gridview_test);

        initData();
        initView();
    }

    private void initData() {
        list = new ArrayList<>();

        for (int i = 1; i <= 40; i++) {
            list.add("我是第" + i + "條數據");
        }
    }

    private void initView() {
        dragGridView = findViewById(R.id.drag_gridview);
        
        dragGridView.setOnDragingListener(new DragGridView.OnDragingListener() {
            @Override
            public void onStart(int startPosition) {

            }

            @Override
            public void onDraging(int x, int y, int rawX, int rawY) {

            }

            @Override
            public void onFinish(int finalPosition) {
                dragGridView.lockDrag();
            }
        });

        adapter = new TestGridViewAdapter(this, R.layout.item_test_gridview, list);

        adapter.setOnItemLongClickListener(new TestGridViewAdapter.OnItemLongClickListener() {
            @Override
            public void onItemLongClick(int position) {
                dragGridView.unlockDrag();
            }
        });

        dragGridView.setAdapter(adapter);
    }

用法和DragListView一毛一樣。運行一下就能看到開頭的效果了。

DragGridView同樣可以自定義拖拽View的樣式,同樣通過setDragViewCreator()方法來實現。比如說添加一個高亮效果:

dragGridView.setDragViewCreator(new DragGridView.DragViewCreator() {
    @Override
    public View createDragView(int width, int height, Bitmap viewCache) {
        RelativeLayout layout = new RelativeLayout(DragGridViewTestActivity.this);

        ImageView imageView = new ImageView(DragGridViewTestActivity.this);
        imageView.setImageBitmap(viewCache);
        layout.addView(imageView, new RelativeLayout.LayoutParams(width, height));

        View view = new View(DragGridViewTestActivity.this);
        view.setBackground(getDrawable(R.drawable.edging_red));
        layout.addView(view, new RelativeLayout.LayoutParams(width, height));

        return layout;
    }
});

看看效果:

 

以上就是全部內容了,最後來總結一下。


DragListView和DragGridView分別實現ListView和GridView的item拖拽功能。接收Adapter必須是BaseDragAdapter的繼承類,通過unlockDrag()方法和lockDrag()方法來開啓和關閉拖動。提供OnDragingListener接口來監聽拖動過程,提供DragViewCreator接口來自定義拖拽樣式。

 

最後的最後,附上源碼地址:https://download.csdn.net/download/Sure_Min/12572918

這次的內容就到這裏,我們下次再見。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章