高仿QQ运动的周报界面
这次高仿的是QQ运动的周报界面的网图。这个控件刚开始的时候以为代码量不大,没想到一路下来界面代码在加上动画代码还是蛮多的。好了老规矩先上图:
效果还是和qq的才不多吧。
1. 首先我把各个变量都贴出来以便在后续中你们可以更好理解代码的意思:
//屏幕的宽度private int mScreemWidth;//屏幕的高度private int mScreemHight;//圆的线private Paint mCirclePaint;//圆区域的颜色private Paint mCirclePaintColor;//虚线private Paint mLineCircle;//圆点private Paint mCircleHoldPaint;//画字体private Paint mCenterCircle;//最外的圆的透明度private int mCircleAlpha1=0;//中间的圆的透明度private int mCircleAlpha2=0;//最内的圆的透明度private int mCircleAlpha3=0;//好友排名private int mFriendDranking=0;//达标天数private int mStandardDay=0;//平均步数private int mAverageCount=0;//好友排名的X轴坐标private float mFriendDrankingX=0;//好友排名的Y轴坐标private float mFriendDrankingY=0;//平均步数的X轴坐标private float mStandardDayX=0;//平均步数的Y轴坐标private float mStandardDayY=0;//达标天数的X轴坐标private float mAverageCountX=0;//达标天数的Y轴坐标private float mAverageCountY=0;//临时的View的半径private int tempCircleRadius=0;//View的半径private int circleRadius=0;//每个圆圈的间隔private float marginCircleSize=0;//圆的颜色private int circleColor=0;//朋友区域的颜色private int friendColor;//平均步数区域的颜色private int averageColor;//达标天数区域的颜色private int standardColor;//总步数private String allStep;//好友排名private String firendDrank;//达标天数private String standarDay;//平均步数private String averageCount;//波浪动画的数值private int waveData=-30;//中间文字翻转动画的数值private float centerData=0;//画波浪的看门狗private boolean waveWatchDag=false;//画虚线的看门狗private boolean lineWatchDag=false;//各点解释的看门狗private boolean expainWatchDag=false;//中心圆的内容的看门狗private boolean centerWatchDag=false;//解释的字符串private String averageCountTxt="平均步数";private String friendDrankTxt="好友排名";private String standarDayTxt="达标天数";private String theyCount="本周总步数";private String tip="步";
2.有点多了,其次就是测量View的大小的onMeasure():
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthModel=MeasureSpec.getMode(widthMeasureSpec);int heightModel=MeasureSpec.getMode(heightMeasureSpec);int measureWidth=MeasureSpec.getSize(widthMeasureSpec);int measureHeight=MeasureSpec.getSize(heightMeasureSpec);int width;int height;if(widthModel==MeasureSpec.EXACTLY){width=measureWidth;}else{width=getPaddingLeft()+getPaddingRight()+measureWidth;}if(heightModel==MeasureSpec.EXACTLY){height=measureHeight;}else{height=(getPaddingLeft()+getPaddingRight()+measureHeight)/2;}setMeasuredDimension(width,height);loadAnimator();}
3.这里当设置大小为wrap_content的时候,View的宽度的话是用屏幕的的宽,而View的高的话是屏幕的高度的一半。当View的大小生成之后会调用onSizeChange()方法,具体操作如下:
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mScreemWidth=w;mScreemHight=h;//得出最大的圆的半径if(mScreemWidth>mScreemHight){circleRadius=Float.valueOf((w/3.4)+"").intValue();}else{circleRadius=Float.valueOf((h/3.4)+"").intValue();}if(tempCircleRadius!=0&&tempCircleRadius<=circleRadius){circleRadius=tempCircleRadius;}//得出每个圆的间隔marginCircleSize=circleRadius/6;}
当View的宽度大于View的高度时,最外边的圆的半径就是w/3.4,反之当View的高度大于View的宽度时,最外边的圆的半径就是h/3.4,而每个圆的间隔就是圆半径的六分之一。
4.接着就是最重要的onDraw()方法了,代码如下:
@Overrideprotected void onDraw(Canvas canvas) {canvas.translate(getWidth()/2,getHeight()/2-(circleRadius/6));canvas.save();//画出三条圆圈drawCircle(canvas);//画出波浪图形drawWaves(canvas);//画虚线drawDottedLine(canvas);//画点drawCircleHold(canvas);//画解释的内容drawExpain(canvas);//画中心圆的内容centerCircleContent(canvas);}
首先把canvas的原点移到X轴为宽度的一半,Y轴为高度的一半再减去六分之一的半径,保存canvas的状态。接着就一个个说这里面的每一个方法:
//画出三条圆圈public void drawCircle(Canvas canvas){//画出最大的圆mCirclePaint.setAlpha(mCircleAlpha1);canvas.drawCircle(0,0,circleRadius,mCirclePaint);//画出第二大的圆mCirclePaint.setAlpha(mCircleAlpha2);canvas.drawCircle(0,0,circleRadius-marginCircleSize,mCirclePaint);mCirclePaintColor.setColor(Color.parseColor("#F1FCFE"));mCirclePaintColor.setAlpha(mCircleAlpha2);canvas.drawCircle(0,0,circleRadius-marginCircleSize-2,mCirclePaintColor);//画出第三大的圆mCirclePaint.setAlpha(mCircleAlpha3);canvas.drawCircle(0,0,circleRadius-marginCircleSize*2,mCirclePaint);mCirclePaintColor.setColor(Color.parseColor("#E7F9FE"));mCirclePaintColor.setAlpha(mCircleAlpha3);canvas.drawCircle(0,0,circleRadius-marginCircleSize*2-2,mCirclePaintColor);}
这个方法是比较简单的,就是画出三个圆圈,每个圆圈的间隔就是前面所初始化的marginCircleSize,圆圈的圆心就是canvas的原点,之前我们移动过原点了。第二和第三个圆圈里面还配有圆的背景,效果如下图:
接着就是画出波浪图形的方法 drawWaves(canvas)代码如下:
//画出波浪图形public void drawWaves(Canvas canvas){if(!waveWatchDag){return ;}canvas.rotate(waveData);float inCircleRadius=circleRadius-marginCircleSize*3;//算出最上面的点float topPointX=0;float topPointY=-inCircleRadius;//算出左下角的点float leftBottpmPointX=-(float)Math.sqrt(Math.pow(inCircleRadius,2)-Math.pow(inCircleRadius/2,2));float leftBottomPointY=inCircleRadius/2;//算出右小角的点float rightBottomPointX=-leftBottpmPointX;float rightBottomPointY=inCircleRadius/2;//得到好友排名半径float mFriendDrankingData=circleValue(mFriendDranking);//得到达标天数半径float mStandarDayData=circleValue(mStandardDay);//得到平均步数半径float mAverageCountData=circleValue(mAverageCount);/*画好友排名*///得出左上角的圆的坐标float[] mFriendDrankingPoint=calculatePoint(mFriendDrankingData);//好友排名的X轴坐标mFriendDrankingX=-mFriendDrankingPoint[0];//好友排名的Y轴坐标mFriendDrankingY=-mFriendDrankingPoint[1];//画出还有排名的波浪线Path mFriendDrankingPath=new Path();mFriendDrankingPath.moveTo(leftBottpmPointX,leftBottomPointY);mFriendDrankingPath.lineTo(mFriendDrankingX-6,mFriendDrankingY-6);mFriendDrankingPath.lineTo(topPointX,topPointY);mFriendDrankingPath.lineTo(topPointX+10,topPointY+10);mCirclePaintColor.setPathEffect(new CornerPathEffect(20));mCirclePaintColor.setColor(friendColor);canvas.drawPath(mFriendDrankingPath,mCirclePaintColor);/*画达标天数*///得出右上角的圆的坐标float[] mStandarDayPoint=calculatePoint(mStandarDayData);//达标天数的X轴坐标mStandardDayX=mStandarDayPoint[0];//达标天数的Y轴坐标mStandardDayY=-mStandarDayPoint[1];//画出还有达标天数的波浪线Path mStandarDayPath=new Path();mStandarDayPath.moveTo(topPointX,topPointY);mStandarDayPath.lineTo(mStandardDayX+6,mStandardDayY-6);mStandarDayPath.lineTo(rightBottomPointX,rightBottomPointY);mStandarDayPath.lineTo(rightBottomPointX-10,rightBottomPointY+10);mCirclePaintColor.setColor(standardColor);canvas.drawPath(mStandarDayPath,mCirclePaintColor);/*平均步数*///平均步数的X轴坐标mAverageCountX=0;//平均步数的Y轴坐标mAverageCountY=mAverageCountData;//画出还有平均步数的波浪线Path mAverageCountPath=new Path();mAverageCountPath.moveTo(rightBottomPointX,rightBottomPointY);mAverageCountPath.lineTo(topPointX,mAverageCountData+8);mAverageCountPath.lineTo(leftBottpmPointX,leftBottomPointY);mAverageCountPath.lineTo(leftBottpmPointX+10,leftBottomPointY+10);mCirclePaintColor.setColor(averageColor);canvas.drawPath(mAverageCountPath,mCirclePaintColor);//最里面的圆mCirclePaintColor.setColor(Color.WHITE);canvas.drawCircle(0,0,circleRadius-marginCircleSize*3,mCirclePaintColor);}
这方法里最核心的就是数学计算了,整个View有3个波浪区域,各占一个圆的三分之一,所以第一步就是计算出这个圆的左下角,右小角和正上角的三个点,如图的蓝色点所示。具体代码见注释。在通过circleValue算出波浪线的半径:
//算出弧线区域的半径public float circleValue(int mDataDranking){if(mDataDranking==1){return circleRadius-marginCircleSize*2;}else if(mDataDranking==2){return circleRadius-marginCircleSize;}else if(mDataDranking==3){return circleRadius;}else{return circleRadius-marginCircleSize*2;}}
然后通过calculatePoint()方法来各个波浪区域对应的顶点,代码如下:
//算出右上角或左上角的坐标public float[] calculatePoint(float radius){float[] result=new float[2];float pointY=radius/2;float pointX=(float)Math.sqrt(Math.pow(radius,2)-Math.pow(pointY,2));result[0]=pointX;result[1]=pointY;return result;}
最后转化为形象的图就是:接着用Path把各个区域的点连起来就是形成区域,不过现在还是尖角,要把它变成原角就要用mCirclePaintColor.setPathEffect(new CornerPathEffect(20));方法,这样各个边的连接处都可以转换成圆角,可是因为是圆角所以到不到圆圈的边,这时候你要对你的顶点进行微调,所以我再顶点都进行了减6或者加6的操作。至于我这个6是怎么得出来的,我用的等比例的数学方法来求出来的,到时有优化我可以把我的方法用代码表示出来。至此,重要的就说完了,剩下的只是用canvas和path和paint画出来就是了。效果如下:
接着就是画虚线的方法了drawDottedLine(canvas)代码如下:
//画圆点和虚线public void drawDottedLine(Canvas canvas){if(!lineWatchDag){return;}for(int i=0;i<3;i++){canvas.rotate(120);if(i==0){//画好友排名的虚线mLineCircle.setTextSize(18);mLineCircle.setColor(friendColor);drawDottedLine(canvas,judgeDotte(mFriendDranking));}else if(i==1){//画达标天数的虚线mLineCircle.setColor(standardColor);drawDottedLine(canvas,judgeDotte(mStandardDay));}else if(i==2){//画平均步数的虚线mLineCircle.setColor(averageColor);drawDottedLine(canvas,judgeDotte(mAverageCount));}}canvas.restore();}//判断虚线public List judgeDotte(int value){List temp=new ArrayList<>();if(value==1){//当为1时,波浪顶点到第三个圆 temp.add(circleRadius-marginCircleSize*2);temp.add((float)circleRadius);temp.add(circleRadius-marginCircleSize*3);}else if(value==2){//当为2时,波浪顶点到第二个圆temp.add(circleRadius-marginCircleSize);temp.add((float)circleRadius);temp.add(circleRadius-marginCircleSize*3);}else if(value==3){//当为3时,波浪顶点到第一个圆temp.add(circleRadius-marginCircleSize*3);temp.add((float)circleRadius);}return temp;}//画虚线public void drawDottedLine(Canvas canvas,List data){if(data.size()==2){/*当数值是最大的是时候也就是3*/mLineCircle.setColor(Color.WHITE);Path path=new Path();path.moveTo(0,data.get(0));path.lineTo(0,data.get(1));canvas.drawPath(path,mLineCircle);return ;}else{/*当数值在1和2的时候*///画出数值外的虚线Path pathOut=new Path();pathOut.moveTo(0,data.get(0));pathOut.lineTo(0,data.get(1));mLineCircle.setPathEffect(new DashPathEffect(new float[]{7,5,7,5},5));canvas.drawPath(pathOut,mLineCircle);//画出数值内的虚线Path pathIn=new Path();pathIn.moveTo(0,data.get(1));pathIn.lineTo(0,data.get(2));mLineCircle.setColor(Color.WHITE);canvas.drawPath(pathIn,mLineCircle);}}
首先canvas通过每次旋转120度来画出每一条波浪线,通过judgeDotte()方法得出
波浪线三个点对应的Y轴的坐标,假如judgeDotte返回的个数是两个的话那就是证明顶点在最外面的圆,假如是3个的话就画出顶点之外和顶点之内的线就可以了,代码注释已经很详细了,效果图如下:
。
接着是画虚线上的圆点,drawCircleHold(Canvas canvas)代码如下:
//画虚线上的圆点public void drawCircleHold(Canvas canvas){if(!lineWatchDag){return;}float[] yuan1=calculatePoint(circleRadius-marginCircleSize*2);float[] yuan2=calculatePoint(circleRadius-marginCircleSize);float[] yuan3=calculatePoint(circleRadius);//画好友排名的圆点drawCircleHoldImpl(-yuan1[0],-yuan1[1],-yuan2[0],-yuan2[1],-yuan3[0],-yuan3[1],mFriendDranking,canvas,friendColor);//画达标天数的圆点drawCircleHoldImpl(yuan1[0],-yuan1[1],yuan2[0],-yuan2[1],yuan3[0],-yuan3[1],mStandardDay,canvas,standardColor);//画平均步数的圆点drawCircleHoldImpl(0,circleRadius-marginCircleSize*2,0,circleRadius-marginCircleSize,0,circleRadius,mAverageCount,canvas,averageColor);expainWatchDag=true;}//画圆的具体的方法public void drawCircleHoldImpl(float mCirlce1X,float mCircle1Y,float mCirlce2X,float mCircle2Y,float mCirlce3X,float mCircle3Y,int action,Canvas canvas,int color){mCircleHoldPaint.setColor(color);if(action==1){//当数值为3时画所有圆圈canvas.drawCircle(mCirlce1X,mCircle1Y,8,mCircleHoldPaint);canvas.drawCircle(mCirlce2X,mCircle2Y,8,mCircleHoldPaint);}else if(action==2){//当数值为2时画中间的圆圈canvas.drawCircle(mCirlce2X,mCircle2Y,8,mCircleHoldPaint);}//画一定要画的圆圈和圆点canvas.drawCircle(mCirlce3X,mCircle3Y,8,mCircleHoldPaint);mCircleHoldPaint.setColor(Color.WHITE);canvas.drawCircle(mCirlce1X,mCircle1Y,6,mCircleHoldPaint);canvas.drawCircle(mCirlce2X,mCircle2Y,6,mCircleHoldPaint);canvas.drawCircle(mCirlce3X,mCircle3Y,6,mCircleHoldPaint);}
这里同样注释也是很详细的,整个思路就是通过calculatePoint()算出三个圆点的坐标,在通过传进去的数值来要画多少个圆圈,而原点是不管数值多少都要画的。效果图如下:
接着就是画解释的内容drawExpain(Canvas canvas)代码如下:
//画解释的内容public void drawExpain(Canvas canvas){if(!expainWatchDag){return ;}//间隔int margin=circleRadius/5;//画平均步数和对应的数值Rect txtRect=new Rect();mCenterCircle.setColor(Color.BLACK);mCenterCircle.setTextSize(circleRadius/6);mCenterCircle.setTypeface(Typeface.SANS_SERIF);canvas.drawText(averageCount,0,circleRadius+margin,mCenterCircle);mCenterCircle.setColor(friendColor);mCenterCircle.setTextSize(circleRadius/10);mCenterCircle.getTextBounds(averageCountTxt,0,averageCountTxt.length(),txtRect);canvas.drawText(averageCountTxt,0,circleRadius+margin+(txtRect.bottom-txtRect.top),mCenterCircle);//画好友排名和对应的数值mCenterCircle.setColor(Color.BLACK);mCenterCircle.setTextSize(circleRadius/6);canvas.drawText(firendDrank,-circleRadius,-(circleRadius-marginCircleSize),mCenterCircle);mCenterCircle.setColor(friendColor);mCenterCircle.setTextSize(circleRadius/10);mCenterCircle.getTextBounds(friendDrankTxt,0,friendDrankTxt.length(),txtRect);canvas.drawText(friendDrankTxt,-circleRadius,-(circleRadius-marginCircleSize)+(txtRect.bottom-txtRect.top),mCenterCircle);//画达标天数和对应的数值mCenterCircle.setColor(Color.BLACK);mCenterCircle.setTextSize(circleRadius/6);canvas.drawText(standarDay,circleRadius,-(circleRadius-marginCircleSize),mCenterCircle);mCenterCircle.setColor(friendColor);mCenterCircle.setTextSize(circleRadius/10);mCenterCircle.getTextBounds(friendDrankTxt,0,friendDrankTxt.length(),txtRect);canvas.drawText(standarDayTxt,circleRadius,-(circleRadius-marginCircleSize)+(txtRect.bottom-txtRect.top),mCenterCircle);centerWatchDag=true;}
看起来代码有点多,其实是最简单的,就是确定好友排名的坐标(-circleRadius,-(circleRadius-marginCircleSize)),int margin=circleRadius/5,平均步数的坐标(0,circleRadius+margin),达标天数的坐标(circleRadius,-(circleRadius-marginCircleSize))来进行drawText的操作而已,没什么可以说的,Rect是得出字体大小的,具体看上面代码。效果如下图:
最后就是画中心圆的内容的centerCircleContent(canvas)了,代码如下:
//画中心圆的内容public void centerCircleContent(Canvas canvas){if(!centerWatchDag){return ;}//画出颜色渐变的圆圈canvas.rotate(140);float centerSize=circleRadius-marginCircleSize*3-(circleRadius/20);mCenterCircle.setShader(new SweepGradient(0,0,new int[]{friendColor,friendColor,standardColor,averageColor},null));canvas.drawCircle(0,0,centerSize,mCenterCircle);canvas.rotate(-140);//画出运动的总步数mCenterCircle.setShader(null);mCenterCircle.setColor(friendColor);mCenterCircle.setTextSize(circleRadius/4);mCenterCircle.setTextAlign(Paint.Align.CENTER);Rect numRect=new Rect();mCenterCircle.getTextBounds(allStep,0,allStep.length(),numRect);Camera camera=new Camera();camera.rotateY(centerData);camera.applyToCanvas(canvas);canvas.drawText(allStep,0,(numRect.bottom-numRect.top)/2,mCenterCircle);//画出总运动步数右边的字Rect tipRect=new Rect();mCenterCircle.setTextSize(circleRadius/12);mCenterCircle.getTextBounds(tip,0,tip.length(),tipRect);canvas.drawText(tip,(numRect.right-numRect.left)/2+(tipRect.right-tipRect.left)/2+5,(numRect.bottom-numRect.top)/2-3,mCenterCircle);//画出总运动步数下面的提示Rect theyRect=new Rect();mCenterCircle.getTextBounds(theyCount,0,theyCount.length(),theyRect);float marginBottom=circleRadius/12;mCenterCircle.setTextSize(circleRadius/11);canvas.drawText(theyCount,0,marginBottom+(numRect.bottom-numRect.top)/2+(theyRect.bottom-theyRect.top)/2,mCenterCircle);}
中心圆的内容里实现的大概思路画解释的内容的思路都差不多,我觉得值得讲的就是这个Camera类了,这里的Camera类可不是相机里的Camera类,他可以实现Camera的旋转缩放的功能,是一个十分强大的类,而camera.rotateY(centerData)就是设置Y轴旋转的效果的关键代码。其次就是用mCenterCircle.setShader(new SweepGradient(0,0,new int[]{ 来实现圆圈颜色的渐变功能的关键代码,里面还可以实现更多效果,这就需要小伙伴们用外的时间学了。最后效果如下:
friendColor,friendColor,standardColor,averageColor},null));
至此整个绘画就结束了,接着就是动画效果,代码如下:
//启动动画的方法public void loadAnimator(){final ValueAnimator alphaAmimator3=ValueAnimator.ofInt(0,225);final ValueAnimator alphaAmimator2=ValueAnimator.ofInt(0,225);final ValueAnimator wavesAminator=ValueAnimator.ofInt(-30,0);final ValueAnimator centerAnimator=ValueAnimator.ofFloat(0,360);ValueAnimator alphaAmimator1=ValueAnimator.ofInt(0,225);centerAnimator.setDuration(1000);centerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {centerData=(float)animation.getAnimatedValue();postInvalidate();}});wavesAminator.setDuration(1000);wavesAminator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {waveData=(int)animation.getAnimatedValue();waveWatchDag=true;if(waveData==0&&lineWatchDag==false){lineWatchDag=true;centerAnimator.start();}postInvalidate();}});alphaAmimator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCircleAlpha3=(int)animation.getAnimatedValue();postInvalidate();if(mCircleAlpha3==225){wavesAminator.start();}}});alphaAmimator3.setDuration(250);alphaAmimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCircleAlpha2=(int)animation.getAnimatedValue();postInvalidate();if(mCircleAlpha2==225){alphaAmimator3.start();}}});alphaAmimator2.setDuration(250);alphaAmimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCircleAlpha1=(int)animation.getAnimatedValue();postInvalidate();if(mCircleAlpha1==225){alphaAmimator2.start();}}});alphaAmimator1.setDuration(250);alphaAmimator1.start();}
其实就是通过ValueAnimator不断的生成状态量然后调用postInvalidate()不断的刷新View即可实现。
最后要想更详细的了解整个流程请看源码吧。
奉上源码。如果对你有帮助就请给我给星星或喜欢吧
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
