前言
最近公司因为业务要求需要实现一个可以拖拽的悬浮按钮,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()方法调用