├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── AndroidRotateAnim.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── rotateanim │ │ └── example │ │ └── com │ │ └── androidrotateanim │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── rotateanim │ │ │ └── example │ │ │ └── com │ │ │ └── androidrotateanim │ │ │ ├── CircleProgressView.java │ │ │ ├── GradientArcProgressView.java │ │ │ ├── GradientArcProgressView2.java │ │ │ ├── GradientArcView.java │ │ │ ├── Rotate3dAnimation.java │ │ │ ├── Rotate3dAnimationXY.java │ │ │ ├── RotateActivity.java │ │ │ └── RoundNumProgressView.java │ └── res │ │ ├── drawable │ │ ├── test1.jpg │ │ ├── test2.jpg │ │ └── test3.jpg │ │ ├── layout │ │ ├── activity_rotate.xml │ │ └── content_rotate.xml │ │ ├── menu │ │ └── menu_rotate.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-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── rotateanim │ └── example │ └── com │ └── androidrotateanim │ └── ExampleUnitTest.java ├── build.gradle ├── docpic └── RotateAnim.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AndroidRotateAnim.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidRotateAnim 2 | Android旋转动画+Android圆形进度条组合 3 | 4 | ![旋转动画](/docpic/RotateAnim.gif) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "rotateanim.example.com.androidrotateanim" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 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 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.0' 26 | compile 'com.android.support:design:23.1.0' 27 | } 28 | -------------------------------------------------------------------------------- /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 H:\android-cmcm-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/rotateanim/example/com/androidrotateanim/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/CircleProgressView.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Matrix; 6 | import android.graphics.Paint; 7 | import android.graphics.SweepGradient; 8 | import android.util.AttributeSet; 9 | import android.util.DisplayMetrics; 10 | import android.util.TypedValue; 11 | import android.view.Surface; 12 | import android.view.SurfaceHolder; 13 | import android.view.SurfaceView; 14 | 15 | /** 16 | * Created by yzs on 2017/7/13. 17 | * 圆形进度控件,在SurfaceView中绘制 18 | */ 19 | 20 | public class CircleProgressView extends SurfaceView implements SurfaceHolder.Callback { 21 | 22 | // ================ 公共数据 ============= // 23 | /** 顶部作为计数起点, 右边是0,左边是-180或者180,底部是90 */ 24 | private static final int START_POINT_TOP = -90; 25 | private static final float TOTAL_ANGLE = 360f; 26 | private static final int CLEAR_COLOR = 0xff0583f7; 27 | 28 | /** 圆环进度的颜色 */ 29 | private int mRoundProgressColor = 0xff04d3ff; 30 | /** 背景颜色 */ 31 | private int mRoundProgressBgColor = CLEAR_COLOR; 32 | /** 圆心的x坐标 */ 33 | float mCenterX = 0; 34 | /** 圆心的y坐标 */ 35 | float mCenterY = 0; 36 | /** 定义画笔 */ 37 | private Paint mProgressPaint; 38 | /** SurfaceView的绘制线程 */ 39 | private DrawThread mDrawThread; 40 | private SurfaceHolder mSurfaceHolder; 41 | private DisplayMetrics mMetrics; 42 | /** 最大帧数 (1000 / 20) */ 43 | private static final int DRAW_INTERVAL = 20; 44 | 45 | 46 | public CircleProgressView(Context context) { 47 | super(context); 48 | init(); 49 | } 50 | 51 | public CircleProgressView(Context context, AttributeSet attrs) { 52 | super(context, attrs); 53 | init(); 54 | } 55 | 56 | private void init() { 57 | mProgressPaint = new Paint(); 58 | mProgressPaint.setAntiAlias(true); 59 | mMetrics = getResources().getDisplayMetrics(); 60 | mOuterRoundWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, mMetrics); 61 | mOuterHeadCircleWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, mMetrics); 62 | 63 | mSurfaceHolder = getHolder(); 64 | mSurfaceHolder.addCallback(this); 65 | } 66 | 67 | private void start() { 68 | if (mDrawThread == null) { 69 | mDrawThread = new DrawThread(mSurfaceHolder, getContext()); 70 | } 71 | try { 72 | if (!mDrawThread.isRunning) { 73 | mDrawThread.isRunning = true; 74 | mDrawThread.start(); 75 | } 76 | } catch (Exception ignore) {} 77 | } 78 | 79 | private void stop() { 80 | mDrawThread.isRunning = false; 81 | try { 82 | mDrawThread.join(); 83 | mDrawThread = null; 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | 89 | @Override 90 | public void surfaceCreated(SurfaceHolder holder) { 91 | start(); 92 | } 93 | 94 | @Override 95 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } 96 | 97 | @Override 98 | public void surfaceDestroyed(SurfaceHolder holder) { 99 | stop(); 100 | } 101 | 102 | class DrawThread extends Thread { 103 | SurfaceHolder surfaceHolder; 104 | Context context; 105 | boolean isRunning; 106 | 107 | public DrawThread(SurfaceHolder surfaceHolder, Context context) { 108 | this.surfaceHolder = surfaceHolder; 109 | this.context = context; 110 | } 111 | 112 | @Override 113 | public void run() { 114 | long timeStartPerDraw; 115 | long deltaTime; 116 | while (isRunning) { 117 | Canvas canvas = null; 118 | timeStartPerDraw = System.currentTimeMillis(); 119 | try { 120 | synchronized (surfaceHolder) { 121 | Surface surface = surfaceHolder.getSurface(); 122 | if (surface != null && surface.isValid()) { 123 | canvas = surfaceHolder.lockCanvas(null); 124 | } 125 | if (canvas != null) { 126 | doDraw(canvas); 127 | } 128 | } 129 | } catch (Exception e) { 130 | e.printStackTrace(); 131 | } finally { 132 | if (surfaceHolder != null && canvas != null) { 133 | try { 134 | surfaceHolder.unlockCanvasAndPost(canvas); 135 | } catch (Exception ignore) {} 136 | } 137 | } 138 | deltaTime = System.currentTimeMillis() - timeStartPerDraw; 139 | if (deltaTime < DRAW_INTERVAL) { 140 | try { 141 | // 控制帧数 142 | Thread.sleep(DRAW_INTERVAL - deltaTime); 143 | } catch (Exception e) { 144 | e.printStackTrace(); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | 152 | private void doDraw(Canvas canvas) { 153 | calculatePreValue(); 154 | canvas.drawColor(mRoundProgressBgColor); 155 | drawProgressPart(canvas); 156 | } 157 | 158 | private int mProgressAlpha = 255; 159 | private void drawProgressPart(Canvas canvas) { 160 | drawOuterGradientProgress(canvas); 161 | } 162 | 163 | private void calculatePreValue() { 164 | if (mCenterX <= 0) { 165 | mCenterX = getWidth() / 2; // 获取圆心的x坐标 166 | } 167 | if (mCenterY <= 0) { 168 | mCenterY = getHeight() / 2; 169 | } 170 | if (mOuterRadius <= 0) { 171 | mOuterRadius = getWidth() / 2 - mOuterRoundWidth; 172 | } 173 | } 174 | 175 | // ================ 外环进度数据 ============= // 176 | /** 顶部作为计数起点 270度, 计算圆上的任意点坐标时顺时针为正,右边是0 */ 177 | private static final float HEAD_CIRCLE_START_ANGLE = 270f; 178 | /** 进度条每次移动的角度 */ 179 | private static int mOuterProgressStep = 8; 180 | /** 修改这个颜色数组就会出现不一样的渐变圆弧 */ 181 | private int[] mColors = { 182 | 0x0004d3ff, 0x0004d3ff, 0x4004d3ff, 0x8004d3ff, 0xff04d3ff 183 | }; 184 | /** 外环渐变处理器 */ 185 | private SweepGradient mOuterSweepGradient; 186 | /** 外环用于旋转的矩阵 Matrix */ 187 | private Matrix mOuterMatrix = new Matrix(); 188 | /** 外圆环的宽度 */ 189 | private float mOuterRoundWidth; 190 | /** 外圆环头部的圆圈半径 */ 191 | private float mOuterHeadCircleWidth; 192 | /** 外环的半径 */ 193 | private float mOuterRadius = 0; 194 | /** 外环角度旋转总进度*/ 195 | private float mOuterAngleProgressTotal = 0; 196 | /** 外环头部圆选择角度 */ 197 | private float mOuterHeadCircleAngleTotal = 0; 198 | private double mOuterHeadCircleAngleTotalMath = 0; 199 | private void drawOuterGradientProgress(final Canvas canvas) { 200 | mProgressPaint.setStrokeWidth(mOuterRoundWidth); // 设置圆环的宽度 201 | mProgressPaint.setColor(mRoundProgressColor); // 设置进度的颜色 202 | mProgressPaint.setAlpha(mProgressAlpha); 203 | mProgressPaint.setStyle(Paint.Style.STROKE); 204 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 205 | if (mOuterSweepGradient == null) { 206 | mOuterSweepGradient = new SweepGradient(mCenterX, mCenterY, mColors, null); 207 | } 208 | mOuterMatrix.setRotate((START_POINT_TOP + mOuterAngleProgressTotal), mCenterX, mCenterY); 209 | mOuterSweepGradient.setLocalMatrix(mOuterMatrix); 210 | mProgressPaint.setShader(mOuterSweepGradient); 211 | canvas.drawCircle(mCenterX, mCenterY, mOuterRadius, mProgressPaint); // 画出圆环 212 | drawOuterArcHeadCircle(canvas); 213 | mOuterAngleProgressTotal += mOuterProgressStep; 214 | if (mOuterAngleProgressTotal > TOTAL_ANGLE) { 215 | mOuterAngleProgressTotal -= TOTAL_ANGLE; 216 | } 217 | } 218 | 219 | private void drawOuterArcHeadCircle(final Canvas canvas) { 220 | mProgressPaint.setShader(null); 221 | mProgressPaint.setStrokeWidth(0); 222 | mProgressPaint.setStyle(Paint.Style.FILL); 223 | // 一开始从顶部开始旋转 224 | mOuterHeadCircleAngleTotal = (HEAD_CIRCLE_START_ANGLE + mOuterAngleProgressTotal); 225 | if (mOuterHeadCircleAngleTotal - TOTAL_ANGLE > 0) { 226 | mOuterHeadCircleAngleTotal -= TOTAL_ANGLE; 227 | } 228 | // 根据旋转角度计算圆上当前位置点坐标,再以当前位置左边点位圆心画一个圆 229 | mOuterHeadCircleAngleTotalMath = mOuterHeadCircleAngleTotal * Math.PI / 180f; 230 | canvas.drawCircle((float) (mCenterX + mOuterRadius * Math.cos(mOuterHeadCircleAngleTotalMath)), 231 | (float) (mCenterY + mOuterRadius * Math.sin(mOuterHeadCircleAngleTotalMath)), 232 | mOuterHeadCircleWidth, mProgressPaint); 233 | } 234 | 235 | public void setProgressColors(int colors[]) { 236 | mColors = colors; 237 | mRoundProgressColor = mColors[mColors.length - 1]; 238 | } 239 | 240 | public void setRoundProgressBgColor(int roundProgressBgColor) { 241 | mRoundProgressBgColor = roundProgressBgColor; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/GradientArcProgressView.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.SweepGradient; 10 | import android.util.AttributeSet; 11 | import android.util.DisplayMetrics; 12 | import android.util.TypedValue; 13 | import android.view.View; 14 | 15 | /** 16 | * 渐变圆弧实现进度条-这里使用画整个圆的方式来实现 17 | */ 18 | public class GradientArcProgressView extends View { 19 | 20 | private static final int ROUND_COLOR_DEFAULT = 0x33ffffff; 21 | private static final int ROUND_WIDTH_DEFAULT = 2; //dp 22 | private static final int ROUND_PROGRESS_COLOR_DEFAULT = 0xffffffff; 23 | 24 | /** 进度条每次移动的角度 */ 25 | private static final int FLICKER_PROGRESS_STEP = 10; 26 | /** 顶部作为计数起点, 右边是0,左边是-180或者180,底部是90 */ 27 | private static final int START_POINT_TOP = -90; 28 | 29 | /** 定义一支画笔 */ 30 | private Paint mPaint; 31 | private Paint mGradientArcPaint; 32 | private SweepGradient mSweepGradient; 33 | /** 修改这个颜色数组就会出现不一样的渐变圆弧 */ 34 | private int[] mColors = { 35 | 0x00ffffff, 0x40ffffff, 0x80ffffff, Color.WHITE 36 | }; 37 | private Matrix mMatrix = new Matrix(); 38 | /** 圆环的颜色 */ 39 | private int mRoundColor; 40 | /** 圆环的宽度 */ 41 | private float mRoundWidth; 42 | /** 圆环进度的颜色 */ 43 | private int mRoundProgressColor; 44 | 45 | /** 进度的风格,实心(FILL)或者空心(STROKE) */ 46 | private int mStyle; 47 | public static final int STROKE = 0; 48 | public static final int FILL = 1; 49 | 50 | /** 进度旋转方向,顺时针(CLOCKWISE)或者逆时针(COUNTERCLOCKWISE) */ 51 | private int mRotateOrientation; 52 | public static final int COUNTERCLOCKWISE = 0; 53 | public static final int CLOCKWISE = 1; 54 | 55 | private int mFlickerProgressTotal = 0; 56 | 57 | /** 闪烁进度条动画正在进行 */ 58 | private boolean isFlickerProgressWorking = false; 59 | 60 | public GradientArcProgressView(Context context) { 61 | this(context, null); 62 | } 63 | 64 | public GradientArcProgressView(Context context, AttributeSet attrs) { 65 | this(context, attrs, 0); 66 | } 67 | 68 | public GradientArcProgressView(Context context, AttributeSet attrs, int defStyle) { 69 | super(context, attrs, defStyle); 70 | mPaint = new Paint(); 71 | mGradientArcPaint = new Paint(); 72 | //获取自定义属性和默认值 73 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundNumProgressView); 74 | mRoundColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundColor, ROUND_COLOR_DEFAULT); 75 | mRoundProgressColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundProgressColor, ROUND_PROGRESS_COLOR_DEFAULT); 76 | 77 | final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 78 | mRoundWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ROUND_WIDTH_DEFAULT, metrics); 79 | mStyle = mTypedArray.getInt(R.styleable.RoundNumProgressView_style, STROKE); 80 | mRotateOrientation = CLOCKWISE; 81 | mTypedArray.recycle(); 82 | } 83 | 84 | int centerX = 0; // 获取圆心的x坐标 85 | int radius =0; // 圆环的半径 86 | @Override 87 | protected void onDraw(Canvas canvas) { 88 | super.onDraw(canvas); 89 | if (centerX == 0) { 90 | centerX = getWidth() / 2; // 获取圆心的x坐标 91 | } 92 | if (radius == 0) { 93 | radius = (int) (centerX - mRoundWidth / 2); // 圆环的半径 94 | } 95 | // 画最外层的大圆环 96 | drawOuterCircle(canvas, centerX, radius); 97 | // 画闪烁的进度条动画 98 | if (isFlickerProgressWorking) { 99 | drawFlickerArcProgress(canvas, centerX, radius); 100 | } else { 101 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 102 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 103 | mGradientArcPaint.setAntiAlias(true); 104 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 105 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 106 | if (mSweepGradient == null) 107 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 108 | mMatrix.setRotate(START_POINT_TOP, centerX, centerX); 109 | mSweepGradient.setLocalMatrix(mMatrix); 110 | mGradientArcPaint.setShader(mSweepGradient); 111 | canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 112 | } 113 | } 114 | 115 | private void drawOuterCircle(final Canvas canvas, final int centerX, final int radius) { 116 | mPaint.setColor(mRoundColor); // 设置圆环的颜色 117 | mPaint.setStyle(Paint.Style.STROKE); // 设置空心 118 | mPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 119 | mPaint.setAntiAlias(true); // 消除锯齿 120 | canvas.drawCircle(centerX, centerX, radius, mPaint); // 画出圆环 121 | } 122 | 123 | private void drawFlickerArcProgress(final Canvas canvas, final int centerX, final int radius) { 124 | drawGradientCircle(canvas, centerX, radius); 125 | post(new Runnable() { 126 | @Override 127 | public void run() { 128 | mFlickerProgressTotal += FLICKER_PROGRESS_STEP; 129 | postInvalidate(); 130 | } 131 | }); 132 | } 133 | 134 | private void drawGradientCircle(final Canvas canvas, final int centerX, final int radius) { 135 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 136 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 137 | mGradientArcPaint.setAntiAlias(true); 138 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 139 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 140 | if (mSweepGradient == null) { 141 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 142 | } 143 | if (mRotateOrientation == COUNTERCLOCKWISE) { // 计数起点在顶部,所以为-90 144 | mMatrix.setRotate((START_POINT_TOP - mFlickerProgressTotal) % 360, centerX, centerX); 145 | } else if (mRotateOrientation == CLOCKWISE) { 146 | mMatrix.setRotate((START_POINT_TOP + mFlickerProgressTotal) % 360, centerX, centerX); 147 | } 148 | mSweepGradient.setLocalMatrix(mMatrix); 149 | mGradientArcPaint.setShader(mSweepGradient); 150 | canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 151 | } 152 | 153 | /*** 154 | * 启动闪烁动画,点击的时候调用会起到很好的提示作用 155 | */ 156 | public void startFlickerArcProgress(final Runnable endFlagRunnable) { 157 | if (isFlickerProgressWorking) 158 | return; 159 | isFlickerProgressWorking = true; 160 | postInvalidate(); 161 | } 162 | 163 | public void setStyle(int style) { 164 | mStyle = style; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/GradientArcProgressView2.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.RectF; 10 | import android.graphics.SweepGradient; 11 | import android.util.AttributeSet; 12 | import android.util.DisplayMetrics; 13 | import android.util.TypedValue; 14 | import android.view.View; 15 | 16 | /** 17 | * 渐变圆弧实现进度条-这个才用画圆弧的方式来实现 18 | */ 19 | public class GradientArcProgressView2 extends View { 20 | 21 | private static final int ROUND_COLOR_DEFAULT = 0x33ffffff; 22 | private static final int ROUND_WIDTH_DEFAULT = 2; //dp 23 | private static final int ROUND_PROGRESS_COLOR_DEFAULT = 0xffffffff; 24 | 25 | /** 闪烁进度条每次移动的角度 */ 26 | private static final int FLICKER_PROGRESS_STEP = 10; 27 | /** 顶部作为计数起点, 右边是0,左边是-180或者180,底部是90 */ 28 | private static final int START_POINT_TOP = -90; 29 | 30 | /** 弧长及方向 */ 31 | private static final int ARC_LENGTH = -180; 32 | 33 | /** 定义一支画笔 */ 34 | private Paint mPaint; 35 | private Paint mGradientArcPaint; 36 | private SweepGradient mSweepGradient; 37 | /** 修改这个颜色数组就会出现不一样的渐变圆弧 */ 38 | private int[] mColors = { 39 | 0x00ffffff, 0x40ffffff, 0x80ffffff, Color.WHITE 40 | }; 41 | private Matrix mMatrix = new Matrix(); 42 | /** 圆环的颜色 */ 43 | private int mRoundColor; 44 | /** 圆环的宽度 */ 45 | private float mRoundWidth; 46 | /** 圆环进度的颜色 */ 47 | private int mRoundProgressColor; 48 | /** 用于定义的圆弧的形状和大小的界限 */ 49 | private RectF mArcLimitRect = new RectF(); 50 | 51 | /** 进度的风格,实心(FILL)或者空心(STROKE) */ 52 | private int mStyle; 53 | public static final int STROKE = 0; 54 | public static final int FILL = 1; 55 | 56 | /** 进度旋转方向,顺时针(CLOCKWISE)或者逆时针(COUNTERCLOCKWISE) */ 57 | private int mRotateOrientation; 58 | public static final int COUNTERCLOCKWISE = 0; 59 | public static final int CLOCKWISE = 1; 60 | 61 | private int mFlickerProgressTotal = 0; 62 | 63 | /** 闪烁进度条动画正在进行 */ 64 | private boolean isFlickerProgressWorking = false; 65 | 66 | public GradientArcProgressView2(Context context) { 67 | this(context, null); 68 | } 69 | 70 | public GradientArcProgressView2(Context context, AttributeSet attrs) { 71 | this(context, attrs, 0); 72 | } 73 | 74 | public GradientArcProgressView2(Context context, AttributeSet attrs, int defStyle) { 75 | super(context, attrs, defStyle); 76 | mPaint = new Paint(); 77 | mGradientArcPaint = new Paint(); 78 | //获取自定义属性和默认值 79 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundNumProgressView); 80 | mRoundColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundColor, ROUND_COLOR_DEFAULT); 81 | mRoundProgressColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundProgressColor, ROUND_PROGRESS_COLOR_DEFAULT); 82 | 83 | final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 84 | mRoundWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ROUND_WIDTH_DEFAULT, metrics); 85 | mStyle = mTypedArray.getInt(R.styleable.RoundNumProgressView_style, STROKE); 86 | mRotateOrientation = CLOCKWISE; 87 | mTypedArray.recycle(); 88 | } 89 | 90 | int centerX = 0; // 获取圆心的x坐标 91 | int radius =0; // 圆环的半径 92 | @Override 93 | protected void onDraw(Canvas canvas) { 94 | super.onDraw(canvas); 95 | if (centerX == 0) { 96 | centerX = getWidth() / 2; // 获取圆心的x坐标 97 | } 98 | if (radius == 0) { 99 | radius = (int) (centerX - mRoundWidth / 2); // 圆环的半径 100 | } 101 | // 画最外层的大圆环 102 | drawOuterCircle(canvas, centerX, radius); 103 | // 画闪烁的进度条动画 104 | if (isFlickerProgressWorking) { 105 | drawFlickerArcProgress(canvas, centerX, radius); 106 | } else { 107 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 108 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 109 | mGradientArcPaint.setAntiAlias(true); 110 | if (mArcLimitRect.isEmpty()) { 111 | mArcLimitRect.set(centerX - radius, centerX - radius, centerX + radius, centerX + radius); 112 | } 113 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 114 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 115 | if (mSweepGradient == null) 116 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 117 | mMatrix.setRotate(START_POINT_TOP, centerX, centerX); 118 | mSweepGradient.setLocalMatrix(mMatrix); 119 | mGradientArcPaint.setShader(mSweepGradient); 120 | canvas.drawArc(mArcLimitRect, START_POINT_TOP, ARC_LENGTH, false, mGradientArcPaint); 121 | // canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 122 | } 123 | } 124 | 125 | private void drawOuterCircle(final Canvas canvas, final int centerX, final int radius) { 126 | mPaint.setColor(mRoundColor); // 设置圆环的颜色 127 | mPaint.setStyle(Paint.Style.STROKE); // 设置空心 128 | mPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 129 | mPaint.setAntiAlias(true); // 消除锯齿 130 | canvas.drawCircle(centerX, centerX, radius, mPaint); // 画出圆环 131 | } 132 | 133 | private void drawFlickerArcProgress(final Canvas canvas, final int centerX, final int radius) { 134 | drawGradientCircle(canvas, centerX, radius); 135 | post(new Runnable() { 136 | @Override 137 | public void run() { 138 | mFlickerProgressTotal += FLICKER_PROGRESS_STEP; 139 | postInvalidate(); 140 | } 141 | }); 142 | } 143 | 144 | private void drawGradientCircle(final Canvas canvas, final int centerX, final int radius) { 145 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 146 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 147 | mGradientArcPaint.setAntiAlias(true); 148 | if (mArcLimitRect.isEmpty()) { 149 | mArcLimitRect.set(centerX - radius, centerX - radius, centerX + radius, centerX + radius); 150 | } 151 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 152 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 153 | if (mSweepGradient == null) { 154 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 155 | } 156 | if (mRotateOrientation == COUNTERCLOCKWISE) { // 计数起点在顶部,所以为-90 157 | mMatrix.setRotate((START_POINT_TOP - mFlickerProgressTotal) % 360, centerX, centerX); 158 | } else if (mRotateOrientation == CLOCKWISE) { 159 | mMatrix.setRotate((START_POINT_TOP + mFlickerProgressTotal) % 360, centerX, centerX); 160 | } 161 | mSweepGradient.setLocalMatrix(mMatrix); 162 | mGradientArcPaint.setShader(mSweepGradient); 163 | canvas.drawArc(mArcLimitRect, (START_POINT_TOP + mFlickerProgressTotal) % 360, ARC_LENGTH, false, mGradientArcPaint); 164 | // canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 165 | } 166 | 167 | /*** 168 | * 启动闪烁动画,点击的时候调用会起到很好的提示作用 169 | */ 170 | public void startFlickerArcProgress(final Runnable endFlagRunnable) { 171 | if (isFlickerProgressWorking) 172 | return; 173 | isFlickerProgressWorking = true; 174 | postInvalidate(); 175 | } 176 | 177 | public void setStyle(int style) { 178 | mStyle = style; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/GradientArcView.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.SweepGradient; 10 | import android.util.AttributeSet; 11 | import android.util.DisplayMetrics; 12 | import android.util.TypedValue; 13 | import android.view.View; 14 | 15 | import java.util.Iterator; 16 | import java.util.Vector; 17 | 18 | /** 19 | * 渐变圆弧 20 | */ 21 | public class GradientArcView extends View { 22 | 23 | private static final int ROUND_COLOR_DEFAULT = 0x33ffffff; 24 | private static final int ROUND_WIDTH_DEFAULT = 2; //dp 25 | private static final int ROUND_PROGRESS_COLOR_DEFAULT = 0xffffffff; 26 | 27 | /** 闪烁进度条每次移动的角度 */ 28 | private static final int FLICKER_PRORESS_STEP = 40; 29 | /** 顶部作为计数起点, 右边是0,左边是-180或者180,底部是90 */ 30 | private static final int START_POINT_TOP = -90; 31 | 32 | /** 定义一支画笔 */ 33 | private Paint mPaint; 34 | private Paint mGradientArcPaint; 35 | private SweepGradient mSweepGradient; 36 | private int[] mColors = { 37 | 0x00ffffff, 0x40ffffff, 0x80ffffff, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, 0x80ffffff, 0x40ffffff, 0x00ffffff 38 | }; 39 | private Matrix mMatrix = new Matrix(); 40 | /** 圆环的颜色 */ 41 | private int mRoundColor; 42 | /** 圆环的宽度 */ 43 | private float mRoundWidth; 44 | /** 圆环进度的颜色 */ 45 | private int mRoundProgressColor; 46 | 47 | /** 进度旋转方向,顺时针(CLOCKWISE)或者逆时针(COUNTERCLOCKWISE) */ 48 | private int mRotateOrientation; 49 | public static final int COUNTERCLOCKWISE = 0; 50 | public static final int CLOCKWISE = 1; 51 | 52 | /** 闪烁(循环旋转)进度条的时间 */ 53 | private int mFlickerProgressTime = 1000; 54 | private int mFlickerProgressTotal = 0; 55 | 56 | /** 闪烁进度条动画正在进行 */ 57 | private boolean isFlickerProgressWorking = false; 58 | 59 | private Vector mRoundNumProgressListeners; 60 | 61 | public interface IRoundNumProgressListener { 62 | void onFlickerProgressEnd(); 63 | } 64 | 65 | public GradientArcView(Context context) { 66 | this(context, null); 67 | } 68 | 69 | public GradientArcView(Context context, AttributeSet attrs) { 70 | this(context, attrs, 0); 71 | } 72 | 73 | public GradientArcView(Context context, AttributeSet attrs, int defStyle) { 74 | super(context, attrs, defStyle); 75 | mPaint = new Paint(); 76 | mGradientArcPaint = new Paint(); 77 | //获取自定义属性和默认值 78 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundNumProgressView); 79 | mRoundColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundColor, ROUND_COLOR_DEFAULT); 80 | mRoundProgressColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundProgressColor, ROUND_PROGRESS_COLOR_DEFAULT); 81 | 82 | final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 83 | mRoundWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ROUND_WIDTH_DEFAULT, metrics); 84 | mRotateOrientation = CLOCKWISE; 85 | mTypedArray.recycle(); 86 | } 87 | 88 | public void addRoundNumProgressListener(IRoundNumProgressListener roundNumProgressListener) { 89 | if (roundNumProgressListener == null) 90 | return; 91 | if (mRoundNumProgressListeners == null) { 92 | mRoundNumProgressListeners = new Vector(); 93 | } 94 | mRoundNumProgressListeners.add(roundNumProgressListener); 95 | } 96 | 97 | private void notifyAllRoundNumProressListeners() { 98 | if (mRoundNumProgressListeners == null) 99 | return; 100 | Iterator iterator = mRoundNumProgressListeners.iterator(); 101 | while (iterator.hasNext()) { 102 | iterator.next().onFlickerProgressEnd(); 103 | } 104 | } 105 | 106 | int centerX = 0; // 获取圆心的x坐标 107 | int radius =0; // 圆环的半径 108 | @Override 109 | protected void onDraw(Canvas canvas) { 110 | super.onDraw(canvas); 111 | if (centerX == 0) { 112 | centerX = getWidth() / 2; // 获取圆心的x坐标 113 | } 114 | if (radius == 0) { 115 | radius = (int) (centerX - mRoundWidth / 2); // 圆环的半径 116 | } 117 | // 画最外层的大圆环 118 | drawOuterCircle(canvas, centerX, radius); 119 | // 画闪烁的进度条动画 120 | if (isFlickerProgressWorking) { 121 | drawFlickerArcProgress(canvas, centerX, radius); 122 | } else { 123 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 124 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 125 | mGradientArcPaint.setAntiAlias(true); 126 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 127 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 128 | if (mSweepGradient == null) 129 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 130 | mMatrix.setRotate(START_POINT_TOP, centerX, centerX); 131 | mSweepGradient.setLocalMatrix(mMatrix); 132 | mGradientArcPaint.setShader(mSweepGradient); 133 | canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 134 | } 135 | } 136 | 137 | private void drawOuterCircle(final Canvas canvas, final int centerX, final int radius) { 138 | mPaint.setColor(mRoundColor); // 设置圆环的颜色 139 | mPaint.setStyle(Paint.Style.STROKE); // 设置空心 140 | mPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 141 | mPaint.setAntiAlias(true); // 消除锯齿 142 | canvas.drawCircle(centerX, centerX, radius, mPaint); // 画出圆环 143 | } 144 | 145 | private void drawFlickerArcProgress(final Canvas canvas, final int centerX, final int radius) { 146 | drawGradientCircle(canvas, centerX, radius); 147 | post(new Runnable() { 148 | @Override 149 | public void run() { 150 | mFlickerProgressTotal += FLICKER_PRORESS_STEP; 151 | postInvalidate(); 152 | } 153 | }); 154 | } 155 | 156 | private void drawGradientCircle(final Canvas canvas, final int centerX, final int radius) { 157 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 158 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 159 | mGradientArcPaint.setAntiAlias(true); 160 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 161 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 162 | if (mSweepGradient == null) { 163 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 164 | } 165 | if (mRotateOrientation == COUNTERCLOCKWISE) { // 计数起点在顶部,所以为-90 166 | mMatrix.setRotate(START_POINT_TOP - mFlickerProgressTotal, centerX, centerX); 167 | } else if (mRotateOrientation == CLOCKWISE) { 168 | mMatrix.setRotate(START_POINT_TOP + mFlickerProgressTotal, centerX, centerX); 169 | } 170 | mSweepGradient.setLocalMatrix(mMatrix); 171 | mGradientArcPaint.setShader(mSweepGradient); 172 | canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 173 | } 174 | 175 | /*** 176 | * 启动闪烁动画,点击的时候调用会起到很好的提示作用 177 | */ 178 | public void startFlickerArcProgress(final Runnable endFlagRunnable) { 179 | if (isFlickerProgressWorking) 180 | return; 181 | isFlickerProgressWorking = true; 182 | postInvalidate(); 183 | postDelayed(new Runnable() { 184 | @Override 185 | public void run() { 186 | isFlickerProgressWorking = false; 187 | postInvalidate(); 188 | if (endFlagRunnable != null) { 189 | endFlagRunnable.run(); 190 | } 191 | notifyAllRoundNumProressListeners(); 192 | } 193 | }, mFlickerProgressTime); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/Rotate3dAnimation.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.view.animation.Animation; 4 | import android.view.animation.Transformation; 5 | import android.graphics.Camera; 6 | import android.graphics.Matrix; 7 | 8 | /** 9 | * An animation that rotates the view on the X,Y,Z axis between two specified angles. 10 | * This animation also adds a translation on the Z axis (depth) to improve the effect. 11 | */ 12 | public class Rotate3dAnimation extends Animation { 13 | public static final Byte ROTATE_X_AXIS = 0x00; 14 | public static final Byte ROTATE_Y_AXIS = 0x01; 15 | public static final Byte ROTATE_Z_AXIS = 0x02; 16 | private final float mFromDegrees; 17 | private final float mToDegrees; 18 | private final float mCenterX; 19 | private final float mCenterY; 20 | private final float mDepthZ; 21 | private final boolean mReverse; 22 | private Camera mCamera; 23 | private Byte mRotateAxis; // 0:X轴 1:Y轴 2:Z轴 24 | 25 | /**创建3D旋转动画 26 | * @param fromDegrees the start angle of the 3D rotation 27 | * @param toDegrees the end angle of the 3D rotation 28 | * @param centerX the X center of the 3D rotation 29 | * @param centerY the Y center of the 3D rotation 30 | * @param depthZ the Z depth of the 3D rotation 31 | * @param rotateAxis the rotate axis of the 3D rotation 32 | * @param reverse true if the translation should be reversed, false otherwise 33 | */ 34 | public Rotate3dAnimation(float fromDegrees, float toDegrees, 35 | float centerX, float centerY, float depthZ, Byte rotateAxis, boolean reverse) { 36 | mFromDegrees = fromDegrees; 37 | mToDegrees = toDegrees; 38 | mCenterX = centerX; 39 | mCenterY = centerY; 40 | mDepthZ = depthZ; 41 | mRotateAxis = rotateAxis; 42 | mReverse = reverse; 43 | } 44 | 45 | @Override 46 | public void initialize(int width, int height, int parentWidth, int parentHeight) { 47 | super.initialize(width, height, parentWidth, parentHeight); 48 | mCamera = new Camera(); 49 | } 50 | 51 | @Override 52 | protected void applyTransformation(float interpolatedTime, Transformation t) { 53 | final float fromDegrees = mFromDegrees; 54 | float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); 55 | 56 | final float centerX = mCenterX; 57 | final float centerY = mCenterY; 58 | final Camera camera = mCamera; 59 | 60 | final Matrix matrix = t.getMatrix(); 61 | // 将当前的摄像头位置保存下来,以便变换进行完成后恢复成原位 62 | camera.save(); 63 | if (mReverse) { 64 | // z的偏移会越来越大。这就会形成这样一个效果,view从近到远 65 | camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); 66 | } else { 67 | // z的偏移会越来越小。这就会形成这样一个效果,我们的View从一个很远的地方向我们移过来,越来越近,最终移到了我们的窗口上面 68 | camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); 69 | } 70 | // 是给我们的View加上旋转效果,在移动的过程中,视图还会以XYZ轴为中心进行旋转。 71 | if (ROTATE_X_AXIS.equals(mRotateAxis)) { 72 | camera.rotateX(degrees); 73 | } else if (ROTATE_Y_AXIS.equals(mRotateAxis)) { 74 | camera.rotateY(degrees); 75 | } else { 76 | camera.rotateZ(degrees); 77 | } 78 | 79 | // 这个是将我们刚才定义的一系列变换应用到变换矩阵上面,调用完这句之后,我们就可以将camera的位置恢复了,以便下一次再使用。 80 | camera.getMatrix(matrix); 81 | // camera位置恢复 82 | camera.restore(); 83 | 84 | // 下面两句是为了动画是以View中心为旋转点 85 | matrix.preTranslate(-centerX, -centerY); 86 | matrix.postTranslate(centerX, centerY); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/Rotate3dAnimationXY.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package rotateanim.example.com.androidrotateanim; 18 | 19 | import android.view.animation.Animation; 20 | import android.view.animation.Transformation; 21 | import android.graphics.Camera; 22 | import android.graphics.Matrix; 23 | 24 | /** 25 | * An animation that rotates the view on the Y axis between two specified angles. 26 | * This animation also adds a translation on the Z axis (depth) to improve the effect. 27 | */ 28 | public class Rotate3dAnimationXY extends Animation { 29 | private final float mFromDegrees; 30 | private final float mToDegrees; 31 | private final float mCenterX; 32 | private final float mCenterY; 33 | private final float mDepthZ; 34 | private final boolean mReverse; 35 | private Camera mCamera; 36 | private byte mRotateXOrY = 0; 37 | 38 | /** 39 | * Creates a new 3D rotation on the Y axis. The rotation is defined by its 40 | * start angle and its end angle. Both angles are in degrees. The rotation 41 | * is performed around a center point on the 2D space, definied by a pair 42 | * of X and Y coordinates, called centerX and centerY. When the animation 43 | * starts, a translation on the Z axis (depth) is performed. The length 44 | * of the translation can be specified, as well as whether the translation 45 | * should be reversed in time. 46 | * 47 | * @param fromDegrees the start angle of the 3D rotation 48 | * @param toDegrees the end angle of the 3D rotation 49 | * @param centerX the X center of the 3D rotation 50 | * @param centerY the Y center of the 3D rotation 51 | * @param reverse true if the translation should be reversed, false otherwise 52 | */ 53 | public Rotate3dAnimationXY(float fromDegrees, float toDegrees, 54 | float centerX, float centerY, float depthZ, boolean reverse , byte rotate) { 55 | mFromDegrees = fromDegrees; 56 | mToDegrees = toDegrees; 57 | mCenterX = centerX; 58 | mCenterY = centerY; 59 | mDepthZ = depthZ; 60 | mReverse = reverse; 61 | mRotateXOrY = rotate; 62 | } 63 | 64 | @Override 65 | public void initialize(int width, int height, int parentWidth, int parentHeight) { 66 | super.initialize(width, height, parentWidth, parentHeight); 67 | mCamera = new Camera(); 68 | } 69 | 70 | @Override 71 | protected void applyTransformation(float interpolatedTime, Transformation t) { 72 | final float fromDegrees = mFromDegrees; 73 | float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); 74 | 75 | final float centerX = mCenterX; 76 | final float centerY = mCenterY; 77 | final Camera camera = mCamera; 78 | 79 | final Matrix matrix = t.getMatrix(); 80 | // 将当前的摄像头位置保存下来,以便变换进行完成后恢复成原位 81 | camera.save(); 82 | 83 | // camera.translate,这个方法接受3个参数,分别是x,y,z三个轴的偏移量,我们这里只将z轴进行了偏移 84 | if (mReverse) { 85 | // z的偏移会越来越大。这就会形成这样一个效果,view从近到远 86 | camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); 87 | } else { 88 | // z的偏移会越来越小。这就会形成这样一个效果,我们的View从一个很远的地方向我们移过来,越来越近,最终移到了我们的窗口上面 89 | camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); 90 | } 91 | 92 | switch (mRotateXOrY) { 93 | case 0: 94 | // 是给我们的View加上旋转效果,在移动的过程中,视图还会移X轴为中心进行旋转。 95 | camera.rotateX(degrees); 96 | break; 97 | 98 | case 1: 99 | // 是给我们的View加上旋转效果,在移动的过程中,视图还会移X轴为中心进行旋转。 100 | camera.rotateY(degrees); 101 | break; 102 | } 103 | 104 | // 这个是将我们刚才定义的一系列变换应用到变换矩阵上面,调用完这句之后,我们就可以将camera的位置恢复了,以便下一次再使用。 105 | camera.getMatrix(matrix); 106 | // camera位置恢复 107 | camera.restore(); 108 | 109 | float fAlphaRate = 0.2f; 110 | // 改变翻转时的透明度 111 | if (!mReverse) 112 | t.setAlpha(fAlphaRate + (1.0f-fAlphaRate)* interpolatedTime ); 113 | else 114 | t.setAlpha(1.0f - fAlphaRate* interpolatedTime ); 115 | 116 | // 以View的中心点为旋转中心,如果不加这两句,就是以(0,0)点为旋转中心 117 | matrix.preTranslate(-centerX, -centerY); 118 | matrix.postTranslate(centerX, centerY); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/RotateActivity.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.util.DisplayMetrics; 11 | import android.util.TypedValue; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.Button; 17 | import android.widget.ImageView; 18 | import android.widget.LinearLayout; 19 | 20 | public class RotateActivity extends AppCompatActivity { 21 | 22 | private ImageView mRotateImgv; 23 | private Button mSwitchAnimBtn1; 24 | private Button mSwitchAnimBtn2; 25 | private Button mSwitchAnimBtn3; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_rotate); 31 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 32 | setSupportActionBar(toolbar); 33 | 34 | // FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 35 | // fab.setOnClickListener(new View.OnClickListener() { 36 | // @Override 37 | // public void onClick(View view) { 38 | // Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 39 | // .setAction("Action", null).show(); 40 | // } 41 | // }); 42 | 43 | initViewAndListener(); 44 | } 45 | 46 | private void initViewAndListener() { 47 | mRotateImgv = (ImageView) findViewById(R.id.rotateview); 48 | mSwitchAnimBtn1 = (Button) findViewById(R.id.rotateanim_btn1); 49 | mSwitchAnimBtn2 = (Button) findViewById(R.id.rotateanim_btn2); 50 | mSwitchAnimBtn3 = (Button) findViewById(R.id.rotateanim_btn3); 51 | mSwitchAnimBtn1.setOnClickListener(new View.OnClickListener() { 52 | @Override 53 | public void onClick(View v) { 54 | rotateAnimHorizon(); 55 | } 56 | }); 57 | mSwitchAnimBtn2.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | rotateOnXCoordinate(); 61 | } 62 | }); 63 | mSwitchAnimBtn3.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | rotateOnYCoordinate(); 67 | } 68 | }); 69 | 70 | initRoundNumProgressViews(); 71 | } 72 | 73 | private int mCurPic = 0; 74 | private Runnable mEndFlagRunnable = new Runnable() { 75 | @Override 76 | public void run() { 77 | mRotateImgv.postDelayed(new Runnable() { 78 | @Override 79 | public void run() { 80 | mCurPic = mCurPic % 3; 81 | switch (mCurPic) { 82 | case 0: 83 | mRotateImgv.setImageResource(R.drawable.test1); 84 | break; 85 | case 1: 86 | mRotateImgv.setImageResource(R.drawable.test2); 87 | break; 88 | case 2: 89 | mRotateImgv.setImageResource(R.drawable.test3); 90 | break; 91 | } 92 | mCurPic ++; 93 | } 94 | }, 50); 95 | } 96 | }; 97 | 98 | private void initRoundNumProgressViews() { 99 | DisplayMetrics metrics = getResources().getDisplayMetrics(); 100 | final int numProgressView1Size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, metrics); 101 | final int numProgressView2Size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, metrics); 102 | 103 | final RoundNumProgressView numProgressView = (RoundNumProgressView) findViewById(R.id.roundnumprogressview); 104 | numProgressView.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | numProgressView.setCurProgress(10); 108 | numProgressView.startFlickerArcProgress(mEndFlagRunnable); 109 | } 110 | }); 111 | LinearLayout parent = (LinearLayout) findViewById(R.id.proress_parent); 112 | final RoundNumProgressView numProgressView1 = new RoundNumProgressView(getBaseContext()); 113 | numProgressView1.setLayoutParams(new ViewGroup.LayoutParams(numProgressView1Size, numProgressView1Size)); 114 | parent.addView(numProgressView1); 115 | ((LinearLayout.LayoutParams) numProgressView1.getLayoutParams()).leftMargin = 30; 116 | numProgressView1.setOnClickListener(new View.OnClickListener() { 117 | @Override 118 | public void onClick(View v) { 119 | numProgressView1.setCurProgress(50); 120 | numProgressView1.reRefreshToOriginProgress(); 121 | } 122 | }); 123 | final RoundNumProgressView numProgressView2 = new RoundNumProgressView(getBaseContext()); 124 | numProgressView2.setLayoutParams(new ViewGroup.LayoutParams(numProgressView2Size, numProgressView2Size)); 125 | parent.addView(numProgressView2); 126 | ((LinearLayout.LayoutParams) numProgressView2.getLayoutParams()).leftMargin = 30; 127 | numProgressView2.setOnClickListener(new View.OnClickListener() { 128 | @Override 129 | public void onClick(View v) { 130 | numProgressView2.setCurProgress(0); 131 | numProgressView2.startFlickerArcProgress(mEndFlagRunnable); 132 | } 133 | }); 134 | final GradientArcView gradientArcView = new GradientArcView(getBaseContext()); 135 | gradientArcView.setLayoutParams(new ViewGroup.LayoutParams(numProgressView2Size, numProgressView2Size)); 136 | parent.addView(gradientArcView); 137 | ((LinearLayout.LayoutParams) gradientArcView.getLayoutParams()).leftMargin = 30; 138 | gradientArcView.setOnClickListener(new View.OnClickListener() { 139 | @Override 140 | public void onClick(View v) { 141 | gradientArcView.startFlickerArcProgress(mEndFlagRunnable); 142 | } 143 | }); 144 | 145 | final GradientArcProgressView gradientArcProgressView = new GradientArcProgressView(getBaseContext()); 146 | gradientArcProgressView.setLayoutParams(new ViewGroup.LayoutParams(numProgressView2Size, numProgressView2Size)); 147 | parent.addView(gradientArcProgressView); 148 | ((LinearLayout.LayoutParams) gradientArcProgressView.getLayoutParams()).leftMargin = 30; 149 | gradientArcProgressView.setOnClickListener(new View.OnClickListener() { 150 | @Override 151 | public void onClick(View v) { 152 | gradientArcProgressView.startFlickerArcProgress(mEndFlagRunnable); 153 | } 154 | }); 155 | 156 | final GradientArcProgressView2 gradientArcProgressView2 = new GradientArcProgressView2(getBaseContext()); 157 | gradientArcProgressView2.setLayoutParams(new ViewGroup.LayoutParams(numProgressView2Size, numProgressView2Size)); 158 | parent.addView(gradientArcProgressView2); 159 | ((LinearLayout.LayoutParams) gradientArcProgressView2.getLayoutParams()).leftMargin = 30; 160 | gradientArcProgressView2.setOnClickListener(new View.OnClickListener() { 161 | @Override 162 | public void onClick(View v) { 163 | gradientArcProgressView2.startFlickerArcProgress(mEndFlagRunnable); 164 | } 165 | }); 166 | 167 | final CircleProgressView circleProgressView = new CircleProgressView(getBaseContext()); 168 | circleProgressView.setRoundProgressBgColor(0xff424045); 169 | circleProgressView.setRoundProgressBgColor(Color.WHITE); 170 | circleProgressView.setProgressColors(new int[] { 0x00535353, 0xff535353}); 171 | circleProgressView.setLayoutParams(new ViewGroup.LayoutParams(100, 100)); 172 | parent.addView(circleProgressView); 173 | ((LinearLayout.LayoutParams) circleProgressView.getLayoutParams()).leftMargin = 30; 174 | } 175 | 176 | private void rotateOnXCoordinate() { 177 | float centerX = mRotateImgv.getWidth() / 2.0f; 178 | float centerY = mRotateImgv.getHeight() / 2.0f; 179 | float depthZ = 0f; 180 | Rotate3dAnimation rotate3dAnimationX = new Rotate3dAnimation(0, 180, centerX, centerY, depthZ, Rotate3dAnimation.ROTATE_X_AXIS, true); 181 | rotate3dAnimationX.setDuration(1000); 182 | mRotateImgv.startAnimation(rotate3dAnimationX); 183 | 184 | // 下面的代码,旋转的时候可以带透明度 185 | // float centerX = mRotateImgv.getWidth() / 2.0f; 186 | // float centerY = mRotateImgv.getHeight() / 2.0f; 187 | // float depthZ = 0f; 188 | // Rotate3dAnimationXY rotate3dAnimationX = new Rotate3dAnimationXY(0, 180, centerX, centerY, depthZ, true, (byte) 0); 189 | // rotate3dAnimationX.setDuration(1000); 190 | // mRotateImgv.startAnimation(rotate3dAnimationX); 191 | } 192 | 193 | private void rotateOnYCoordinate() { 194 | float centerX = mRotateImgv.getWidth() / 2.0f; 195 | float centerY = mRotateImgv.getHeight() / 2.0f; 196 | float centerZ = 0f; 197 | 198 | Rotate3dAnimation rotate3dAnimationX = new Rotate3dAnimation(0, 180, centerX, centerY, centerZ, Rotate3dAnimation.ROTATE_Y_AXIS, true); 199 | rotate3dAnimationX.setDuration(1000); 200 | mRotateImgv.startAnimation(rotate3dAnimationX); 201 | } 202 | 203 | private void rotateAnimHorizon() { 204 | float centerX = mRotateImgv.getWidth() / 2.0f; 205 | float centerY = mRotateImgv.getHeight() / 2.0f; 206 | float centerZ = 0f; 207 | 208 | Rotate3dAnimation rotate3dAnimationX = new Rotate3dAnimation(180, 0, centerX, centerY, centerZ, Rotate3dAnimation.ROTATE_Z_AXIS, true); 209 | rotate3dAnimationX.setDuration(1000); 210 | mRotateImgv.startAnimation(rotate3dAnimationX); 211 | 212 | // 下面是使用android自带的旋转动画 213 | // RotateAnimation rotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 214 | // rotateAnimation.setDuration(1000); 215 | // mRotateImgv.startAnimation(rotateAnimation); 216 | } 217 | 218 | @Override 219 | public boolean onCreateOptionsMenu(Menu menu) { 220 | // Inflate the menu; this adds items to the action bar if it is present. 221 | getMenuInflater().inflate(R.menu.menu_rotate, menu); 222 | return true; 223 | } 224 | 225 | @Override 226 | public boolean onOptionsItemSelected(MenuItem item) { 227 | // Handle action bar item clicks here. The action bar will 228 | // automatically handle clicks on the Home/Up button, so long 229 | // as you specify a parent activity in AndroidManifest.xml. 230 | int id = item.getItemId(); 231 | 232 | //noinspection SimplifiableIfStatement 233 | if (id == R.id.action_settings) { 234 | return true; 235 | } 236 | 237 | return super.onOptionsItemSelected(item); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /app/src/main/java/rotateanim/example/com/androidrotateanim/RoundNumProgressView.java: -------------------------------------------------------------------------------- 1 | package rotateanim.example.com.androidrotateanim; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.RectF; 10 | import android.graphics.SweepGradient; 11 | import android.graphics.Typeface; 12 | import android.util.AttributeSet; 13 | import android.util.DisplayMetrics; 14 | import android.util.TypedValue; 15 | import android.view.View; 16 | 17 | import java.util.Iterator; 18 | import java.util.Vector; 19 | 20 | /** 21 | * Created by yuzhangsheng on 2015/11/17. 22 | * 带进度的圆弧进度条,线程安全的View,可直接在线程中更新进度 23 | * 可配置顺时针和逆时针旋转 24 | */ 25 | public class RoundNumProgressView extends View { 26 | 27 | private static final int ROUND_COLOR_DEFAULT = 0x33ffffff; 28 | private static final int ROUND_WIDTH_DEFAULT = 2; //dp 29 | private static final int ROUND_PROGRESS_COLOR_DEFAULT = 0xffffffff; 30 | private static final int PERCENT_TEXTCOLOR_DEFAULT = 0xffffffff; 31 | private static final int PERCENT_TEXTSIZE_DEFAULT = 9; //sp 32 | private static final int MAX_PROGRESS_DEFAULT = 100; 33 | /** 允许显示的最大最小值 - 这个与mMaxProgress和 mMinProgress不一样 */ 34 | private static final int ALLOW_SHOW_MAX = 95; 35 | private static final int ALLOW_SHOW_MIN = 5; 36 | private static final int PERCENT_BASE = 100; 37 | 38 | /** 正常累加进度每次移动的角度 */ 39 | private static final int NORMAL_PROGRESS_STEP = 2; 40 | /** 闪烁进度条每次移动的角度 */ 41 | private static final int FLICKER_PROGRESS_STEP = 40; 42 | private static final int TOTAL_DEGREE = 360; 43 | /** 闪烁进度的时候,画的弧长 */ 44 | private static final int FLICKER_ARC_LENGTH = 270; 45 | /** 顶部作为计数起点, 右边是0,左边是-180或者180,底部是90 */ 46 | private static final int START_POINT_TOP = -90; 47 | 48 | /** 定义一支画笔 */ 49 | private Paint mPaint; 50 | private Paint mGradientArcPaint; 51 | private SweepGradient mSweepGradient; 52 | private int[] mColors = { 53 | 0x00ffffff, 0x40ffffff, 0x80ffffff, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, 0x80ffffff, 0x40ffffff, 0x00ffffff 54 | }; 55 | private Matrix mMatrix = new Matrix(); 56 | /** 圆环的颜色 */ 57 | private int mRoundColor; 58 | /** 圆环的宽度 */ 59 | private float mRoundWidth; 60 | /** 圆环进度的颜色 */ 61 | private int mRoundProgressColor; 62 | /** 中间进度百分比的字符串的颜色 */ 63 | private int mPercentTextColor; 64 | /** 中间进度百分比的字符串的字体 */ 65 | private float mPercentTextSize; 66 | /** 最大进度数(可以允许超过100的,但是没什么意义,数字可以超过100%) */ 67 | private int mMaxProgress; 68 | /** 允许显示的最大进度 */ 69 | private int mAllowMaxProgress = ALLOW_SHOW_MAX; 70 | /** 允许显示的最小进度 */ 71 | private int mAllowMinProgress = ALLOW_SHOW_MIN; 72 | /** 当前进度 */ 73 | private int mCurProgress = ALLOW_SHOW_MIN; 74 | 75 | /** 是否显示中间的进度 */ 76 | private boolean isPercentTextDisplayable = true; 77 | /** 用于定义的圆弧的形状和大小的界限 */ 78 | private RectF mArcLimitRect = new RectF(); 79 | 80 | /** 进度的风格,实心(FILL)或者空心(STROKE) */ 81 | private int mStyle; 82 | public static final int STROKE = 0; 83 | public static final int FILL = 1; 84 | 85 | /** 进度旋转方向,顺时针(CLOCKWISE)或者逆时针(COUNTERCLOCKWISE) */ 86 | private int mRotateOrientation; 87 | public static final int COUNTERCLOCKWISE = 0; 88 | public static final int CLOCKWISE = 1; 89 | 90 | /** 闪烁(循环旋转)进度条的时间 */ 91 | private int mFlickerProgressTime = 1000; 92 | private int mFlickerProgressTotal = 0; 93 | 94 | /** 闪烁进度条动画正在进行 */ 95 | private boolean isFlickerProgressWorking = false; 96 | private boolean isRefreshingProgress = false; 97 | 98 | private Vector mRoundNumProgressListeners; 99 | 100 | public interface IRoundNumProgressListener { 101 | void onFlickerProgressEnd(); 102 | } 103 | 104 | public RoundNumProgressView(Context context) { 105 | this(context, null); 106 | } 107 | 108 | public RoundNumProgressView(Context context, AttributeSet attrs) { 109 | this(context, attrs, 0); 110 | } 111 | 112 | public RoundNumProgressView(Context context, AttributeSet attrs, int defStyle) { 113 | super(context, attrs, defStyle); 114 | mPaint = new Paint(); 115 | mGradientArcPaint = new Paint(); 116 | //获取自定义属性和默认值 117 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundNumProgressView); 118 | mRoundColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundColor, ROUND_COLOR_DEFAULT); 119 | mRoundProgressColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_roundProgressColor, ROUND_PROGRESS_COLOR_DEFAULT); 120 | mPercentTextColor = mTypedArray.getColor(R.styleable.RoundNumProgressView_percentTextColor, PERCENT_TEXTCOLOR_DEFAULT); 121 | 122 | final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 123 | final float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, PERCENT_TEXTSIZE_DEFAULT, metrics); 124 | final float roundWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ROUND_WIDTH_DEFAULT, metrics); 125 | mPercentTextSize = mTypedArray.getDimension(R.styleable.RoundNumProgressView_percentTextSize, textSize); 126 | mRoundWidth = mTypedArray.getDimension(R.styleable.RoundNumProgressView_roundWidth, roundWidth); 127 | mMaxProgress = mTypedArray.getInteger(R.styleable.RoundNumProgressView_maxProgress, MAX_PROGRESS_DEFAULT); 128 | isPercentTextDisplayable = mTypedArray.getBoolean(R.styleable.RoundNumProgressView_percentTextDisplayable, true); 129 | mStyle = mTypedArray.getInt(R.styleable.RoundNumProgressView_style, STROKE); 130 | mRotateOrientation = mTypedArray.getInt(R.styleable.RoundNumProgressView_rotateOrientation, COUNTERCLOCKWISE); 131 | mTypedArray.recycle(); 132 | } 133 | 134 | public void addRoundNumProgressListener(IRoundNumProgressListener roundNumProgressListener) { 135 | if (roundNumProgressListener == null) 136 | return; 137 | if (mRoundNumProgressListeners == null) { 138 | mRoundNumProgressListeners = new Vector(); 139 | } 140 | mRoundNumProgressListeners.add(roundNumProgressListener); 141 | } 142 | 143 | private void notifyAllRoundNumProressListeners() { 144 | if (mRoundNumProgressListeners == null) 145 | return; 146 | Iterator iterator = mRoundNumProgressListeners.iterator(); 147 | while (iterator.hasNext()) { 148 | iterator.next().onFlickerProgressEnd(); 149 | } 150 | } 151 | 152 | int centerX = 0; // 获取圆心的x坐标 153 | int radius = 0; // 圆环的半径 154 | @Override 155 | protected void onDraw(Canvas canvas) { 156 | super.onDraw(canvas); 157 | if (centerX == 0) { 158 | centerX = getWidth() / 2; // 获取圆心的x坐标 159 | } 160 | if (radius == 0) { 161 | radius = (int) (centerX - mRoundWidth / 2); // 圆环的半径 162 | } 163 | // 画最外层的大圆环 164 | drawOuterCircle(canvas, centerX, radius); 165 | // 画进度百分比 166 | drawPercentText(canvas, centerX); 167 | // 画闪烁的进度条动画 168 | if (isFlickerProgressWorking) { 169 | drawFlickerArcProgress(canvas, centerX, radius); 170 | } else { 171 | // 画圆弧 ,画圆环的进度 172 | drawArcProgress(canvas, centerX, radius); 173 | } 174 | } 175 | 176 | private void drawOuterCircle(final Canvas canvas, final int centerX, final int radius) { 177 | mPaint.setColor(mRoundColor); // 设置圆环的颜色 178 | mPaint.setStyle(Paint.Style.STROKE); // 设置空心 179 | mPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 180 | mPaint.setAntiAlias(true); // 消除锯齿 181 | canvas.drawCircle(centerX, centerX, radius, mPaint); // 画出圆环 182 | } 183 | 184 | private void drawPercentText(final Canvas canvas, final int center) { 185 | mPaint.setStrokeWidth(0); 186 | mPaint.setColor(mPercentTextColor); 187 | mPaint.setTextSize(mPercentTextSize); 188 | mPaint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体 189 | final int percent = (int) (((float) mCurProgress / (float) mMaxProgress) * PERCENT_BASE); // 中间的进度百分比,先转换成float在进行除法运算,不然都为0 190 | final float textWidth = mPaint.measureText(percent + "%"); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间 191 | if (isPercentTextDisplayable && percent != 0 && mStyle == STROKE) { 192 | canvas.drawText(percent + "%", center - textWidth / 2, center + mPercentTextSize / 2 - 3, mPaint); // 画出进度百分比 193 | } 194 | } 195 | 196 | private void drawArcProgress(final Canvas canvas, final int centerX, final int radius) { 197 | mPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 198 | mPaint.setColor(mRoundProgressColor); // 设置进度的颜色 199 | // 用于定义的圆弧的形状和大小的界限 200 | if (mArcLimitRect.isEmpty()) { 201 | mArcLimitRect.set(centerX - radius, centerX - radius, centerX + radius, centerX + radius); 202 | } 203 | if (mStyle == STROKE) { 204 | mPaint.setStyle(Paint.Style.STROKE); 205 | } else if (mStyle == FILL) { 206 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 207 | } 208 | final float percent = ((float) mCurProgress / (float) mMaxProgress); 209 | if (mRotateOrientation == COUNTERCLOCKWISE) { // 计数起点在顶部,所以为-90 210 | canvas.drawArc(mArcLimitRect, START_POINT_TOP, -TOTAL_DEGREE * percent, false, mPaint); // 根据进度画圆弧 211 | } else if (mRotateOrientation == CLOCKWISE) { 212 | canvas.drawArc(mArcLimitRect, START_POINT_TOP, TOTAL_DEGREE * percent, false, mPaint); // 根据进度画圆弧 213 | } 214 | } 215 | 216 | private void drawFlickerArcProgress(final Canvas canvas, final int centerX, final int radius) { 217 | mGradientArcPaint.setStrokeWidth(mRoundWidth); // 设置圆环的宽度 218 | mGradientArcPaint.setColor(mRoundProgressColor); // 设置进度的颜色 219 | mGradientArcPaint.setAntiAlias(true); 220 | if (mArcLimitRect.isEmpty()) { 221 | mArcLimitRect.set(centerX - radius, centerX - radius, centerX + radius, centerX + radius); 222 | } 223 | mGradientArcPaint.setStyle(Paint.Style.STROKE); 224 | // 定义一个梯度渲染,由于梯度渲染是从三点钟方向开始,所以再让他逆时针旋转90°,从0点开始 225 | if (mSweepGradient == null) 226 | mSweepGradient = new SweepGradient(centerX, centerX, mColors, null); 227 | mMatrix.setRotate(-90 - mFlickerProgressTotal, centerX, centerX); 228 | mSweepGradient.setLocalMatrix(mMatrix); 229 | mGradientArcPaint.setShader(mSweepGradient); 230 | canvas.drawCircle(centerX, centerX, radius, mGradientArcPaint); // 画出圆环 231 | post(new Runnable() { 232 | @Override 233 | public void run() { 234 | mFlickerProgressTotal += FLICKER_PROGRESS_STEP; 235 | postInvalidate(); 236 | } 237 | }); 238 | } 239 | 240 | /*** 241 | * 启动闪烁动画,点击的时候调用会起到很好的提示作用 242 | */ 243 | public void startFlickerArcProgress(final Runnable endFlagRunnable) { 244 | if (isFlickerProgressWorking) 245 | return; 246 | isFlickerProgressWorking = true; 247 | postInvalidate(); 248 | postDelayed(new Runnable() { 249 | @Override 250 | public void run() { 251 | isFlickerProgressWorking = false; 252 | if (endFlagRunnable != null) { 253 | endFlagRunnable.run(); 254 | } 255 | notifyAllRoundNumProressListeners(); 256 | } 257 | }, mFlickerProgressTime); 258 | } 259 | 260 | private int mTempCurProgress; 261 | /** 262 | * 重新刷新显示进度,从0开始累加到要显示的值 263 | */ 264 | public void reRefreshToOriginProgress() { 265 | reRefreshToOriginProgress(getCurProgress()); 266 | } 267 | 268 | public void reRefreshToOriginProgress(final int toProgress) { 269 | if (isRefreshingProgress) 270 | return; 271 | if (toProgress <= mAllowMinProgress) 272 | return; 273 | mTempCurProgress = mAllowMinProgress; 274 | post(new Runnable() { 275 | @Override 276 | public void run() { 277 | if (mTempCurProgress >= toProgress) { 278 | isRefreshingProgress = false; 279 | setCurProgress(toProgress); 280 | return; 281 | } 282 | setCurProgress(mTempCurProgress += NORMAL_PROGRESS_STEP); 283 | post(this); 284 | } 285 | }); 286 | } 287 | 288 | public boolean isFlickerProgressWorking() { 289 | return isFlickerProgressWorking; 290 | } 291 | 292 | public boolean isRefreshingProgress() { 293 | return isRefreshingProgress; 294 | } 295 | 296 | public synchronized int getMaxProgress() { 297 | return mMaxProgress; 298 | } 299 | 300 | public synchronized int getMinProgress() { 301 | return mMaxProgress; 302 | } 303 | 304 | /** 305 | * 设置总的进度数 306 | * 307 | * @param maxProgress 308 | */ 309 | public synchronized void setMaxProgress(int maxProgress) { 310 | if (maxProgress < 0) { 311 | throw new IllegalArgumentException("max not less than 0"); 312 | } 313 | mMaxProgress = maxProgress; 314 | } 315 | 316 | /** 317 | * 设置允许显示进度的最小值 318 | * 319 | * @param allowMinProgress 320 | */ 321 | public synchronized void setAlloShowMinProgress(int allowMinProgress) { 322 | if (allowMinProgress < 0) { 323 | throw new IllegalArgumentException("min not less than 0"); 324 | } 325 | mAllowMinProgress = allowMinProgress; 326 | } 327 | 328 | /** 329 | * 获取进度.需要同步 330 | * 331 | * @return 332 | */ 333 | public synchronized int getCurProgress() { 334 | return mCurProgress; 335 | } 336 | 337 | /** 338 | * 设置进度,此为线程安全控件,由于考虑多线程的问题,需要同步 339 | * 刷新界面调用postInvalidate()能在非UI线程刷新 340 | * 341 | * @param curProgress 342 | */ 343 | public synchronized void setCurProgress(int curProgress) { 344 | if (curProgress < 0) { 345 | throw new IllegalArgumentException("progress not less than 0"); 346 | } 347 | if (curProgress > mAllowMaxProgress) { 348 | curProgress = mAllowMaxProgress; 349 | } else if (curProgress < mAllowMinProgress) { 350 | curProgress = mAllowMinProgress; 351 | } 352 | mCurProgress = curProgress; 353 | postInvalidate(); 354 | } 355 | 356 | public int getOuterRoundColor() { 357 | return mRoundColor; 358 | } 359 | 360 | public void setOuterRoundColor(int roundColor) { 361 | mRoundColor = roundColor; 362 | } 363 | 364 | public int getRoundProgressColor() { 365 | return mRoundProgressColor; 366 | } 367 | 368 | public void setRoundProgressColor(int roundProgressColor) { 369 | mRoundProgressColor = roundProgressColor; 370 | } 371 | 372 | public int getPercentTextColor() { 373 | return mPercentTextColor; 374 | } 375 | 376 | public void setPercentTextColor(int textColor) { 377 | mPercentTextColor = textColor; 378 | } 379 | 380 | public float getPercentTextSize() { 381 | return mPercentTextSize; 382 | } 383 | 384 | public void setPercentTextSize(float percentTextSize) { 385 | mPercentTextSize = percentTextSize; 386 | } 387 | 388 | public float getRoundWidth() { 389 | return mRoundWidth; 390 | } 391 | 392 | public void setRoundWidth(float roundWidth) { 393 | mRoundWidth = roundWidth; 394 | } 395 | 396 | public void setRotateOrientation(int rotateOrientation) { 397 | mRotateOrientation = rotateOrientation; 398 | } 399 | 400 | public void setStyle(int style) { 401 | mStyle = style; 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopFisher/AndroidRotateAnim/93876bb6bc779113ed1bf9685df631671342d2a3/app/src/main/res/drawable/test1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopFisher/AndroidRotateAnim/93876bb6bc779113ed1bf9685df631671342d2a3/app/src/main/res/drawable/test2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/test3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopFisher/AndroidRotateAnim/93876bb6bc779113ed1bf9685df631671342d2a3/app/src/main/res/drawable/test3.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 28 | 29 | 34 | 35 | 43 | 44 | 52 | 65 | 66 | 67 | 75 | 76 |