当前位置: 首页 > 科技观察

自定义视图-模仿虎扑直播游戏界面的奖励按钮

时间:2023-03-22 01:07:03 科技观察

作为一名资深篮球迷,我经常使用虎扑APP观看比赛直播。直播时送虎扑币,为你支持的队伍加油。具体效果如下图所示:个人觉得很好玩,所以决定自己实现这个按钮。废话不多说,先看看效果Bar:这个效果看起来和popupwindow差不多,不过我是用自定义view来实现的。下面说说过程吧。首先从Hupu的效果可以看出,这两个按钮是悬浮在整个界面上的,所以需要结合FrameLayout来使用,所以我让它的宽度跟随屏幕大小,高度根据到dpi。它的实际大小是这样的:另外,在视图初始化的时候,我们可以看到可以分为三部分,背景圆圈,圆圈内的文字,圆圈上方的数字,所以正常情况下,你只需要在onDraw方法中绘制这三个部分。首先在初始化方法中准备自定义属性、画笔和初始化数据:privatevoidinit(Contextcontext,AttributeSetattrs){//获取自定义属性TypedArraytypedArray=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=newPaint();mBgPaint.setAntiAlias(真);mBgPaint.setColor(mThemeColor);mBgPaint.setAlpha(190);mBgPaint.setStyle(Paint.Style.FILL);mPopPaint=newPaint();mPopPaint.setAntiAlias(true);mPopPaint.setColor(Color.LTGRAY);mPopPaint.setAlpha(190);mPopPaint.setStyle(Paint.Style.FILL_AND_STROKE);mTextPaint=newTextPaint();mTextPaint.setAntiAlias(true);mTextPaint.setColor(mTextColor);mTextPaint.setTextSize(context.getResources().getDimension(R).dimen.hoop_text_size));mCountTextPaint=newTextPaint();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=newString[]{"1","10","100"};//计算背景框变化的长度,默认为三buttonsmChangeWidth=(int)(2*mSmallRadius*3+4*margin);}在onMeasure中测量view的宽度后,根据宽度计算背景圆的圆心坐标和一些相关的数据值@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){intwidthSize=MeasureSpec.getSize(widthMeasureSpec);mWidth=getDefaultSize(widthSize,widthMeasureSpec);setMeasuredDimension(mWidth,mHeight);值cx=mWidth-mBigRadius;cy=mHeight-mBigRadius;//大圆心circle=newPointF(cx,cy);//三个按钮的圆心circleOne=newPointF(cx-mBigRadius-mSmallRadius-margin,cy);circleTwo=newPointF(cx-mBigRadius-3*mSmallRadius-2*margin,cy);circleThree=newPointF(cx-mBigRadius-5*mSmallRadius-3*margin,cy);//初始背景框的边界为四个大圆边界点top=cy-mBigRadius;bottom=cy+mBigRadius;}因为这个涉及到点击按钮展开和收缩的过程,我定义了如下几种状态,某些操作只能在特定的状态下进行。privateintmState=STATE_NORMAL;//当前展开和收缩的状态privatebooleanmIsRun=false;//是否展开或收缩//正常状态publicstaticfinalintSTATE_NORMAL=0;//按钮展开publicstaticfinalintSTATE_EXPAND=1;//按钮收缩publicstaticfinalintSTATE_SHRINK=2;//正在扩张publicstaticfinalintSTATE_EXPANDING=3;//正在收缩publicstaticfinalintSTATE_SHRINKING=4;接下来执行onDraw方法,先看代码:@OverrideprotectedvoidonDraw(Canvascanvas){switch(mState){caseSTATE_NORMAL:drawCircle(canvas);break;caseSTATE_SHRINK:caseSTATEBackback:dSHRINgrINgr(canvas);break;caseSTATE_EXPAND:caseSTATE_EXPANDING:drawBackground(canvas);break;}drawCircleText(canvas);drawCountText(canvas);}圆上面的数字和圆里面的文字在整个过程中一直存在,所以我将这两个操作放在switch之外。在正常状态下,绘制圆圈和前面两部分文字。点击展开时,绘制背景框展开过程和文字。在展开状态下,再次点击绘制收缩过程和文字。当然,在绘制背景框的方法中,还需要不断地绘制大圆,大圆始终存在。上面的绘制方法:/***画背景大圆*@paramcanvas*/privatevoiddrawCircle(Canvascanvas){left=cx-mBigRadius;right=cx+mBigRadius;canvas.drawCircle(cx,cy,mBigRadius,mBgPaint);}/***在大圆上画文字代表金币数量*@paramcanvas*/privatevoiddrawCountText(Canvascanvas){canvas.translate(0,-countMargin);//计算文字的宽度floattextWidth=mCountTextPaint.measureText(mCount,0,mCount.length());canvas.drawText(mCount,0,mCount.length(),(2*mBigRadius-textWidth-35)/2,0.2f,mCountTextPaint);}/***在大圆圈内绘制文字*@paramcanvas*/privatevoiddrawCircleText(Canvascanvas){StaticLayoutlayout=newStaticLayout(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();}/***绘制背景框伸缩*@paramcanvas*/privatevoiddrawBackground(Canvascanvas){left=cx-mBigRadius-mChange;right=cx+mBigRadius;canvas.drawRoundRect(左、上、右、下、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);}elseif((mChange>2*mSmallRadius+margin)&&(mChange<=4*mSmallRadius+2*margin)){//绘制***一个按钮画布。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);}elseif((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);//绘制s第二个按钮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);}elseif(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);//绘制第三个按钮画布。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);}然后是click事件的处理,只有触摸点在大圆圈内才会触发展开或收缩操作,并提供接口小圆圈点击外呼@OverridepublicbooleanonTouchEvent(MotionEventevent){intaction=event.getAction();switch(action){caseMotionEvent.ACTION_DOWN://如果点击时动画正在进行,不处理if(mIsRun)returntrue;PointFpointF=newPointF(event.getX(),event.getY());if(isPointInCircle(pointF,circle,mBigRadius)){//如果触摸点在大圆圈内,则按弹出方向弹出或缩小按钮if((mState==STATE_SHRINK||mState==STATE_NORMAL)&&!mIsRun){//ExpandmIsRun=true;//这里必须先设置为true,因为onAnimationStart在onAnimationUpdate之后调用showPopMenu();}else{//ContractmIsRun=true;hidePopMenu();}}else{//触摸点不在大圆内if(mState==STATE_EXPAND){//如果处于展开状态if(isPointInCircle(pointF,circleOne,mSmallRadius)){listener.clickButton(this,Integer.parseInt(mDatas[0]));}elseif(isPointInCircle(pointF,circleTwo,mSmallRadius)){listener.clickButton(this,Integer.parseInt(mDatas[1]));}elseif(isPointInCircle(pointF,circleThree,mSmallRadius)){listener.clickButton(this,Integer.parseInt(mDatas[2]));}mIsRun=true;hidePopMenu();}}break;}returnsuper.onTouchEvent(event);}展开和收缩的动画是改变背景框宽度属性的动画,并听这个属性动画,改变宽度值在更改期间重绘整个视图因为我一开始就确定了大圆和小圆的半径以及小圆和背景框的距离,所以背景框的宽度在初始化的时候已经计算好了:mChangeWidth=(int)(2*mSmallRadius*3+4*margin);/***弹出背景框*/privatevoidshowPopMenu(){if(mState==STATE_SHRINK||mState==STATE_NORMAL){ValueAnimatoranimator=ValueAnimator.ofInt(0,mChangeWidth);animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){@OverridepublicvoidonAnimationUpdate(ValueAnimatoranimation){if(mIsRun){mChange=(int)animation.getAnimatedValue();invalidate();}else{animation.cancel();mState=STATE_NORMAL;}}});animator.addListener(newAnimatorListenerAdapter((){super.onAnimationEnd(animation);mIsRun=false;//设置动画结束后状态展开mState=STATE_EXPAND;}});animator.setDuration(500);animator.start();}}/***隐藏弹出框*/privatevoidhidePopMenu(){if(mState==STATE_EXPAND){ValueAnimatoranimator=ValueAnimator.ofInt(mChangeWidth,0);animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){@OverridepublicvoidonAnimationUpdate(ValueAnimatoranimation){if(mIsRun){mChange=(int)animation.getAnimatedValue();invalidate();}else{animation.cancel();}}});animator.addListener(newAnimatorListenerAdapter(){@OverridepublicvoidonAnimationStart(Animatoranimation){super.onAnimationStart(动画);mIsRun=true;mState=STATE_SHRINKING;}@OverridepublicvoidonAnimationCancel(Animatoranimation){super.onAnimationCancel(动画);mIsRun=false;mState=STATE_EXPAND;}@OverridepublicvoidonAnimationEnd(Animatoranimation){super;d(Enimatoranimation))mIsRun=false;//设置状态在动画结束后收缩mState=STATE_SHRINK;}});animator.setDuration(500);animator.start();}}这个过程好像是弹出或者收缩,但是实际上宽度值是每一个如果你改变一点,所有的组件ts会重绘一次,但是文字和大圆圈的大小和位置不会改变,只会改变背景框的宽度值,所以才有这个效果在xml中的使用:activity中使用:hoopview1=(HoopView)findViewById(R.id.hoopview1);hoopview1.setOnClickButtonListener(newHoopView.OnClickButtonListener(){@OverridepublicvoidclickButton(Viewview,intnum){Toast.makeText(MainActivity.this,"hoopview1added"+num,Toast.LENGTH_SHORT).show();}});大致的实现过程是这样的,和原来的效果还是有点差距的。我还有很多瑕疵,比如文字居中,弹出或收缩时小圆圈内文字的旋转动画。