Android自定义View仿微博运动积分动画效果,android 自定view

4
回复
816
查看
[复制链接]

394

主题

1043

帖子

850

安币

手工艺人

发表于 2018-1-9 10:49:45 | 显示全部楼层 |阅读模式

        自定义view一直是自己的短板,趁着公司项目不紧张的时候,多加强这方面的练习。这一系列文章主要记录自己在自定义view的学习过程中的心得与体会。

        刷微博的时候,发现微博运动界面,运动积分的显示有一个很好看的动画效果。ok,就从这个开始我的自定义view之路!

        看一下最后的效果图:

        

        分数颜色,分数大小,外圆的颜色,圆弧的颜色都支持自己设置,整体还是和微博那个挺像的。一起看看自定义view是怎样一步一步实现的:

        1.自定义view的属性:
在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性以及声明我们的整个样式。

        

[Java] 查看源文件 复制代码
<

        依次定义了字体大小,字体颜色,外圆颜色,圆弧颜色4个属性,format是值该属性的取值类型。
然后就是在布局文件中申明我们的自定义view:

[Java] 查看源文件 复制代码
    <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的属性:

        

[Java] 查看源文件 复制代码
/**
 * 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方法:

[Java] 查看源文件 复制代码
 @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方法:

        

[Java] 查看源文件 复制代码
 @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方法进行绘画:

[Java] 查看源文件 复制代码
 @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点的位置
复制代码 代码如下:canvas.drawtext(text, getwidth() / 2 - mbound.width() / 2, getwidth() / 2 + mbound.height() / 2, mpaint);
(2)绘制圆弧先确定圆弧的范围,传入的四个参数就是图中内圆的外接正方形的坐标
复制代码 代码如下:circlerect = new rectf(20, 20, getwidth() - 20, getwidth() - 20);
(3)绘制圆弧,参数依次是圆弧范围;开始的角度;圆弧的角度;第四个为true时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形,我们选false;圆弧画笔复制代码 代码如下:canvas.drawarc(circlerect, mstartsweepvalue, mcurrentangle, false, circlepaint);

        最后就是分数增加与圆弧动画的实现,这时就需要调用postinvalidatedelayed这个方法,这个方法会每隔指定的时间来调用view的invalidate()方法,最终会重新调用ondraw方法,完成一个周期。所以如果想控制动画,我们就可以定义一个全局的mcurrentpercent与mcurrentangle变量,在ondraw方法中不断的递增,达到动画的效果。

        ok,到这里自定义view的实现就全部结束了,其实重头梳理一遍,也没有那么恐怖。

        下一篇自定义view,不见不散!


0

主题

9369

帖子

2401

安币

Android大神

Rank: 6Rank: 6

发表于 2018-1-10 00:49:15 | 显示全部楼层
帮帮顶顶!!

0

主题

9871

帖子

1695

安币

Android大神

IT

Rank: 6Rank: 6

发表于 2018-1-10 15:44:01 | 显示全部楼层
感谢分享,安卓巴士有你更精彩:lol

0

主题

9519

帖子

1146

安币

Android大神

Rank: 6Rank: 6

发表于 2018-1-11 13:00:48 | 显示全部楼层
支持楼主,支持安卓巴士!

11

主题

9461

帖子

980

安币

代码手工艺人

Rank: 4

发表于 2018-1-12 18:46:12 | 显示全部楼层
帮帮顶顶!!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )