【自定义View】可拖拽的悬浮按钮

290 阅读2分钟

目录

前言

一、继承系统控件

二、重写方法onTouchEvent()

  • 1.变量
  • 2.具体重写方法代码
  • 3.边界限制(在移动时我们要防止超出边界)
  • 3.不足:该自定义View暂时不能用在最外层父布局为NestedScrollView的布局里(暂时在排查原因中)

总结


前言

随着安卓技术的接触和深入,产品需求也越来越离谱,一个个优美的UI和高大上的功能,仅仅使用系统提供的控件是无法完成的,而自定义View就能够满足产品需求。近日,我便做到了一个需求,需要一个可以拖拽的悬浮按钮,那就拿出我三脚猫功夫的自定义View能力了。


无图无据:

在这里插入图片描述

一、继承系统控件

系统已经有了现成的悬浮按钮:ExtendedFloatingActionButtonFloatingActionButton,那么我们便图简单直接继承这两个控件就行。 因为我要做的是一个带文字的悬浮按钮,所以我选择继承ExtendedFloatingActionButton。如下:

public class DraggableFab extends ExtendedFloatingActionButton {}

二、重写方法onTouchEvent()

1.变量

    private int lastAction =  MotionEvent.ACTION_DOWN; // 上次的点击类型
    private float startX; // 开始的位置X
    private float startY; // 开始的位置Y
    private float lastX; // 最开始的位置X
    private float lastY; // 最开始的位置Y
    private int parentWidth;// 父布局的宽
    private int parentHeight; // 父布局的高

2.具体重写方法代码

  1. 首先在点击按钮时候记录当前初始位置
  2. 在移动按钮时重新设置当前View的位置
  3. 在松开按钮时判断是否移动,决定是否触发点击事件 代码如下(示例):
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getActionMasked();
        // 可以点击
        setPressed(true);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                getParentDimensions();
                lastAction = MotionEvent.ACTION_DOWN;
                startX = getX() - event.getRawX();
                startY = getY() - event.getRawY();
                lastX = getX();
                lastY = getY();
                break;

            case MotionEvent.ACTION_MOVE:
                lastAction = MotionEvent.ACTION_MOVE;
                setX(event.getRawX() + startX);// 设置位置
                setY(event.getRawY() + startY);
                checkBoundaries();
                // 返回true消费该事件
                break;

            case MotionEvent.ACTION_UP:
                // 没有移动
                if (lastAction == MotionEvent.ACTION_DOWN) {
                    // 移动像素小于10
                } else if (Math.abs(getX() - lastX) < 10 && Math.abs(getY() - lastY) < 10) {
                }else {
                    // 变成点击
                    lastAction = MotionEvent.ACTION_DOWN;
                    // 设置不可点击
                    setPressed(false);
                }
                break;
            default:
                return false;
        }
        // 保留原有的水波纹效果
        return super.onTouchEvent(event);

    }

3.边界限制(在移动时我们要防止超出边界)

  1. 这里我们就要先获取父布局的宽高
  2. 在根据当前View的位置来判断是否越界
  3. 越界者则重新设置View在边界,不越界则不做处理
    /**
     * 获取父布局宽高
     */
    private void getParentDimensions() {
        if (getParent() != null && getParent() instanceof ViewGroup) {
            ViewGroup parent = (ViewGroup) getParent();
            parentWidth = parent.getWidth();
            parentHeight = parent.getHeight();
        }
    }

    /**
     * 边界限制
     */
    private void checkBoundaries() {
        // 左边
        if (getX() < 0) {
            setX(0);
        } 
        if (getY() < 0) {
            setY(0);
        }
        // 这里可以根据所需隐藏部分View在屏幕外,我这里使用了工具类,将17转化为17sp,有兴趣的同学可以在评论区留言要,当然如果不需要隐藏View,只需要改为注释代码即可
        if (getX() > parentWidth - getWidth()+ DensityUtil.dip2px(getContext(),17)) {
            setX(parentWidth - getWidth()+ DensityUtil.dip2px(getContext(),17));
        }
        if (getY() > parentHeight - getHeight()) {
            setY(parentHeight - getHeight());
        }
//        if (getX() > parentWidth - getWidth()) {
//            setX(parentWidth - getWidth());
//        }
//        if (getY() > parentHeight - getHeight()) {
//            setY(parentHeight - getHeight());
//        }
    }

3.不足:该自定义View暂时不能用在最外层父布局为NestedScrollView的布局里(暂时在排查原因中)

总结

虽然这是一个小小功能,但足见自定义View技术在安卓里的重要性,我会在完善这个自定义View的基础下接着深入学习相关知识,希望大家一起加油。