使用自定義 View 繪製一個懸浮式可拖拽按鈕

Rickon發表於2019-03-14

前言

最近公司因為業務要求需要實現一個可以拖拽的懸浮按鈕,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()方法呼叫

相關文章