使用自定义 View 绘制一个悬浮式可拖拽按钮

1,698 阅读2分钟

前言

最近公司因为业务要求需要实现一个可以拖拽的悬浮按钮,Android 官方提供了 FloatingActionButton 但是并不支持定制。于是我打算采用自定义 View 的方法来实现。Android 官方文档告诉我们,使用自定义控件需要以下的步骤。(根据你的需要,某些步骤可以省略)

  • 创建 View
  • 处理 View 的布局
  • 绘制 View
  • 与用户进行交互
  • 优化已定义的 View

下面我分别对每一步进行介绍。

创建 View(继承 View)

在第三步 onDraw 方法中开始绘制之前,你应该让画笔 Paint 对象的信息初始化完毕。这是因为 View 的重新绘制是比较频繁的,这就可能多次调用 onDraw,所以初始化的代码不应该放在 onDraw 方法里。

public class FloatDragView extends View {//继承 View
public FloatDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        
        //初始化画笔
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setColor(Color.parseColor("#000000"));
        mTextPaint.setTextSize(context.getResources().getDimensionPixelSize(R.dimen.medium_text_size));
        mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_round);
    }
}

处理 View 的布局

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mRadius * 2, mRadius * 2);
    }

绘制 View

一旦自定义控件被创建并且测量代码写好之后,接下来你就可以实现 onDraw()来绘制 View 了,onDraw 方法包含了一个 Canvas 叫做画布的参数,onDraw()简单来说就两点:1、Canvas 决定要去画什么;2、Paint 决定怎么画。比如,Canvas 提供了画线方法,Paint 就来决定线的颜色。Canvas 提供了画矩形,Paint 又可以决定让矩形是空心还是实心。代码如下:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(bitmap, 0, 0, mBitmapPaint);
        float textWidth = mTextPaint.measureText(mText, 0, mText.length());
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        canvas.drawText(mText, 0, mText.length(), mRadius - textWidth / 2, mRadius +
                -(fontMetrics.ascent + fontMetrics.descent) / 2, mTextPaint);
    }

与用户交互

本文要实现的是一个可拖拽可点击的按钮。拖拽事件代码如下:

public boolean onTouchEvent(MotionEvent event) {
        float x = event.getRawX();
        float y = event.getRawY() - getStatusBarHeight(getContext());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mTouchX = event.getX();
                mTouchY = event.getY();
                mStartX = x;
                mStartY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (mOnScrollListener != null) {
                    mOnScrollListener.onScroll((int) (x - mTouchX), (int) (y - mTouchY));
                }
                break;
            case MotionEvent.ACTION_UP:
                mTouchX = mTouchY = 0;
                if (Math.abs(x - mStartX) < 5 && Math.abs(y - mStartY) < 5) {
                    if (mOnClickListener != null) {
                        mOnClickListener.onClick();
                    }
                }
                break;
        }
        return true;
    }
    
    public void setOnScrollListener(OnScrollListener onScrollListener) {
        mOnScrollListener = onScrollListener;
    }

    public interface OnScrollListener {
        void onScroll(int x, int y);
    }

    public void setOnClickListener(OnClickListener onClickListener) {
        mOnClickListener = onClickListener;
    }

    public interface OnClickListener {
        void onClick();
    }
    
    /**
     * 滑动监听,动态改变按钮和列表的位置
     */
    @Override
    public void onScroll(int x, int y) {
        mFdvParams.x = x;
        mFdvParams.y = y;
        mWindowManager.updateViewLayout(mFloatDragView, mFdvParams);
        if (mIsSpinnerShow) {
            mRvParams.x = mFdvParams.x;
            mRvParams.y = mFdvParams.y + mFloatDragView.getHeight();
            mWindowManager.updateViewLayout(mSpinnerRv, mRvParams);
        }
    }
    
    //点击事件忽略

那么我们需要将自定义 View 显示在屏幕上(注意:显示悬浮按钮还需要申请权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />):

public void showFloatDragView() {
        mContext = BaseApplication.getInstance();
        mWindowManager = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
        mFloatDragView = new FloatDragView(mContext);
        mFloatDragView.setOnClickListener(this);
        mFloatDragView.setOnScrollListener(this);
        mFloatDragView.setText(mUrlArr[0]);
        mFdvParams = new WindowManager.LayoutParams();
        mFdvParams.type = WindowManager.LayoutParams.TYPE_PHONE;//级别
        mFdvParams.format = PixelFormat.TRANSPARENT;//背景透明
        mFdvParams.gravity = Gravity.LEFT | Gravity.TOP;//位置
        mFdvParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        mFdvParams.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高
        mFdvParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowManager.addView(mFloatDragView, mFdvParams);
    }

这样我们的悬浮按钮就显示在屏幕上了。

优化自定义的 View

  • 去除无用代码
  • 在 onDraw()方法中不应该有会导致垃圾回收的代码
  • 尽可能少让 onDraw()方法调用