本文转载自:http://blog.csdn.net/tyk0910/article/details/51594479
最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现。先看效果图:
可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化。整体效果还是和QQ运动健康界面很像的。
自定义View四部曲,一起来看看怎么实现的。
1.自定义view的属性:
<code class="hljs xml has-numbering"><span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span> <span class="hljs-tag"><<span class="hljs-title">resources</span>></span> //自定义属性名,定义公共属性 <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"titleSize"</span> <span class="hljs-attribute">format</span>=<span class="hljs-value">"dimension"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"titleText"</span> <span class="hljs-attribute">format</span>=<span class="hljs-value">"string"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"titleColor"</span> <span class="hljs-attribute">format</span>=<span class="hljs-value">"color"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"outCircleColor"</span> <span class="hljs-attribute">format</span>=<span class="hljs-value">"color"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"inCircleColor"</span> <span class="hljs-attribute">format</span>=<span class="hljs-value">"color"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"lineColor"</span> <span class="hljs-attribute">format</span>=<span class="hljs-value">"color"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> //自定义View的属性 <span class="hljs-tag"><<span class="hljs-title">declare-styleable</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"MyQQHealthView"</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"titleColor"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"><<span class="hljs-title">attr</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"lineColor"</span>></span><span class="hljs-tag"></<span class="hljs-title">attr</span>></span> <span class="hljs-tag"></<span class="hljs-title">declare-styleable</span>></span> <span class="hljs-tag"></<span class="hljs-title">resources</span>></span></code>
依次定义了字体颜色,线的颜色2个属性,format是该属性的取值类型。
然后就是在布局文件中申明我们的自定义view:
<code class="hljs avrasm has-numbering"> <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.example</span><span class="hljs-preprocessor">.tangyangkai</span><span class="hljs-preprocessor">.myview</span><span class="hljs-preprocessor">.MyQQHealthView</span> android:id=<span class="hljs-string">"@+id/myQQView"</span> android:layout_width=<span class="hljs-string">"match_parent"</span> android:layout_height=<span class="hljs-string">"530dp"</span> android:layout_margin=<span class="hljs-string">"15dp"</span> myQQ:lineColor=<span class="hljs-string">"@color/font_tips"</span> myQQ:titleColor=<span class="hljs-string">"@color/textcolor"</span> myQQ:titleSize=<span class="hljs-string">"50dp"</span> /></code>
自定义view的属性我们可以自己进行设置,记得最后要引入我们的命名空间,
xmlns:app=”http://schemas.Android.com/apk/res-auto”
2.获取自定义view的属性:
<code class="hljs java has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-title">MyQQHealthView</span>(Context context) { <span class="hljs-keyword">this</span>(context, <span class="hljs-keyword">null</span>); } <span class="hljs-keyword">public</span> <span class="hljs-title">MyQQHealthView</span>(Context context, AttributeSet attrs) { <span class="hljs-keyword">this</span>(context, attrs, <span class="hljs-number">0</span>); } <span class="hljs-keyword">public</span> <span class="hljs-title">MyQQHealthView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr) { <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr); <span class="hljs-comment">//获取我们自定义的样式属性</span> TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyQQHealthView, defStyleAttr, <span class="hljs-number">0</span>); <span class="hljs-keyword">int</span> n = array.getIndexCount(); <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < n; i++) { <span class="hljs-keyword">int</span> attr = array.getIndex(i); <span class="hljs-keyword">switch</span> (attr) { <span class="hljs-keyword">case</span> R.styleable.MyQQHealthView_titleColor: <span class="hljs-comment">// 默认颜色设置为黑色</span> textColor = array.getColor(attr, Color.BLACK); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> R.styleable.MyQQHealthView_lineColor: lineColor = array.getColor(attr, Color.BLACK); <span class="hljs-keyword">break</span>; } } array.recycle(); init(); }</code>
自定义View一般需要实现一下三个构造方法,这三个构造方法是一层调用一层的,属于递进关系。因此,我们只需要在最后一个构造方法中来获得View的属性了。
第一步通过theme.obtainStyledAttributes()方法获得自定义控件的主题样式数组;
第二步就是遍历每个属性来获得对应属性的值,也就是我们在xml布局文件中写的属性值;
第三步就是在循环结束之后记得调用array.recycle()来回收资源;
第四步就是进行一下必要的初始化,不建议在onDraw的过程中去实例化对象,因为这是一个频繁重复执行的过程,new是需要分配内存空间的,如果在一个频繁重复的过程中去大量地new对象会造成内存浪费的情况。
3.重写onMesure方法确定view大小:
当你没有重写onMeasure方法时候,系统调用默认的onMeasure方法:
<code class="hljs java has-numbering"><span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) { <span class="hljs-keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); }</code>
这个方法的作用是:测量控件的大小。其实Android系统在加载布局的时候是由系统测量各子View的大小来告诉父View我需要占多大空间,然后父View会根据自己的大小来决定分配多大空间给子View。MeasureSpec的specMode模式一共有三种:
MeasureSpec.EXACTLY:父视图希望子视图的大小是specSize中指定的大小;一般是设置了明确的值或者是MATCH_PARENT
MeasureSpec.AT_MOST:子视图的大小最多是specSize中的大小;表示子布局限制在一个最大值内,一般为WARP_CONTENT
MeasureSpec.UNSPECIFIED:父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;表示子布局想要多大就多大,很少使用。
想要设置WARP_CONTENT,只要重写onMeasure方法:
<code class="hljs java has-numbering"> <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) { <span class="hljs-keyword">int</span> widthMode = MeasureSpec.getMode(widthMeasureSpec); <span class="hljs-keyword">int</span> widthSize = MeasureSpec.getSize(widthMeasureSpec); <span class="hljs-keyword">int</span> heightMode = MeasureSpec.getMode(heightMeasureSpec); <span class="hljs-keyword">int</span> heightSize = MeasureSpec.getSize(heightMeasureSpec); <span class="hljs-keyword">int</span> width; <span class="hljs-keyword">int</span> height; <span class="hljs-comment">//如果布局里面设置的是固定值,这里取布局里面的固定值;如果设置的是match_parent,则取父布局的大小</span> <span class="hljs-keyword">if</span> (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } <span class="hljs-keyword">else</span> { <span class="hljs-comment">//如果布局里面没有设置固定值,这里取布局的宽度的1/2</span> width = widthSize * <span class="hljs-number">1</span> / <span class="hljs-number">2</span>; } <span class="hljs-keyword">if</span> (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } <span class="hljs-keyword">else</span> { <span class="hljs-comment">//如果布局里面没有设置固定值,这里取布局的高度的3/4</span> height = heightSize * <span class="hljs-number">3</span> / <span class="hljs-number">4</span>; } widthBg = width; heightBg = height; setMeasuredDimension(width, height); startAnim(); }</code>
我这里为了不让布局显得过小,所以WARP_CONTENT分别取宽的1/2,高的3/4,具体情况因人而异。
4.重写onDraw方法进行绘画:
界面比较复杂,我们从上到下,一步一步来:
<code class="hljs avrasm has-numbering"> //绘制最底层的背景 radiusBg = widthBg / <span class="hljs-number">20</span><span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.moveTo</span>(<span class="hljs-number">0</span>, heightBg)<span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.lineTo</span>(<span class="hljs-number">0</span>, radiusBg)<span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.quadTo</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, radiusBg, <span class="hljs-number">0</span>)<span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.lineTo</span>(widthBg - radiusBg, <span class="hljs-number">0</span>)<span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.quadTo</span>(widthBg, <span class="hljs-number">0</span>, widthBg, radiusBg)<span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.lineTo</span>(widthBg, heightBg)<span class="hljs-comment">;</span> pathBg<span class="hljs-preprocessor">.lineTo</span>(<span class="hljs-number">0</span>, heightBg)<span class="hljs-comment">;</span> backgroundPaint<span class="hljs-preprocessor">.setColor</span>(Color<span class="hljs-preprocessor">.WHITE</span>)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawPath</span>(pathBg, backgroundPaint)<span class="hljs-comment">;</span></code>
整个自定义View的最底层是一个左上,右上有弧度,左下,右下为直角的白色背景。使用canvas.drawRoundRect实现的矩形是四个角都有弧度,达不到预期。但是一阶贝塞尔曲线加上二阶贝塞尔曲线就能很好的实现这种特殊的情况。
moveTo(x,y):不会进行绘制,只用于移动移动画笔,确定起点坐标,与其他方法配合使用;
lineTo(x,y):一阶贝塞尔曲线,坐标为终点坐标,配合moveTo方法用于进行直线绘制;
quadTo (x1,y1,x2,y2):二阶贝塞尔曲线,(x1,y1) 为控制点,(x2,y2)为结束点,用于绘制圆滑的曲线;
最后调用canvas.drawPath(path,paint)即可达到这种效果
<code class="hljs avrasm has-numbering"> //绘制圆弧 arcPaint<span class="hljs-preprocessor">.setStrokeWidth</span>(widthBg / <span class="hljs-number">20</span>)<span class="hljs-comment">;</span> //设置空心 arcPaint<span class="hljs-preprocessor">.setStyle</span>(Paint<span class="hljs-preprocessor">.Style</span><span class="hljs-preprocessor">.STROKE</span>)<span class="hljs-comment">;</span> //防抖动 arcPaint<span class="hljs-preprocessor">.setDither</span>(true)<span class="hljs-comment">;</span> //连接处为圆弧 arcPaint<span class="hljs-preprocessor">.setStrokeJoin</span>(Paint<span class="hljs-preprocessor">.Join</span><span class="hljs-preprocessor">.ROUND</span>)<span class="hljs-comment">;</span> //画笔的笔触为圆角 arcPaint<span class="hljs-preprocessor">.setStrokeCap</span>(Paint<span class="hljs-preprocessor">.Cap</span><span class="hljs-preprocessor">.ROUND</span>)<span class="hljs-comment">;</span> arcPaint<span class="hljs-preprocessor">.setColor</span>(lineColor)<span class="hljs-comment">;</span> //圆弧范围 arcRect = new RectF(widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">4</span>, widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">4</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">4</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">4</span>)<span class="hljs-comment">;</span> //绘制背景大圆弧 canvas<span class="hljs-preprocessor">.drawArc</span>(arcRect, <span class="hljs-number">120</span>, <span class="hljs-number">300</span>, false, arcPaint)<span class="hljs-comment">;</span> arcPaint<span class="hljs-preprocessor">.setColor</span>(textColor)<span class="hljs-comment">;</span> //绘制分数小圆弧 canvas<span class="hljs-preprocessor">.drawArc</span>(arcRect, <span class="hljs-number">120</span>, arcNum, false, arcPaint)<span class="hljs-comment">;</span></code>
绘制圆弧先确定圆弧的范围,传入的四个参数就是圆弧所在圆的外接矩形的坐标。canvas.drawArc的五个参数依次是圆弧范围;开始的角度;圆弧的角度;第四个为True时,在绘制圆弧时会将圆心包括在内,通常用来绘制扇形,我们这里选false;圆弧的画笔
<code class="hljs avrasm has-numbering"> //绘制圆圈内的数字 textPaint<span class="hljs-preprocessor">.setColor</span>(textColor)<span class="hljs-comment">;</span> textPaint<span class="hljs-preprocessor">.setTextSize</span>(widthBg / <span class="hljs-number">10</span>)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(String<span class="hljs-preprocessor">.valueOf</span>(walkNum), widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">8</span>, widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">2</span> + <span class="hljs-number">20</span>, textPaint)<span class="hljs-comment">;</span> //绘制名次 textPaint<span class="hljs-preprocessor">.setTextSize</span>(widthBg / <span class="hljs-number">15</span>)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(String<span class="hljs-preprocessor">.valueOf</span>(rankNum), widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">2</span> - <span class="hljs-number">15</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">4</span> + <span class="hljs-number">10</span>, textPaint)<span class="hljs-comment">;</span> //绘制其他文字 textPaint<span class="hljs-preprocessor">.setColor</span>(lineColor)<span class="hljs-comment">;</span> textPaint<span class="hljs-preprocessor">.setTextSize</span>(widthBg / <span class="hljs-number">25</span>)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"截止13:45已走"</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">8</span> - <span class="hljs-number">10</span>, widthBg * <span class="hljs-number">5</span> / <span class="hljs-number">12</span> - <span class="hljs-number">10</span>, textPaint)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"好友平均2781步"</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">8</span> - <span class="hljs-number">10</span>, widthBg * <span class="hljs-number">2</span> / <span class="hljs-number">3</span> - <span class="hljs-number">20</span>, textPaint)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"第"</span>, widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">2</span> - <span class="hljs-number">50</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">4</span> + <span class="hljs-number">10</span>, textPaint)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"名"</span>, widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">2</span> + <span class="hljs-number">30</span>, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">4</span> + <span class="hljs-number">10</span>, textPaint)<span class="hljs-comment">;</span> //绘制圆圈外的文字 canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"最近7天"</span>, widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">15</span>, widthBg, textPaint)<span class="hljs-comment">;</span> myaverageTxt = String<span class="hljs-preprocessor">.valueOf</span>(averageSize)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"平均"</span>, widthBg * <span class="hljs-number">10</span> / <span class="hljs-number">15</span> - <span class="hljs-number">15</span>, widthBg, textPaint)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(myaverageTxt, widthBg * <span class="hljs-number">11</span> / <span class="hljs-number">15</span>, widthBg, textPaint)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"步/天"</span>, widthBg * <span class="hljs-number">12</span> / <span class="hljs-number">15</span> + <span class="hljs-number">20</span>, widthBg, textPaint)<span class="hljs-comment">;</span></code>
绘制文字就稍微简单点,这里计算好各自的位置即可
<code class="hljs avrasm has-numbering"> //绘制虚线 linePaint<span class="hljs-preprocessor">.setStyle</span>(Paint<span class="hljs-preprocessor">.Style</span><span class="hljs-preprocessor">.STROKE</span>)<span class="hljs-comment">;</span> linePaint<span class="hljs-preprocessor">.setStrokeWidth</span>(<span class="hljs-number">2</span>)<span class="hljs-comment">;</span> linePaint<span class="hljs-preprocessor">.setColor</span>(lineColor)<span class="hljs-comment">;</span> linePath<span class="hljs-preprocessor">.moveTo</span>(widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">15</span>, widthBg + <span class="hljs-number">80</span>)<span class="hljs-comment">;</span> linePath<span class="hljs-preprocessor">.lineTo</span>(widthBg * <span class="hljs-number">14</span> / <span class="hljs-number">15</span>, widthBg + <span class="hljs-number">80</span>)<span class="hljs-comment">;</span> linePaint<span class="hljs-preprocessor">.setPathEffect</span>(effects)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawPath</span>(linePath, linePaint)<span class="hljs-comment">;</span> rectSize = widthBg / <span class="hljs-number">12</span><span class="hljs-comment">;</span> rectAgHeight = widthBg / <span class="hljs-number">10</span><span class="hljs-comment">;</span> //绘制虚线上的圆角竖线 for (int i = <span class="hljs-number">0</span><span class="hljs-comment">; i < FourActivity.sizes.size(); i++) {</span> rectPaint<span class="hljs-preprocessor">.setStrokeWidth</span>(widthBg / <span class="hljs-number">25</span>)<span class="hljs-comment">;</span> rectPaint<span class="hljs-preprocessor">.setStyle</span>(Paint<span class="hljs-preprocessor">.Style</span><span class="hljs-preprocessor">.STROKE</span>)<span class="hljs-comment">;</span> rectPaint<span class="hljs-preprocessor">.setStrokeJoin</span>(Paint<span class="hljs-preprocessor">.Join</span><span class="hljs-preprocessor">.ROUND</span>)<span class="hljs-comment">;</span> rectPaint<span class="hljs-preprocessor">.setStrokeCap</span>(Paint<span class="hljs-preprocessor">.Cap</span><span class="hljs-preprocessor">.ROUND</span>)<span class="hljs-comment">;</span> float startHeight = widthBg + <span class="hljs-number">90</span> + rectAgHeight<span class="hljs-comment">;</span> rectPath<span class="hljs-preprocessor">.moveTo</span>(rectSize, startHeight)<span class="hljs-comment">;</span> double percentage = Double<span class="hljs-preprocessor">.valueOf</span>(FourActivity<span class="hljs-preprocessor">.sizes</span><span class="hljs-preprocessor">.get</span>(i)) / Double<span class="hljs-preprocessor">.valueOf</span>(averageSize)<span class="hljs-comment">;</span> double height = percentage * rectAgHeight<span class="hljs-comment">;</span> rectPath<span class="hljs-preprocessor">.lineTo</span>(rectSize, (float) (startHeight - height))<span class="hljs-comment">;</span> rectPaint<span class="hljs-preprocessor">.setColor</span>(textColor)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawPath</span>(rectPath, rectPaint)<span class="hljs-comment">;</span> //绘制下方的文字 textPaint<span class="hljs-preprocessor">.setColor</span>(lineColor)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"0"</span> + (i + <span class="hljs-number">1</span>) + <span class="hljs-string">"日"</span>, rectSize - <span class="hljs-number">25</span>, startHeight + <span class="hljs-number">50</span>, textPaint)<span class="hljs-comment">;</span> rectSize += widthBg / <span class="hljs-number">7</span><span class="hljs-comment">;</span> }</code>
DashPathEffect的作用就是将Path的线段虚线化。构造函数为DashPathEffect(float[] intervals, float offset),其中intervals为虚线的ON和OFF数组,该数组的length必须大于等于2,float[0] ,float[1] 依次代表第一条实线与第一条虚线的长度,如果数组后面不再有数据则重复第一个数以此往复循环。offset为绘制时的偏移量。
<code class="hljs cs has-numbering">DashPathEffect effects = <span class="hljs-keyword">new</span> DashPathEffect(<span class="hljs-keyword">new</span> <span class="hljs-keyword">float</span>[]{<span class="hljs-number">5</span>,<span class="hljs-number">5</span>}, <span class="hljs-number">1</span>);</code>
然后将这个实例作为linePaint.setPathEffect()的参数即可。绘制下方文字写的是一个简单的循环,然后竖线的长度是根据步数数组大小来进行计算的。示例图中改变平均步数以后,竖线的长度也进行了变化。
<code class="hljs avrasm has-numbering"> //绘制底部波纹 weavPaint<span class="hljs-preprocessor">.setColor</span>(textColor)<span class="hljs-comment">;</span> weavPath<span class="hljs-preprocessor">.reset</span>()<span class="hljs-comment">;</span> weavPath<span class="hljs-preprocessor">.moveTo</span>(<span class="hljs-number">0</span>, heightBg)<span class="hljs-comment">;</span> weavPath<span class="hljs-preprocessor">.lineTo</span>(<span class="hljs-number">0</span>, heightBg * <span class="hljs-number">10</span> / <span class="hljs-number">12</span>)<span class="hljs-comment">;</span> weavPath<span class="hljs-preprocessor">.cubicTo</span>(weavX, weavY, widthBg * <span class="hljs-number">3</span> / <span class="hljs-number">10</span>, heightBg * <span class="hljs-number">11</span> / <span class="hljs-number">12</span>, widthBg, heightBg * <span class="hljs-number">10</span> / <span class="hljs-number">12</span>)<span class="hljs-comment">;</span> weavPath<span class="hljs-preprocessor">.lineTo</span>(widthBg, heightBg)<span class="hljs-comment">;</span> weavPath<span class="hljs-preprocessor">.lineTo</span>(<span class="hljs-number">0</span>, heightBg)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawPath</span>(weavPath, weavPaint)<span class="hljs-comment">;</span> //绘制底部文字 weavPaint<span class="hljs-preprocessor">.setColor</span>(Color<span class="hljs-preprocessor">.WHITE</span>)<span class="hljs-comment">;</span> weavPaint<span class="hljs-preprocessor">.setTextSize</span>(widthBg / <span class="hljs-number">20</span>)<span class="hljs-comment">;</span> canvas<span class="hljs-preprocessor">.drawText</span>(<span class="hljs-string">"成绩不错,继续努力哟!"</span>, widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">10</span> - <span class="hljs-number">20</span>, heightBg * <span class="hljs-number">11</span> / <span class="hljs-number">12</span> + <span class="hljs-number">50</span>, weavPaint)<span class="hljs-comment">;</span></code>
底部水波纹的实现使用的是三阶贝塞尔曲线:
cubicTo(x1, y1, x2, y2, x3, y3):三阶贝塞尔曲线, (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点,用于绘制复杂的曲线。
关于重写onDraw方法,个人建议就是最好在本子上将需要完成的自定义view大致轮廓画下来,标注好坐标与位置,计算一下高宽。然后根据绘制的自定义View,从上到下,从外到内,一步一步进行绘制。真的很实用,这样逻辑清晰,层次分明,对你写代码很有帮助。
5.动画的实现:
<code class="hljs java has-numbering"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">startAnim</span>() { <span class="hljs-comment">//步数动画的实现</span> ValueAnimator walkAnimator = ValueAnimator.ofInt(<span class="hljs-number">0</span>, mySize); walkAnimator.addUpdateListener(<span class="hljs-keyword">new</span> ValueAnimator.AnimatorUpdateListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationUpdate</span>(ValueAnimator animation) { walkNum = (<span class="hljs-keyword">int</span>) animation.getAnimatedValue(); postInvalidate(); } }); <span class="hljs-comment">//排名动画的实现</span> ValueAnimator rankAnimator = ValueAnimator.ofInt(<span class="hljs-number">0</span>, rank); rankAnimator.addUpdateListener(<span class="hljs-keyword">new</span> ValueAnimator.AnimatorUpdateListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationUpdate</span>(ValueAnimator animation) { rankNum = (<span class="hljs-keyword">int</span>) animation.getAnimatedValue(); postInvalidate(); } }); <span class="hljs-keyword">double</span> size = mySize; <span class="hljs-keyword">double</span> avgSize = averageSize; <span class="hljs-keyword">if</span> (size > avgSize) { size = avgSize; } <span class="hljs-comment">//圆弧动画的实现</span> ValueAnimator arcAnimator = ValueAnimator.ofFloat(<span class="hljs-number">0</span>, (<span class="hljs-keyword">float</span>) (size / avgSize * <span class="hljs-number">300</span>)); arcAnimator.addUpdateListener(<span class="hljs-keyword">new</span> ValueAnimator.AnimatorUpdateListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationUpdate</span>(ValueAnimator animation) { arcNum = (<span class="hljs-keyword">float</span>) animation.getAnimatedValue(); postInvalidate(); } }); <span class="hljs-comment">//水波纹动画的实现</span> ValueAnimator weavXAnimator = ValueAnimator.ofFloat(widthBg * <span class="hljs-number">1</span> / <span class="hljs-number">10</span>, widthBg * <span class="hljs-number">2</span>/ <span class="hljs-number">10</span>); weavXAnimator.addUpdateListener(<span class="hljs-keyword">new</span> ValueAnimator.AnimatorUpdateListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationUpdate</span>(ValueAnimator animation) { weavX = (<span class="hljs-keyword">float</span>) animation.getAnimatedValue(); postInvalidate(); } }); ValueAnimator weavYAnimator = ValueAnimator.ofFloat(heightBg*<span class="hljs-number">10</span>/<span class="hljs-number">12</span>, heightBg*<span class="hljs-number">11</span>/<span class="hljs-number">12</span>); weavYAnimator.addUpdateListener(<span class="hljs-keyword">new</span> ValueAnimator.AnimatorUpdateListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationUpdate</span>(ValueAnimator animation) { weavY = (<span class="hljs-keyword">float</span>) animation.getAnimatedValue(); postInvalidate(); } }); animSet.setDuration(<span class="hljs-number">3000</span>); animSet.playTogether(walkAnimator, rankAnimator, arcAnimator,weavXAnimator,weavYAnimator); animSet.start(); }</code>
这里就不得不提到属性动画的优越性了,不仅可以作用在view上,也可以作用于某个对象上。只需要设置好开始值与结束值,添加一个动画的监听,就能够得到变化的值,再使用postInvalidate()方法,从而调用onDraw方法来进行数值的改变。最后设置一个组合动画—-AnimatorSet,使五个动画达到同步一致的效果。
然后就是使用动画了,刚开始我是将这个方法写在init()初始化函数里面,一直达不到预期的效果。后来才知道,自定义View进行初始化的时候,组合动画需要的一些值:步数,排名,平均步数等还没有传递过来,所以动画无法完成。最后我是将这个方法写在onMeasure()方法的后面才达到效果,这里很感谢同事的提醒。
6.设置步数大小以及在Activity中的使用:
<code class="hljs cs has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">reSet</span>(<span class="hljs-keyword">int</span> mysize, <span class="hljs-keyword">int</span> myrank, <span class="hljs-keyword">int</span> myaverageSize) { walkNum = <span class="hljs-number">0</span>; arcNum = <span class="hljs-number">0</span>; rankNum = <span class="hljs-number">0</span>; mySize = mysize; rank = myrank; averageSize = myaverageSize; startAnim(); }</code>
将设置的值通过构造方法传递过来,最后调用开启动画的方法即可。对应的Activity的代码:
<code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FourActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span> <span class="hljs-keyword">private</span> MyQQHealthView view; <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List<Integer> sizes = <span class="hljs-keyword">new</span> ArrayList<>(); <span class="hljs-keyword">private</span> Button btn; <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) { <span class="hljs-keyword">super</span>.onCreate(savedInstanceState); setContentView(R.layout.activity_four); initview(); } <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initview</span>() { view = (MyQQHealthView) findViewById(R.id.myQQView); view.setMySize(<span class="hljs-number">2345</span>); view.setRank(<span class="hljs-number">11</span>); view.setAverageSize(<span class="hljs-number">5436</span>); sizes.add(<span class="hljs-number">1234</span>); sizes.add(<span class="hljs-number">2234</span>); sizes.add(<span class="hljs-number">4234</span>); sizes.add(<span class="hljs-number">6234</span>); sizes.add(<span class="hljs-number">3834</span>); sizes.add(<span class="hljs-number">7234</span>); sizes.add(<span class="hljs-number">5436</span>); btn = (Button) findViewById(R.id.set_btn); btn.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span>(View v) { view.reSet(<span class="hljs-number">6534</span>, <span class="hljs-number">8</span>, <span class="hljs-number">4567</span>); } }); } }</code>
自己根据情况设置想要的值就可以了。
整个流程走下来,你会发现其实自定义View并没有想象之中的那么难。多加练习,熟能生巧,相信再复杂的界面也难不住我们的。
OK,下一篇自定义View再见~