仿Alipay我的应用页面实现拖拽功能

背景

需求:需要做一个仿支付宝“我的应用页面的功能”,编辑状态下支持跨两栏拖拽。但由于Android中RecyclerView无法支持跨控件拖拽,所以就想到自定义控件来解决问题。

分析

首先,需要将两栏的视图“首页展示”,中间的分割视图,置顶应用放在一个可拖拽的控件中。

该控件需要支持的功能

  • 不满首页数目时,自动添加到首页展示中
  • 当首页数目满时,如果下一个刚好添加到置顶应用中,将先添加分割视图,然后再添加置顶应用
  • 当置顶应用已有数目时,自动添加到置顶应用中
  • 移除首页展示的数据时,后面自动向前补齐,并会将置顶应用中首个自动补齐
  • 移除置顶应用时,后面的自动补齐
  • 移除后,如果数目刚好等于首页数目,将隐藏分割视图
  • 长按触发拖拽
  • 点击触发删除
  • 向前拖拽时,数据自动向后补齐,当前拖拽的数据插入到触发交换数据的位置
  • 向后拖拽时,数据自动向前补齐,当前拖拽的数据插入到触发交换数据的位置

实现

1.前提

  • 拖拽控件的父视图有且只有一个ScrollView
  • 悬浮视图在ScrollView上方
  • 悬浮视图的父控件必须是FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!--悬浮视图的父布局时FrameLayout-->
    <FrameLayout
        android:id="@+id/drag_layout_parent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">

        <!--父视图有且只有一个ScrollView-->
        <ScrollView
            android:id="@+id/scroll_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#DDDDDD">

            <com.hhsjtest.testjump2.impl.CustomFrameLayout
                android:id="@+id/custom_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

        </ScrollView>

        <!--悬浮视图在ScrollView的上方-->
        <com.hhsjtest.testjump2.impl.DefaultDragItemView
            android:id="@+id/float_view_2"
            android:layout_width="270px"
            android:layout_height="250px"
            android:visibility="gone" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/mode_edit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="编辑" />

        <Button
            android:id="@+id/mode_normal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="正常" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/add_one"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="添加1条数据" />

        <Button
            android:id="@+id/add_15"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="添加15条数据" />

        <Button
            android:id="@+id/remove_one"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="移除1条数据" />
    </LinearLayout>

</LinearLayout>

2.触发拖拽的延时任务

当任务被执行时:
1. 标记正在拖拽
2. 展示悬浮窗

3.dispatchTouchEvent

1. ACTION_DOWN,记录按下的坐标,计算按下时的视图位置,记录按下的时间,发送长按触发拖拽的延时任务,请求父视图不要拦截时间
2. ACTION_MOVE,如果时间内超过阈值时,将移除长按拖拽的延时任务,并标记已经取消长按,并请求父视图拦截
3. ACTION_MOVE,小于阈值时,不做任何处理
4. ACTION_UP 还未触发长按,将移除长按事件,并标记长按事件已经取消;
5. ACTION_UP 长按事件没有取消时,将取消长按拖拽的任务,并标记长按事件已经取消;
6. 最终所有的事件走super.dispathTouchEvent() 并返回值

4.onInterceptTouchEvent

1. ACTION_MOVE 滑动超过阈值直接拦截(一般ScrollView会先拦截,如果ScrollView被告诉不拦截,它将能成功拦截) return true
2. ACTION_MOVE 正在拖拽时直接拦截 return true
3. ACTION_UP 正在拖拽视直接拦截(为了保证事件能走进onTouchEvent,在onTouchEvent的UP时候停止拖拽任务),return true
4. ACTION_UP 点击事件的时候不能拦截(点击事件是无法触发长按,也无法把状态标记为正在拖拽,所以不拦截)
5. 其他走super.onInterceptTouchEvent() 并返回值

5.onTouchEvent

1. ACTION_DOWN, return true
2. ACTION_MOVE, 正在拖拽时将根据点位 计算悬浮窗的位置,并更新
3. ACTION_MOVE, (当ScrollView超屏时)滑动触发父视图ScrollView的自动滚动(手动调用ScrollView的scrollBy)
4. ACTION_MOVE,松开滚动或者小于滚动阈,取消ScrollView的自动滚动
5. ACTION_MOVE, 超过滑动阈值时,如果已经取消长按拖拽,将请求父控件拦截,return false,
6. ACTION_MOVE,超过滑动阈值时,如果没有取消拖拽,将执行拖拽(1,展示悬浮窗,2,隐藏当前点位的视图,3,移动悬浮窗),并根据拖拽的点位,时刻计算当前位置,判断是否满足数据交换算法,满足将执行数据交换,并在动画结束的时候,将数据展示在视图上。记录当前点位,将返回true
7. ACTION_UP 如果取消了长按拖拽,将停止拖拽(1,隐藏悬浮窗,2,显示当前拖拽的视图)
8. ACTION_UP 最终走super.onTouchEvent() 并返回值

6.Item的点击

1. 点击item 由于点击操作,时间很短,所以在UP的时候判断没有长按的触发为false,从而取消长按事件触发的任务。
2. 同时UP不应该拦截点击事件,因为点击事件的UP必须由子视图的onTouchEvent处理。

7.抽象

数据

public interface DragItemInfo<D> {

    public D getData();

    /**
     * 拖动状态
     *
     * @return
     */
    public abstract boolean isDrag();

    /**
     * 设置拖动状态
     *
     * @param isDrag
     */
    public abstract void setDrag(boolean isDrag);

    /**
     * 可见状态
     *
     * @return
     */
    public abstract boolean isVisible();

    /**
     * 设置可见状态
     *
     * @param visible
     */
    public abstract void setVisible(boolean visible);

}

Item视图

public abstract class DragItemView<D extends DragItemInfo<D>> extends FrameLayout {

    protected D dragItemInfo;

    private View container;
    private boolean isFloatView;

    public DragItemView(@NonNull Context context) {
        this(context, null);
    }

    public DragItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    /**
     * 必须重载
     */
    protected void initView() {
        container = View.inflate(getContext(), bindLayout(), this);
        createItem(this, dragItemInfo);
    }

    /**
     * 绑定布局id
     *
     * @param
     */
    public abstract int bindLayout();

    /**
     * 创建item视图
     * @param view
     * @param dragItemInfo
     */
    public abstract void createItem(DragItemView<D> view, D dragItemInfo);

    /**
     * 设为可见
     */
    public abstract void visibleItem();

    /**
     * 设为不可见
     */
    public abstract void inVisibleItem();

    /**
     * 删除按钮
     *
     * @return
     */
    public abstract View getDeleteView();

    /**
     * 添加按钮
     *
     * @return
     */
    public abstract View getAddView();

    /**
     * 视图inflate的布局
     * @return
     */
    public View getContainer() {
        return container;
    }

    /**
     * 作为悬浮窗视图
     * @param isFloatView
     */
    public void setAsFloatView(boolean isFloatView) {
        this.isFloatView = isFloatView;
    }

    /**
     * 是否是悬浮窗视图
     * @return
     */
    public boolean isFloatViewView() {
        return isFloatView;
    }

    /**
     * 更新视图数据
     * @param dragItemInfo
     */
    public abstract void updateData(D dragItemInfo);

    /**
     * 设置编辑模式
     * @param edit
     */
    public abstract void setEdit(boolean edit);

}

视图创建工厂


    public interface DragItemFactory<D extends DragItemInfo<D>, V extends DragItemView<D>> {
        V createDragItem(D dragInfo);
    }

重要方法

public abstract class AlipayDragFrameLayout<D extends DragItemInfo<D>, V extends DragItemView<D>> 
    extends FrameLayout {
    
    //编辑模式,才可以拖拽
    public void setEditMode(boolean b);
    
    //debug模式,将展示日志
    public void setDebug(boolean isDebug);

    //添加数据
    public void setDataList(List<D> newDataList);

    //添加一条数据
    public void addData(D info);

    //移除当前视图位置的数据
    public void removeViewData(int viewPosition);

    //移除最后一条数据
    public void removeLastData();

    //设置顶部的行数和列数
    public void setRowColumnNumber(int rowNumber, int columnNumber);
    
    //返回所有的数据
    public List<D> getDataList();

    //需要实现,返回悬浮视图的父控件,FrameLayout
    public abstract View getDragShadowParent();

}

视图拖拽监听

    public interface DragFloatListener<D extends DragItemInfo<D>> {
        /**
         * 展示拖拽悬浮视图
         *
         * @param data     数据
         * @param position 位置
         * @param left     视图的左边有坐标
         * @param top      视图的右边有坐标
         */
        void showFloatView(D data, int position, int left, int top);

        /**
         * 移动拖拽悬浮视图
         *
         * @param left
         * @param top
         */
        void updateFloatView(int left, int top);

        /**
         * 隐藏拖拽悬浮视图
         */
        void hideFloatView();
    }

数据移除监听


    public interface OnDataEditListener<D extends DragItemInfo<D>> {
        void onDeleted(D dragInfo);
    }

仓库地址

https://gitee.com/hhsjdesign/alipaydragview.git
前面忘了开放代码权限,现在可以点击下载了

欢迎交流

QQ 2423458506

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值