作为一个资深篮球爱好者,我经常会用虎扑app看比赛直播,后来注意到文字直播界面右下角加了两个按钮,可以在直播过程中送虎扑币,为自己支持的球队加油,具体的效果如下图所示:
我个人觉得挺好玩的,所以决定自己实现下这个按钮,废话不多说,先看实现的效果吧:
这个效果看起来和popupwindow差不多,但我是采用自定义view的方式来实现,下面说说过程。
首先从虎扑的效果可以看到,它这两个按钮时浮在整个界面之上的,所以它需要和FrameLayout结合使用,因此我让它的宽度跟随屏幕大小,高度根据dpi固定,它的实际尺寸时这样的:
另外这个view初始化出来我们看到可以分为三块,背景圆、圆内文字、圆上方数字,所以正常状态下,只需要在onDraw方法中画出这三块内容即可。先在初始化方法中将自定义的属性和画笔以及初始化数据准备好:
- private void init(Context context, AttributeSet attrs) {
- //获取自定义属性
- TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HoopView);
- mThemeColor = typedArray.getColor(R.styleable.HoopView_theme_color, Color.YELLOW);
- mText = typedArray.getString(R.styleable.HoopView_text);
- mCount = typedArray.getString(R.styleable.HoopView_count);
- mBgPaint = new Paint();
- mBgPaint.setAntiAlias(true);
- mBgPaint.setColor(mThemeColor);
- mBgPaint.setAlpha(190);
- mBgPaint.setStyle(Paint.Style.FILL);
- mPopPaint = new Paint();
- mPopPaint.setAntiAlias(true);
- mPopPaint.setColor(Color.LTGRAY);
- mPopPaint.setAlpha(190);
- mPopPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- mTextPaint = new TextPaint();
- mTextPaint.setAntiAlias(true);
- mTextPaint.setColor(mTextColor);
- mTextPaint.setTextSize(context.getResources().getDimension(R.dimen.hoop_text_size));
- mCountTextPaint = new TextPaint();
- mCountTextPaint.setAntiAlias(true);
- mCountTextPaint.setColor(mThemeColor);
- mCountTextPaint.setTextSize(context.getResources().getDimension(R.dimen.hoop_count_text_size));
- typedArray.recycle();
- mBigRadius = context.getResources().getDimension(R.dimen.hoop_big_circle_radius);
- mSmallRadius = context.getResources().getDimension(R.dimen.hoop_small_circle_radius);
- margin = (int) context.getResources().getDimension(R.dimen.hoop_margin);
- mHeight = (int) context.getResources().getDimension(R.dimen.hoop_view_height);
- countMargin = (int) context.getResources().getDimension(R.dimen.hoop_count_margin);
- mDatas = new String[] {"1", "10", "100"};
- // 计算背景框改变的长度,默认是三个按钮
- mChangeWidth = (int) (2 * mSmallRadius * 3 + 4 * margin);}
在onMeasure中测出view的宽度后,根据宽度计算出背景圆的圆心坐标和一些相关的数据值。
- @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- mWidth = getDefaultSize(widthSize, widthMeasureSpec);
- setMeasuredDimension(mWidth, mHeight);
- // 此时才测出了mWidth值,再计算圆心坐标及相关值
- cx = mWidth - mBigRadius;
- cy = mHeight - mBigRadius;
- // 大圆圆心
- circle = new PointF(cx, cy);
- // 三个按钮的圆心
- circleOne = new PointF(cx - mBigRadius - mSmallRadius - margin, cy);
- circleTwo = new PointF(cx - mBigRadius - 3 * mSmallRadius - 2 * margin, cy);
- circleThree = new PointF(cx - mBigRadius - 5 * mSmallRadius - 3 * margin, cy);
- // 初始的背景框的边界即为大圆的四个边界点
- top = cy - mBigRadius;
- bottom = cy + mBigRadius;
- }
因为这里面涉及到点击按钮展开和收缩的过程,所以我定义了如下几种状态,只有在特定的状态下才能进行某些操作。
- private int mState = STATE_NORMAL;//当前展开收缩的状态
- private boolean mIsRun = false;//是否正在展开或收缩
- //正常状态
- public static final int STATE_NORMAL = 0;
- //按钮展开
- public static final int STATE_EXPAND = 1;
- //按钮收缩
- public static final int STATE_SHRINK = 2;
- //正在展开
- public static final int STATE_EXPANDING = 3;
- //正在收缩
- public static final int STATE_SHRINKING = 4;
接下来就执行onDraw方法了,先看看代码:
- @Override protected void onDraw(Canvas canvas) {
- switch (mState) {
- case STATE_NORMAL:
- drawCircle(canvas);
- break;
- case STATE_SHRINK:
- case STATE_SHRINKING:
- drawBackground(canvas);
- break;
- case STATE_EXPAND:
- case STATE_EXPANDING:
- drawBackground(canvas);
- break;
- }
- drawCircleText(canvas);
- drawCountText(canvas);
- }
圆上方的数字和圆内的文字是整个过程中一直存在的,所以我将这两个操作放在switch之外,正常状态下绘制圆和之前两部分文字,点击展开时绘制背景框展开过程和文字,展开状态下再次点击绘制收缩过程和文字,当然在绘制背景框的方法中也需要不断绘制大圆,大圆也是一直存在的。
上面的绘制方法:
- /**
- * 画背景大圆
- * @param canvas
- */
- private void drawCircle(Canvas canvas) {
- left = cx - mBigRadius;
- right = cx + mBigRadius;
- canvas.drawCircle(cx, cy, mBigRadius, mBgPaint);
- }
- /**
- * 画大圆上面表示金币数的文字
- * @param canvas
- */
- private void drawCountText(Canvas canvas) {
- canvas.translate(0, -countMargin);
- //计算文字的宽度
- float textWidth = mCountTextPaint.measureText(mCount, 0, mCount.length());
- canvas.drawText(mCount, 0, mCount.length(), (2 * mBigRadius - textWidth - 35) / 2, 0.2f, mCountTextPaint);
- }
- /**
- * 画大圆内的文字
- * @param canvas
- */
- private void drawCircleText(Canvas canvas) {
- StaticLayout layout = new StaticLayout(mText, mTextPaint, (int) (mBigRadius * Math.sqrt(2)), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, true);
- canvas.translate(mWidth - mBigRadius * 1.707f, mHeight - mBigRadius * 1.707f);
- layout.draw(canvas);
- canvas.save();
- }
- /**
- * 画背景框展开和收缩
- * @param canvas
- */
- private void drawBackground(Canvas canvas) {
- left = cx - mBigRadius - mChange;
- right = cx + mBigRadius;
- canvas.drawRoundRect(left, top, right, bottom, mBigRadius, mBigRadius, mPopPaint);
- if ((mChange > 0) && (mChange <= 2 * mSmallRadius + margin)) {
- // 绘制***个按钮
- canvas.drawCircle(cx - mChange, cy, mSmallRadius, mBgPaint);
- // 绘制***个按钮内的文字
- canvas.drawText(mDatas[0], cx - (mBigRadius - mSmallRadius) - mChange, cy + 15, mTextPaint);
- } else if ((mChange > 2 * mSmallRadius + margin) && (mChange <= 4 * mSmallRadius + 2 * margin)) {
- // 绘制***个按钮
- canvas.drawCircle(cx - mBigRadius - mSmallRadius - margin, cy, mSmallRadius, mBgPaint);
- // 绘制***个按钮内的文字
- canvas.drawText(mDatas[0], cx - mBigRadius - mSmallRadius - margin - 20, cy + 15, mTextPaint);
- // 绘制第二个按钮
- canvas.drawCircle(cx - mChange, cy, mSmallRadius, mBgPaint);
- // 绘制第二个按钮内的文字
- canvas.drawText(mDatas[1], cx - mChange - 20, cy + 15, mTextPaint);
- } else if ((mChange > 4 * mSmallRadius + 2 * margin) && (mChange <= 6 * mSmallRadius + 3 * margin)) {
- // 绘制***个按钮
- canvas.drawCircle(cx - mBigRadius - mSmallRadius - margin, cy, mSmallRadius, mBgPaint);
- // 绘制***个按钮内的文字
- canvas.drawText(mDatas[0], cx - mBigRadius - mSmallRadius - margin - 16, cy + 15, mTextPaint);
- // 绘制第二个按钮
- canvas.drawCircle(cx - mBigRadius - 3 * mSmallRadius - 2 * margin, cy, mSmallRadius, mBgPaint);
- // 绘制第二个按钮内的文字
- canvas.drawText(mDatas[1], cx - mBigRadius - 3 * mSmallRadius - 2 * margin - 25, cy + 15, mTextPaint);
- // 绘制第三个按钮
- canvas.drawCircle(cx - mChange, cy, mSmallRadius, mBgPaint);
- // 绘制第三个按钮内的文字
- canvas.drawText(mDatas[2], cx - mChange - 34, cy + 15, mTextPaint);
- } else if (mChange > 6 * mSmallRadius + 3 * margin) {
- // 绘制***个按钮
- canvas.drawCircle(cx - mBigRadius - mSmallRadius - margin, cy, mSmallRadius, mBgPaint);
- // 绘制***个按钮内的文字
- canvas.drawText(mDatas[0], cx - mBigRadius - mSmallRadius - margin - 16, cy + 15, mTextPaint);
- // 绘制第二个按钮
- canvas.drawCircle(cx - mBigRadius - 3 * mSmallRadius - 2 * margin, cy, mSmallRadius, mBgPaint);
- // 绘制第二个按钮内的文字
- canvas.drawText(mDatas[1], cx - mBigRadius - 3 * mSmallRadius - 2 * margin - 25, cy + 15, mTextPaint);
- // 绘制第三个按钮
- canvas.drawCircle(cx - mBigRadius - 5 * mSmallRadius - 3 * margin, cy, mSmallRadius, mBgPaint);
- // 绘制第三个按钮内的文字
- canvas.drawText(mDatas[2], cx - mBigRadius - 5 * mSmallRadius - 3 * margin - 34, cy + 15, mTextPaint);
- }
- drawCircle(canvas);
- }
然后是点击事件的处理,只有触摸点在大圆内时才会触发展开或收缩的操作,点击小圆时提供了一个接口给外部调用。
- @Override public boolean onTouchEvent(MotionEvent event) {
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- //如果点击的时候动画在进行,不处理
- if (mIsRun) return true;
- PointF pointF = new PointF(event.getX(), event.getY());
- if (isPointInCircle(pointF, circle, mBigRadius)) { //如果触摸点在大圆内,根据弹出方向弹出或者收缩按钮
- if ((mState == STATE_SHRINK || mState == STATE_NORMAL) && !mIsRun) {
- //展开
- mIsRun = true;//这是必须先设置true,因为onAnimationStart在onAnimationUpdate之后才调用 </li%