欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-01 18:23:28
自定义view一直是自己的短板,趁着公司项目不紧张的时候,多加强这方面的练习。这一系列文章主要记录自己在自定义view的学习过程中的心得与体会。 刷微博的时候,发现微博运...

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

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

看一下最后的效果图:

Android自定义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方法的之前,自己在本子上画一遍,坐标,位置等简单标注一下。真的很实用!!!

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

(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,不见不散!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。