一个实现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

这次的内容就到这里,我们下次再见。
 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值