├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── bluemobi │ │ └── dylan │ │ └── animatordemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── MainTest.java │ │ └── cn │ │ │ └── bluemobi │ │ │ └── dylan │ │ │ └── animatordemo │ │ │ ├── MainActivity.java │ │ │ └── StepArcView.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── bluemobi │ └── dylan │ └── animatordemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshorts └── effect.gif └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | Android属性动画上手实现各种效果,包括实现基本的透明度,缩放,平移,旋转,以及组合动画,还有就是自定义动画仿 2 | QQ运动和抛物线动画。效果图如下: 3 | ![效果图](https://github.com/linglongxin24/AnimatorDemo/blob/master/screenshorts/effect.gif?raw=true) 4 | #1.为什么要用属性动画 5 | 属性动画:顾名思义,属性动画就是通过改变一个控件的属性值而达到动画的效果。是3.0之后新出的动画框架。 6 | 注意:只要控件的属性提供了set属性的方法,就可以通过属性动画去操作。 7 | 属性动画和普通动画的区别:一个普通的动画,比如当发生位移动画的时候普通的补间动画只是改变了其显示效果 8 | 并没有真正去改变其属性,所以当点击位移发生移动后的view是没有任何的效果,因为它的真是位置还在原来的地方,比如去动态改变一个view的背景颜色 9 | 补间动画都无法去实现,真正是属性动画的好处。 10 | #2.两个核心类 11 | 12 | * ValueAnimator 13 | 14 | ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, 15 | 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 16 | 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, 17 | 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, 18 | 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 19 | 20 | * ObjectAnimator是ValueAnimator的子类 21 | 22 | ObjectAnimator是ValueAnimator的子类,他本身就已经包含了时间引擎和值计算,所以它拥有为对象的某个属性设置动画的功能。 23 | 这使得为任何对象设置动画更加的容易。你不再需要实现 ValueAnimator.AnimatorUpdateListener接口, 24 | 因为ObjectAnimator动画自己会自动更新相应的属性值。 25 | ObjectAnimator的实例和ValueAnimator是类似的,但是你需要描叙该对象,需要设置动画的属性的名字(一个字符串),以及动画属性值的变化范围。 26 | 举例: 27 | 28 | /** 29 | * 第一个参数:所要作用的目标控件 30 | * 第二个参数:所要操作该控件的属性值 31 | * 第三个参数:所要操作的属性的开始值 32 | * 第四个参数:所要操作属性的结束值 33 | */ 34 | ObjectAnimator anim = ObjectAnimator.ofFloat(iv, "alpha", 0f, 1f); 35 | 36 | /**设置时长**/ 37 | 38 | anim.setDuration(1000); 39 | 40 | /**开始动画**/ 41 | 42 | anim.start(); 43 | 44 | #3.实现动画效果 45 | 46 | * (1)透明度动画 47 | 48 | ```java 49 | /** 50 | * 第一个参数:所要作用的目标控件 51 | * 第二个参数:所要操作该控件的属性值 52 | * 第三个参数:所要操作的属性的开始值 53 | * 第四个参数:所要操作属性的结束值 54 | */ 55 | ObjectAnimator objectAnimator = ObjectAn 56 | objectAnimator.setDuration(1000); 57 | objectAnimator.start(); 58 | ``` 59 | 60 | * (2)缩放动画 61 | 62 | ```java 63 | /**动画组合**/ 64 | PropertyValuesHolder objectAnimatorScaleX = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f); 65 | PropertyValuesHolder objectAnimatorScaleY = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f); 66 | /**同时播放两个动画**/ 67 | ObjectAnimator.ofPropertyValuesHolder(iv, objectAnimatorScaleX, objectAnimatorScaleY).setDuration(1000).start(); 68 | 69 | ``` 70 | 71 | * (3)旋转动画 72 | 73 | ```java 74 | ObjectAnimator objectAnimatorScale = ObjectAnimator.ofFloat(iv, "rotation", 0f, 360f); 75 | objectAnimatorScale.setDuration(1000); 76 | objectAnimatorScale.start(); 77 | ``` 78 | 79 | * (4)位移动画 80 | 81 | ```java 82 | ObjectAnimator objectAnimatorTransl 83 | objectAnimatorTranslate.setDuration 84 | objectAnimatorTranslate.start(); 85 | 86 | ``` 87 | 88 | * (5)组合动画一:先播放缩放动画,完成后播放旋转动画 89 | 90 | ```java 91 | /**动画组合**/ 92 | AnimatorSet animatorSetGroup1 = new AnimatorSet(); 93 | ObjectAnimator objectAnimatorScaleX1 = ObjectAnimator.ofFloat(iv, "scaleX", 0f, 1f); 94 | ObjectAnimator objectAnimatorScaleY1 = ObjectAnimator.ofFloat(iv, "scaleY", 0f, 1f); 95 | ObjectAnimator objectAnimatorRotateX1 = ObjectAnimator.ofFloat(iv, "rotationX", 0f, 360f); 96 | ObjectAnimator objectAnimatorRotateY1 = ObjectAnimator.ofFloat(iv, "rotationY", 0f, 360f); 97 | animatorSetGroup1.setDuration(1000); 98 | animatorSetGroup1.play(objectAnimatorScaleX1).with(objectAnimatorScaleY1) 99 | .before(objectAnimatorRotateX1).before(objectAnimatorRotateY1); 100 | animatorSetGroup1.start(); 101 | ``` 102 | 103 | * (6))组合动画二:先播放旋转动画,完成后播放位移动画 104 | 105 | ```java 106 | AnimatorSet animatorSetGroup2 = new AnimatorSet(); 107 | ObjectAnimator objectAnimatorTranslate2 = ObjectAnimator.ofFloat(iv, "translationX", 0f, 500f); 108 | ObjectAnimator objectAnimatorRotateX2 = ObjectAnimator.ofFloat(iv, "rotationX", 0f, 360f); 109 | ObjectAnimator objectAnimatorRotateY2 = ObjectAnimator.ofFloat(iv, "rotationY", 0f, 360f); 110 | animatorSetGroup2.setDuration(1000); 111 | animatorSetGroup2.play(objectAnimatorTranslate2).after(objectAnimatorRotateX2) 112 | .after(objectAnimatorRotateY2); 113 | animatorSetGroup2.start(); 114 | ``` 115 | 116 | * (7)重复动画一:重复的透明度动画-闪烁 117 | 118 | ```java 119 | ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(iv, "alpha", 0f, 1f); 120 | objectAnimator2.setDuration(500); 121 | objectAnimator2.setRepeatCount(3); 122 | objectAnimator2.start(); 123 | ``` 124 | 125 | * (8)重复动画二:重复的位移动画-抖动 126 | 127 | ```java 128 | ObjectAnimator objectAnimatorTranslate3 = ObjectAnimator.ofFloat(iv, "translationX", -50f, 50f); 129 | objectAnimatorTranslate3.setDuration(500); 130 | objectAnimatorTranslate3.setRepeatCount(3); 131 | objectAnimatorTranslate3.start();objectAnimator2.start(); 132 | ``` 133 | 134 | * (9)动态改变控件颜色 135 | 136 | ```java 137 | ObjectAnimator objectAnimatorBg = ObjectAnimator.ofInt(iv, "backgroundColor", Color.BLUE, Color.YELLOW, Color.RED); 138 | objectAnimatorBg.setDuration(3000); 139 | objectAnimatorBg.start(); 140 | ``` 141 | 142 | * (10)仿QQ运动动画 143 | 144 | ```java 145 | /** 146 | * 为进度设置动画 147 | * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, 148 | * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 149 | * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, 150 | * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, 151 | * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 152 | * 153 | * @param last 154 | * @param current 155 | */ 156 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 157 | private void setAnimation(float last, float current, int length) { 158 | ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current); 159 | progressAnimator.setDuration(length); 160 | progressAnimator.setTarget(currentAngleLength); 161 | progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 162 | @Override 163 | public void onAnimationUpdate(ValueAnimator animation) { 164 | currentAngleLength = (float) animation.getAnimatedValue(); 165 | invalidate(); 166 | } 167 | }); 168 | progressAnimator.start(); 169 | } 170 | ``` 171 | 172 | * (11)抛物线动画 173 | 174 | ```java 175 | 176 | /** 177 | * 抛物线动画 178 | */ 179 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 180 | public void parabola() 181 | { 182 | 183 | ValueAnimator valueAnimator = new ValueAnimator(); 184 | valueAnimator.setDuration(3000); 185 | valueAnimator.setObjectValues(new PointF(0, 0)); 186 | valueAnimator.setInterpolator(new LinearInterpolator()); 187 | valueAnimator.setEvaluator(new TypeEvaluator() 188 | { 189 | 190 | @Override 191 | public PointF evaluate(float fraction, PointF startValue, 192 | PointF endValue) 193 | { 194 | /**x方向200px/s ,则y方向0.5 * 200 * t**/ 195 | PointF point = new PointF(); 196 | point.x = 200 * fraction * 3; 197 | point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3); 198 | return point; 199 | } 200 | }); 201 | 202 | valueAnimator.start(); 203 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 204 | { 205 | @Override 206 | public void onAnimationUpdate(ValueAnimator animation) 207 | { 208 | PointF point = (PointF) animation.getAnimatedValue(); 209 | iv.setX(point.x); 210 | iv.setY(point.y); 211 | 212 | } 213 | }); 214 | } 215 | ``` 216 | 217 | #4.[GiHub](https://github.com/linglongxin24/AnimatorDemo) 218 | 219 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.0" 6 | defaultConfig { 7 | applicationId "cn.bluemobi.dylan.animatordemo" 8 | minSdkVersion 9 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.0.0' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\kejiang\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/bluemobi/dylan/animatordemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cn.bluemobi.dylan.animatordemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cn.bluemobi.dylan.animatordemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/MainTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yuandl on 2016-11-08. 3 | */ 4 | 5 | public class MainTest { 6 | public static void main(String args[]) { 7 | System.out.println("Hello Word"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/cn/bluemobi/dylan/animatordemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.bluemobi.dylan.animatordemo; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.PropertyValuesHolder; 6 | import android.animation.TypeEvaluator; 7 | import android.animation.ValueAnimator; 8 | import android.graphics.Color; 9 | import android.graphics.PointF; 10 | import android.os.Build; 11 | import android.support.annotation.RequiresApi; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.os.Bundle; 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.view.animation.LinearInterpolator; 17 | import android.widget.ImageView; 18 | 19 | public class MainActivity extends AppCompatActivity { 20 | private ImageView iv; 21 | private StepArcView sv; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | iv = (ImageView) findViewById(R.id.iv); 28 | sv = (StepArcView) findViewById(R.id.sv); 29 | } 30 | 31 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 32 | public void onClick(View v) { 33 | iv.setBackgroundColor(Color.TRANSPARENT); 34 | switch (v.getId()) {//透明度动画 35 | case R.id.animation_alpha: 36 | /** 37 | * 第一个参数:所要作用的目标控件 38 | * 第二个参数:所要操作该控件的属性值 39 | * 第三个参数:所要操作的属性的开始值 40 | * 第四个参数:所要操作属性的结束值 41 | */ 42 | ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(iv, "alpha", 0f, 1f); 43 | objectAnimator.setDuration(1000); 44 | objectAnimator.start(); 45 | break; 46 | case R.id.animation_scale://缩放动画 47 | /**动画组合**/ 48 | PropertyValuesHolder objectAnimatorScaleX = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f); 49 | PropertyValuesHolder objectAnimatorScaleY = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f); 50 | /**同时播放两个动画**/ 51 | ObjectAnimator.ofPropertyValuesHolder(iv, objectAnimatorScaleX, objectAnimatorScaleY).setDuration(1000).start(); 52 | 53 | break; 54 | case R.id.animation_rotate://旋转动画 55 | ObjectAnimator objectAnimatorScale = ObjectAnimator.ofFloat(iv, "rotation", 0f, 360f); 56 | objectAnimatorScale.setDuration(1000); 57 | objectAnimatorScale.start(); 58 | break; 59 | case R.id.animation_translate://位移动画 60 | ObjectAnimator objectAnimatorTranslate = ObjectAnimator.ofFloat(iv, "translationX", 0f, 500f); 61 | objectAnimatorTranslate.setDuration(1000); 62 | objectAnimatorTranslate.start(); 63 | break; 64 | case R.id.animation_group1://先播放缩放动画,完成后播放旋转动画 65 | /**动画组合**/ 66 | AnimatorSet animatorSetGroup1 = new AnimatorSet(); 67 | ObjectAnimator objectAnimatorScaleX1 = ObjectAnimator.ofFloat(iv, "scaleX", 0f, 1f); 68 | ObjectAnimator objectAnimatorScaleY1 = ObjectAnimator.ofFloat(iv, "scaleY", 0f, 1f); 69 | ObjectAnimator objectAnimatorRotateX1 = ObjectAnimator.ofFloat(iv, "rotationX", 0f, 360f); 70 | ObjectAnimator objectAnimatorRotateY1 = ObjectAnimator.ofFloat(iv, "rotationY", 0f, 360f); 71 | animatorSetGroup1.setDuration(1000); 72 | animatorSetGroup1.play(objectAnimatorScaleX1).with(objectAnimatorScaleY1) 73 | .before(objectAnimatorRotateX1).before(objectAnimatorRotateY1); 74 | animatorSetGroup1.start(); 75 | break; 76 | case R.id.animation_group2://先播放旋转动画,完成后播放位移动画 77 | AnimatorSet animatorSetGroup2 = new AnimatorSet(); 78 | ObjectAnimator objectAnimatorTranslate2 = ObjectAnimator.ofFloat(iv, "translationX", 0f, 500f); 79 | ObjectAnimator objectAnimatorRotateX2 = ObjectAnimator.ofFloat(iv, "rotationX", 0f, 360f); 80 | ObjectAnimator objectAnimatorRotateY2 = ObjectAnimator.ofFloat(iv, "rotationY", 0f, 360f); 81 | animatorSetGroup2.setDuration(1000); 82 | animatorSetGroup2.play(objectAnimatorTranslate2).after(objectAnimatorRotateX2) 83 | .after(objectAnimatorRotateY2); 84 | animatorSetGroup2.start(); 85 | break; 86 | case R.id.animation_group3://重复的透明度动画 87 | ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(iv, "alpha", 0f, 1f); 88 | objectAnimator2.setDuration(500); 89 | objectAnimator2.setRepeatCount(3); 90 | objectAnimator2.start(); 91 | break; 92 | case R.id.animation_group4://重复的位移动画 93 | ObjectAnimator objectAnimatorTranslate3 = ObjectAnimator.ofFloat(iv, "translationX", -50f, 50f); 94 | objectAnimatorTranslate3.setDuration(500); 95 | objectAnimatorTranslate3.setRepeatCount(3); 96 | objectAnimatorTranslate3.start(); 97 | break; 98 | case R.id.animation_frame: 99 | ObjectAnimator objectAnimatorBg = ObjectAnimator.ofInt(iv, "backgroundColor", Color.BLUE, Color.YELLOW, Color.RED); 100 | objectAnimatorBg.setDuration(3000); 101 | objectAnimatorBg.start(); 102 | break; 103 | case R.id.animation_layout: 104 | sv.setCurrentCount(7000, 1000); 105 | break; 106 | case R.id.animation_activity: 107 | parabola(); 108 | break; 109 | } 110 | 111 | } 112 | 113 | /** 114 | * 抛物线动画 115 | */ 116 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 117 | public void parabola() 118 | { 119 | 120 | ValueAnimator valueAnimator = new ValueAnimator(); 121 | valueAnimator.setDuration(3000); 122 | valueAnimator.setObjectValues(new PointF(0, 0)); 123 | valueAnimator.setInterpolator(new LinearInterpolator()); 124 | valueAnimator.setEvaluator(new TypeEvaluator() 125 | { 126 | 127 | @Override 128 | public PointF evaluate(float fraction, PointF startValue, 129 | PointF endValue) 130 | { 131 | /**x方向200px/s ,则y方向0.5 * 200 * t**/ 132 | PointF point = new PointF(); 133 | point.x = 200 * fraction * 3; 134 | point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3); 135 | return point; 136 | } 137 | }); 138 | 139 | valueAnimator.start(); 140 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 141 | { 142 | @Override 143 | public void onAnimationUpdate(ValueAnimator animation) 144 | { 145 | PointF point = (PointF) animation.getAnimatedValue(); 146 | iv.setX(point.x); 147 | iv.setY(point.y); 148 | 149 | } 150 | }); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/cn/bluemobi/dylan/animatordemo/StepArcView.java: -------------------------------------------------------------------------------- 1 | package cn.bluemobi.dylan.animatordemo; 2 | 3 | /** 4 | * Created by yuandl on 2016-11-08. 5 | */ 6 | import android.animation.ValueAnimator; 7 | import android.content.Context; 8 | import android.graphics.Canvas; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.RectF; 12 | import android.graphics.Typeface; 13 | import android.os.Build; 14 | import android.support.annotation.RequiresApi; 15 | import android.util.AttributeSet; 16 | import android.view.View; 17 | 18 | 19 | /** 20 | * Created by DylanAndroid on 2016/5/26. 21 | * 显示步数的圆弧 22 | */ 23 | public class StepArcView extends View { 24 | 25 | /** 26 | * 圆弧的宽度 27 | */ 28 | private float borderWidth = 38f; 29 | /** 30 | * 画步数的数值的字体大小 31 | */ 32 | private float numberTextSize = 0; 33 | /** 34 | * 步数 35 | */ 36 | private String stepNumber = "0"; 37 | /** 38 | * 开始绘制圆弧的角度 39 | */ 40 | private float startAngle = 135; 41 | /** 42 | * 终点对应的角度和起始点对应的角度的夹角 43 | */ 44 | private float angleLength = 270; 45 | /** 46 | * 所要绘制的当前步数的红色圆弧终点到起点的夹角 47 | */ 48 | private float currentAngleLength = 0; 49 | /** 50 | * 动画时长 51 | */ 52 | private int animationLength = 3000; 53 | 54 | public StepArcView(Context context) { 55 | super(context); 56 | 57 | 58 | } 59 | 60 | public StepArcView(Context context, AttributeSet attrs) { 61 | super(context, attrs); 62 | } 63 | 64 | public StepArcView(Context context, AttributeSet attrs, int defStyleAttr) { 65 | super(context, attrs, defStyleAttr); 66 | } 67 | 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | super.onDraw(canvas); 72 | /**中心点的x坐标*/ 73 | float centerX = (getWidth()) / 2; 74 | /**指定圆弧的外轮廓矩形区域*/ 75 | RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth); 76 | 77 | /**【第一步】绘制整体的黄色圆弧*/ 78 | drawArcYellow(canvas, rectF); 79 | /**【第二步】绘制当前进度的红色圆弧*/ 80 | drawArcRed(canvas, rectF); 81 | /**【第三步】绘制当前进度的红色数字*/ 82 | drawTextNumber(canvas, centerX); 83 | /**【第四步】绘制"步数"的红色数字*/ 84 | drawTextStepString(canvas, centerX); 85 | } 86 | 87 | /** 88 | * 1.绘制总步数的黄色圆弧 89 | * 90 | * @param canvas 画笔 91 | * @param rectF 参考的矩形 92 | */ 93 | private void drawArcYellow(Canvas canvas, RectF rectF) { 94 | Paint paint = new Paint(); 95 | /** 默认画笔颜色,黄色 */ 96 | paint.setColor(getResources().getColor(R.color.yellow)); 97 | /** 结合处为圆弧*/ 98 | paint.setStrokeJoin(Paint.Join.ROUND); 99 | /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/ 100 | paint.setStrokeCap(Paint.Cap.ROUND); 101 | /** 设置画笔的填充样式 Paint.Style.FILL :填充内部;Paint.Style.FILL_AND_STROKE :填充内部和描边; Paint.Style.STROKE :仅描边*/ 102 | paint.setStyle(Paint.Style.STROKE); 103 | /**抗锯齿功能*/ 104 | paint.setAntiAlias(true); 105 | /**设置画笔宽度*/ 106 | paint.setStrokeWidth(borderWidth); 107 | 108 | /**绘制圆弧的方法 109 | * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧, 110 | 参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧, 111 | 参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。 112 | 参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。 113 | 参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线, 114 | 参数五是Paint对象; 115 | */ 116 | canvas.drawArc(rectF, startAngle, angleLength, false, paint); 117 | 118 | } 119 | 120 | /** 121 | * 2.绘制当前步数的红色圆弧 122 | */ 123 | private void drawArcRed(Canvas canvas, RectF rectF) { 124 | Paint paintCurrent = new Paint(); 125 | paintCurrent.setStrokeJoin(Paint.Join.ROUND); 126 | paintCurrent.setStrokeCap(Paint.Cap.ROUND);//圆角弧度 127 | paintCurrent.setStyle(Paint.Style.STROKE);//设置填充样式 128 | paintCurrent.setAntiAlias(true);//抗锯齿功能 129 | paintCurrent.setStrokeWidth(borderWidth);//设置画笔宽度 130 | paintCurrent.setColor(getResources().getColor(R.color.red));//设置画笔颜色 131 | canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); 132 | } 133 | 134 | /** 135 | * 3.圆环中心的步数 136 | */ 137 | private void drawTextNumber(Canvas canvas, float centerX) { 138 | Paint vTextPaint = new Paint(); 139 | vTextPaint.setTextAlign(Paint.Align.CENTER); 140 | vTextPaint.setAntiAlias(true);//抗锯齿功能 141 | vTextPaint.setTextSize(numberTextSize); 142 | Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); 143 | vTextPaint.setTypeface(font);//字体风格 144 | vTextPaint.setColor(getResources().getColor(R.color.red)); 145 | Rect bounds_Number = new Rect(); 146 | vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); 147 | canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); 148 | 149 | } 150 | 151 | /** 152 | * 4.圆环中心[步数]的文字 153 | */ 154 | private void drawTextStepString(Canvas canvas, float centerX) { 155 | Paint vTextPaint = new Paint(); 156 | vTextPaint.setTextSize(dipToPx(16)); 157 | vTextPaint.setTextAlign(Paint.Align.CENTER); 158 | vTextPaint.setAntiAlias(true);//抗锯齿功能 159 | vTextPaint.setColor(getResources().getColor(R.color.grey)); 160 | String stepString = "步数"; 161 | Rect bounds = new Rect(); 162 | vTextPaint.getTextBounds(stepString, 0, stepString.length(), bounds); 163 | canvas.drawText(stepString, centerX, getHeight() / 2 + bounds.height() + getFontHeight(numberTextSize), vTextPaint); 164 | } 165 | 166 | /** 167 | * 获取当前步数的数字的高度 168 | * 169 | * @param fontSize 字体大小 170 | * @return 字体高度 171 | */ 172 | public int getFontHeight(float fontSize) { 173 | Paint paint = new Paint(); 174 | paint.setTextSize(fontSize); 175 | Rect bounds_Number = new Rect(); 176 | paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); 177 | return bounds_Number.height(); 178 | } 179 | 180 | /** 181 | * dip 转换成px 182 | * 183 | * @param dip 184 | * @return 185 | */ 186 | 187 | private int dipToPx(float dip) { 188 | float density = getContext().getResources().getDisplayMetrics().density; 189 | return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1)); 190 | } 191 | 192 | /** 193 | * 所走的步数进度 194 | * 195 | * @param totalStepNum 设置的步数 196 | * @param currentCounts 所走步数 197 | */ 198 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 199 | public void setCurrentCount(int totalStepNum, int currentCounts) { 200 | stepNumber = currentCounts + ""; 201 | setTextSize(currentCounts); 202 | /**如果当前走的步数超过总步数则圆弧还是270度,不能成为园*/ 203 | if (currentCounts > totalStepNum) { 204 | currentCounts = totalStepNum; 205 | } 206 | /**所走步数占用总共步数的百分比*/ 207 | float scale = (float) currentCounts / totalStepNum; 208 | /**换算成弧度最后要到达的角度的长度-->弧长*/ 209 | float currentAngleLength = scale * angleLength; 210 | /**开始执行动画*/ 211 | setAnimation(0, currentAngleLength, animationLength); 212 | } 213 | 214 | /** 215 | * 为进度设置动画 216 | * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, 217 | * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 218 | * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, 219 | * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, 220 | * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 221 | * 222 | * @param last 223 | * @param current 224 | */ 225 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 226 | private void setAnimation(float last, float current, int length) { 227 | ValueAnimator progressAnimator = ValueAnimator.ofFloat(last, current); 228 | progressAnimator.setDuration(length); 229 | progressAnimator.setTarget(currentAngleLength); 230 | progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 231 | @Override 232 | public void onAnimationUpdate(ValueAnimator animation) { 233 | currentAngleLength = (float) animation.getAnimatedValue(); 234 | invalidate(); 235 | } 236 | }); 237 | progressAnimator.start(); 238 | } 239 | 240 | /** 241 | * 设置文本大小,防止步数特别大之后放不下,将字体大小动态设置 242 | * 243 | * @param num 244 | */ 245 | public void setTextSize(int num) { 246 | String s = String.valueOf(num); 247 | int length = s.length(); 248 | if (length <= 4) { 249 | numberTextSize = dipToPx(50); 250 | } else if (length > 4 && length <= 6) { 251 | numberTextSize = dipToPx(40); 252 | } else if (length > 6 && length <= 8) { 253 | numberTextSize = dipToPx(30); 254 | } else if (length > 8) { 255 | numberTextSize = dipToPx(25); 256 | } 257 | } 258 | 259 | } 260 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 18 | 19 | 23 | 24 |