Android自定义View仿微博运动积分动画效果
自定义view一直是自己的短板,趁着公司项目不紧张的时候,多加强这方面的练习。这一系列文章主要记录自己在自定义view的学习过程中的心得与体会。
刷微博的时候,发现微博运动界面,运动积分的显示有一个很好看的动画效果。ok,就从这个开始我的自定义view之路!
看一下最后的效果图:
分数颜色,分数大小,外圆的颜色,圆弧的颜色都支持自己设置,整体还是和微博那个挺像的。一起看看自定义view是怎样一步一步实现的:
1.自定义view的属性:
在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性以及声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定义公共属性 <attr name="titlesize" format="dimension"></attr> <attr name="titlecolor" format="color"></attr> <attr name="outcirclecolor" format="color"></attr> <attr name="incirclecolor" format="color"></attr> <attr name="linecolor" format="color"></attr> //自定义控件的主题样式 <declare-styleable name="mysportview"> <attr name="titlesize"></attr> <attr name="titlecolor"></attr> <attr name="outcirclecolor"></attr> <attr name="incirclecolor"></attr> </declare-styleable> </resources>
依次定义了字体大小,字体颜色,外圆颜色,圆弧颜色4个属性,format是值该属性的取值类型。
然后就是在布局文件中申明我们的自定义view:
<com.example.tangyangkai.myview.mysportview android:id="@+id/sport_view" android:layout_width="200dp" android:layout_height="200dp" android:layout_margin="20dp" app:incirclecolor="@color/strong" app:outcirclecolor="@color/coloraccent" app:titlecolor="@color/colorprimary" app:titlesize="50dp" />
自定义view的属性我们可以自己进行设置,记得最后要引入我们的命名空间,
xmlns:app=”http://schemas.android.com/apk/res-auto”
2.获取自定义view的属性:
/** * created by tangyangkai on 16/5/23. */ public class mysportview extends view { private string text; private int textcolor; private int textsize; private int outcirclecolor; private int incirclecolor; private paint mpaint, circlepaint; //绘制文本的范围 private rect mbound; private rectf circlerect; private float mcurrentangle; private float mstartsweepvalue; private int mcurrentpercent, mtargetpercent; public mysportview(context context) { this(context, null); } public mysportview(context context, attributeset attrs) { this(context, attrs, 0); } public mysportview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); //获取我们自定义的样式属性 typedarray array = context.gettheme().obtainstyledattributes(attrs, r.styleable.mysportview, defstyleattr, 0); int n = array.getindexcount(); for (int i = 0; i < n; i++) { int attr = array.getindex(i); switch (attr) { case r.styleable.mysportview_titlecolor: // 默认颜色设置为黑色 textcolor = array.getcolor(attr, color.black); break; case r.styleable.mysportview_titlesize: // 默认设置为16sp,typevalue也可以把sp转化为px textsize = array.getdimensionpixelsize(attr, (int) typedvalue.applydimension( typedvalue.complex_unit_sp, 16, getresources().getdisplaymetrics())); break; case r.styleable.mysportview_outcirclecolor: // 默认颜色设置为黑色 outcirclecolor = array.getcolor(attr, color.black); break; case r.styleable.mysportview_incirclecolor: // 默认颜色设置为黑色 incirclecolor = array.getcolor(attr, color.black); break; } } array.recycle(); init(); } //初始化 private void init() { //创建画笔 mpaint = new paint(); circlepaint = new paint(); //设置是否抗锯齿 mpaint.setantialias(true); //圆环开始角度 (-90° 为12点钟方向) mstartsweepvalue = -90; //当前角度 mcurrentangle = 0; //当前百分比 mcurrentpercent = 0; //绘制文本的范围 mbound = new rect(); }
自定义view一般需要实现一下三个构造方法,这三个构造方法是一层调用一层的,属于递进关系。因此,我们只需要在最后一个构造方法中来获得view的属性了。
第一步:通过theme.obtainstyledattributes()方法获得自定义控件的主题样式数组;
第二步:遍历每个属性来获得对应属性的值,也就是我们在xml布局文件中写的属性值;
第三步:在循环结束之后记得调用array.recycle()来回收资源;第四步就是进行一下必要的初始化,不建议在ondraw的过程中去实例化对象,因为这是一个频繁重复执行的过程,new是需要分配内存空间的,如果在一个频繁重复的过程中去大量地new对象会造成内存浪费的情况。
3.重写onmesure方法确定view大小:
当你没有重写onmeasure方法时候,系统调用默认的onmeasure方法:
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); }
这个方法的作用是:测量控件的大小。其实android系统在加载布局的时候是由系统测量各子view的大小来告诉父view我需要占多大空间,然后父view会根据自己的大小来决定分配多大空间给子view。measurespec的specmode模式一共有三种:
measurespec.exactly:父视图希望子视图的大小是specsize中指定的大小;一般是设置了明确的值或者是match_parent
measurespec.at_most:子视图的大小最多是specsize中的大小;表示子布局限制在一个最大值内,一般为warp_content
measurespec.unspecified:父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;表示子布局想要多大就多大,很少使用。
想要”wrap_content”的效果怎么办?不着急,只有重写onmeasure方法:
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { //如果布局里面设置的是固定值,这里取布局里面的固定值和父布局大小值中的最小值;如果设置的是match_parent,则取父布局的大小 int widthmode = measurespec.getmode(widthmeasurespec); int widthsize = measurespec.getsize(widthmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); int heightsize = measurespec.getsize(heightmeasurespec); int width; int height; if (widthmode == measurespec.exactly) { width = widthsize; } else { mpaint.settextsize(textsize); mpaint.gettextbounds(text, 0, text.length(), mbound); float textwidth = mbound.width(); int desired = (int) (getpaddingleft() + textwidth + getpaddingright()); width = desired; } if (heightmode == measurespec.exactly) { height = heightsize; } else { mpaint.settextsize(textsize); mpaint.gettextbounds(text, 0, text.length(), mbound); float textheight = mbound.height(); int desired = (int) (getpaddingtop() + textheight + getpaddingbottom()); height = desired; } //调用父类方法,把view的大小告诉父布局。 setmeasureddimension(width, height); }
4.重写ondraw方法进行绘画:
@override protected void ondraw(canvas canvas) { //设置外圆的颜色 mpaint.setcolor(outcirclecolor); //设置外圆为空心 mpaint.setstyle(paint.style.stroke); //画外圆 canvas.drawcircle(getwidth() / 2, getwidth() / 2, getwidth() / 2, mpaint); //设置字体颜色 mpaint.setcolor(textcolor); //设置字体大小 mpaint.settextsize(textsize); //得到字体的宽高范围 text = string.valueof(mcurrentpercent); mpaint.gettextbounds(text, 0, text.length(), mbound); //绘制字体 canvas.drawtext(text, getwidth() / 2 - mbound.width() / 2, getwidth() / 2 + mbound.height() / 2, mpaint); //设置字体大小 mpaint.settextsize(textsize / 3); //绘制字体 canvas.drawtext("分", getwidth() * 3 / 4, getwidth() / 3, mpaint); circlepaint.setantialias(true); circlepaint.setstyle(paint.style.stroke); //设置圆弧的宽度 circlepaint.setstrokewidth(10); //设置圆弧的颜色 circlepaint.setcolor(incirclecolor); //圆弧范围 circlerect = new rectf(20, 20, getwidth() - 20, getwidth() - 20); //绘制圆弧 canvas.drawarc(circlerect, mstartsweepvalue, mcurrentangle, false, circlepaint); //判断当前百分比是否小于设置目标的百分比 if (mcurrentpercent < mtargetpercent) { //当前百分比+1 mcurrentpercent += 1; //当前角度+360 mcurrentangle += 3.6; //每100ms重画一次 postinvalidatedelayed(100); } }
代码注释写的灰常详细,这里和大家分享一个小技巧,就是在重写ondraw方法的之前,自己在本子上画一遍,坐标,位置等简单标注一下。真的很实用!!!
(1)绘制文本的时候,传入的第二个参数与第三个参数也就是图中a点的位置
(2)绘制圆弧先确定圆弧的范围,传入的四个参数就是图中内圆的外接正方形的坐标
(3)绘制圆弧,参数依次是圆弧范围;开始的角度;圆弧的角度;第四个为true时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形,我们选false;圆弧画笔
最后就是分数增加与圆弧动画的实现,这时就需要调用postinvalidatedelayed这个方法,这个方法会每隔指定的时间来调用view的invalidate()方法,最终会重新调用ondraw方法,完成一个周期。所以如果想控制动画,我们就可以定义一个全局的mcurrentpercent与mcurrentangle变量,在ondraw方法中不断的递增,达到动画的效果。
ok,到这里自定义view的实现就全部结束了,其实重头梳理一遍,也没有那么恐怖。
下一篇自定义view,不见不散!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。