安卓自定义View之旋转麦克风
前言
工作需要一个外圈会转动,还能任意分成多少份的外圈的麦克风,研究一番之后设计了一个会旋转的麦克风。
一、先看效果图
视频效果请看https://live.youkuaiyun.com/v/464864?spm=1001.2014.3001.5501

二、自定义属性
接下来就是熟悉的自定义属性了
在项目的\res\values\attrs.xml 中 新增一个 declare-styleable 名字就叫RotateView。
为了好看出是在旋转,静止和旋转状态都定义了俩种颜色。
份数的简单计算方式:360度除以每份的度数再取整即可,份数 = (int) (360/ 每份扫过的角度)
<declare-styleable name="RotateView">
<!--静止状态下的第1种颜色-->
<attr name="rotate_view_color_normal_one" format="reference|color" />
<!--静止状态下的第2种颜色-->
<attr name="rotate_view_color_normal_two" format="reference|color" />
<!--旋转状态下的第1种颜色-->
<attr name="rotate_view_color_running_one" format="reference|color" />
<!--旋转状态下的第2种颜色-->
<attr name="rotate_view_color_running_two" format="reference|color" />
<!--静止状态下画笔描边宽度-->
<attr name="rotate_view_stroke_width_normal" format="reference|float|dimension" />
<!--旋转状态下画笔描边宽度-->
<attr name="rotate_view_stroke_width_running" format="reference|float|dimension" />
<!--每份扫过的角度-->
<attr name="rotate_view_angle_of_each_sweep" format="reference|float|dimension" />
<attr name="rotate_view_rotate_direction">
<!--顺时针旋转-->
<enum name="clockwise" value="1" />
<!--逆时针旋转-->
<enum name="counterclockwise" value="2" />
</attr>
</declare-styleable>
三、关键设计
1. 继承View 复写必要的构造方法
/**
* 可以旋转的 圆环 麦克风
*/
public class RotateView extends View {
public RotateView(Context context) {
this(context, null);
}
public RotateView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RotateView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttr(context, attrs);
init();
}
2. 读取自定义属性并初始化
private void initAttr(Context context, AttributeSet attrs) {
int normalOneColorDefValue = context.getResources().getColor(R.color.colorRingNormalOne);
int normalTwoColorDefValue = context.getResources().getColor(R.color.colorRingNormalTwo);
int runningOneColorDefValue = context.getResources().getColor(R.color.colorRingRunningOne);
int runningTwoColorDefValue = context.getResources().getColor(R.color.colorRingRunningTwo);
normalColorOne = normalOneColorDefValue;
normalColorTwo = normalTwoColorDefValue;
runningColorOne = runningOneColorDefValue;
runningColorTwo = runningTwoColorDefValue;
float normalStrokeWidthDef = 3;
normalStrokeWidth = dipToPx(normalStrokeWidthDef);
float runningStrokeWidthDef = 3;
runningStrokeWidth = dipToPx(runningStrokeWidthDef);
rotateDirection = ANTI_CLOCK_WISE;
int sweepAngleDef = 18;
sweepAngle = sweepAngleDef;
countStep = (int) (360 / sweepAngle); //计算分为几份
try (TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RotateView)) {
normalColorOne = typedArray.getColor(R.styleable.RotateView_rotate_view_color_normal_one, normalOneColorDefValue);
normalColorTwo = typedArray.getColor(R.styleable.RotateView_rotate_view_color_normal_two, normalTwoColorDefValue);
runningColorOne = typedArray.getColor(R.styleable.RotateView_rotate_view_color_running_one, runningOneColorDefValue);
runningColorTwo = typedArray.getColor(R.styleable.RotateView_rotate_view_color_running_two, runningTwoColorDefValue);
float normalStrokeWidthV = typedArray.getFloat(R.styleable.RotateView_rotate_view_stroke_width_normal, normalStrokeWidthDef);
normalStrokeWidth = dipToPx(normalStrokeWidthV);
float runningStrokeWidthV = typedArray.getFloat(R.styleable.RotateView_rotate_view_stroke_width_running, runningStrokeWidthDef);
runningStrokeWidth = dipToPx(runningStrokeWidthV);
sweepAngle = typedArray.getFloat(R.styleable.RotateView_rotate_view_angle_of_each_sweep, sweepAngleDef);
countStep = (int) (360 / sweepAngle); //计算分成几份
// 读取自定义属性中的 旋转方向 ,默认逆时针
rotateDirection = typedArray.getInt(R.styleable.RotateView_rotate_view_rotate_direction, ANTI_CLOCK_WISE);
//typedArray.recycle();
}
}
private void init() {
paintRing = new Paint();
paintRing.setAntiAlias(true);
paintRing.setDither(true);
paintRing.setStrokeWidth(normalStrokeWidth);
paintRing.setStyle(Paint.Style.STROKE);
paintMic = new Paint();
paintMic.setAntiAlias(true);
paintMic.setDither(true);
paintMic.setStrokeWidth(normalStrokeWidth / 2);
paintMic.setStyle(Paint.Style.FILL);
ringRectF = new RectF(); // 这个矩形区域用来确定圆环的位置
roundRectF = new RectF();
micRectF = new RectF();
}
3. 确定圆环的位置
为了让视图保持在正中央,需要找出圆心的位置,确定矩形的区域;

4. 绘制圆环
计算好开始角度和每份需要扫过的角度,通过 drawArc 函数来画圆环即可
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint) {
super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
}

5.绘制麦克风
之前有一篇文章已经介绍过如何绘制麦克风了,这里不再赘述。传送门自定义麦克风
如何让圆环旋转起来呢?
可以每次画圆环之前让画布旋转一个角度rotateDegrees,然后每次改变这个 rotateDegrees 重新绘制圆环即可达到旋转的效果。


6.实现旋转效果
使用 Handler 发延迟消息不断重绘,就能旋转起来了。
private final Handler handlerForRotate = new Handler(Looper.getMainLooper());
private final Runnable runnableForRotate = new Runnable() {
@Override
public void run() {
updateRotateDegrees();
invalidate();
handlerForRotate.postDelayed(runnableForRotate, 10);
}
};
/**
* 更新角度
*/
private float updateRotateDegrees() {
if (isRunning()) {
// rotateDegrees = (float) (rotateDegrees + 1.828)
rotateDegrees = (float) (rotateDegrees + 0.225);
if (rotateDegrees > 360) {
rotateDegrees = rotateDegrees % 360;
}
} else {
rotateDegrees = 0;
}
// Log.i("RotateView", "角度 " + rotateDegrees);
return rotateDegrees;
}
/**
* 开始 旋转
* <p>
* requestLayout 方法会导致View的onMeasure、onLayout、onDraw方法被调用;
* <p>
* 在非UI线程中调用 postInvalidate(); 来请求重绘
* <p>
* 在UI线程中调用 Invalidate(); 来请求重绘
*/
public void startRotate() {
if (isRunning()) {
return;
}
setRunning(true);
handlerForRotate.removeCallbacks(runnableForRotate);
handlerForRotate.post(runnableForRotate);
}
/**
* 停止 旋转
*/
public void stopRotate() {
handlerForRotate.removeCallbacks(runnableForRotate);
handlerForRotate.removeCallbacksAndMessages(null);
setRunning(false);
rotateDegrees = 0;
invalidate();
}
7.完整代码
package com.linkpoon.hamlauncher.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import com.linkpoon.hamlauncher.R;
/**
* 可以旋转的 圆环 麦克风
*/
public class RotateView extends View {
public RotateView(Context context) {
this(context, null);
}
public RotateView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RotateView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttr(context, attrs);
init();
}
private Paint paintRing;// 圆环画笔
private Paint paintMic;// 麦克风画笔
private int normalColorOne; //静止状态下 圆环 颜色1
private int normalColorTwo;//静止状态下 圆环 颜色2
private float normalStrokeWidth; //静止状态下 麦克风画笔 描边宽度
private int runningColorOne;//旋转状态下 圆环 颜色1
private int runningColorTwo;//旋转状态下 圆环 颜色2
private float runningStrokeWidth;//旋转状态下 圆环 画笔 描边宽度
public static final int CLOCK_WISE = 1;// 顺时针 旋转
public static final int ANTI_CLOCK_WISE = 2;// 逆时针 旋转
private float circlePointX; //圆心横坐标
private float circlePointY; //圆心纵坐标
private RectF ringRectF;// 用于绘制 最外圈的圆环
private RectF roundRectF;// 用于绘制 麦克风上面的圆角矩形部分
private RectF micRectF;// 用于绘制 麦克风中间的弧线部分
private int paddingTopRect = 5;//麦克风的位置在中心往下偏移5dp
private float startAngle = 0; // 开始角度
private float sweepAngle; // 扫过的角度(每一小段占的角度)
private int countStep; //计算分为几段
private float rotateDegrees = 0; //画布旋转的初始角度
private int rotateDirection;// 旋转方向
private boolean isRunning;// 是否在旋转
public boolean isRunning() {
return this.isRunning;
}
public void setRunning(boolean running) {
this.isRunning = running;
}
/**
* 设置旋转方向
*/
public void setRotateDirection(int direction) {
this.rotateDirection = direction;
// requestLayout();
}
/**
* 获取旋转方向
*/
public int getRotateDirection() {
return this.rotateDirection;
}
public int getNormalColorOne() {
return normalColorOne;
}
public void setNormalColorOne(int normalColorOne) {
this.normalColorOne = normalColorOne;
}
public int getNormalColorTwo() {
return normalColorTwo;
}
public void setNormalColorTwo(int normalColorTwo) {
this.normalColorTwo = normalColorTwo;
}
public float getNormalStrokeWidth() {
return normalStrokeWidth;
}
public void setNormalStrokeWidth(float normalStrokeWidth) {
this.normalStrokeWidth = normalStrokeWidth;
}
public int getRunningColorOne() {
return runningColorOne;
}
public void setRunningColorOne(int runningColorOne) {
this.runningColorOne = runningColorOne;
}
public int getRunningColorTwo() {
return runningColorTwo;
}
public void setRunningColorTwo(int runningColorTwo) {
this.runningColorTwo = runningColorTwo;
}
public float getRunningStrokeWidth() {
return runningStrokeWidth;
}
public void setRunningStrokeWidth(float runningStrokeWidth) {
this.runningStrokeWidth = runningStrokeWidth;
}
/**
* 将 dp 转换成 px
*/
private int dipToPx(float dip) {
float density = getResources().getDisplayMetrics().density;
return (int) ((dip * density) + 0.5f);
}
private final Handler handlerForRotate = new Handler(Looper.getMainLooper());
private final Runnable runnableForRotate = new Runnable() {
@Override
public void run() {
//requestLayout();
updateRotateDegrees();
invalidate();
handlerForRotate.postDelayed(runnableForRotate, 10);
}
};
private void initAttr(Context context, AttributeSet attrs) {
int normalOneColorDefValue = context.getResources().getColor(R.color.colorRingNormalOne);
int normalTwoColorDefValue = context.getResources().getColor(R.color.colorRingNormalTwo);
int runningOneColorDefValue = context.getResources().getColor(R.color.colorRingRunningOne);
int runningTwoColorDefValue = context.getResources().getColor(R.color.colorRingRunningTwo);
normalColorOne = normalOneColorDefValue;
normalColorTwo = normalTwoColorDefValue;
runningColorOne = runningOneColorDefValue;
runningColorTwo = runningTwoColorDefValue;
float normalStrokeWidthDef = 3;
normalStrokeWidth = dipToPx(normalStrokeWidthDef);
float runningStrokeWidthDef = 3;
runningStrokeWidth = dipToPx(runningStrokeWidthDef);
rotateDirection = ANTI_CLOCK_WISE;
int sweepAngleDef = 18;
sweepAngle = sweepAngleDef;
countStep = (int) (360 / sweepAngle); //计算分为几份
try (TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RotateView)) {
normalColorOne = typedArray.getColor(R.styleable.RotateView_rotate_view_color_normal_one, normalOneColorDefValue);
normalColorTwo = typedArray.getColor(R.styleable.RotateView_rotate_view_color_normal_two, normalTwoColorDefValue);
runningColorOne = typedArray.getColor(R.styleable.RotateView_rotate_view_color_running_one, runningOneColorDefValue);
runningColorTwo = typedArray.getColor(R.styleable.RotateView_rotate_view_color_running_two, runningTwoColorDefValue);
float normalStrokeWidthV = typedArray.getFloat(R.styleable.RotateView_rotate_view_stroke_width_normal, normalStrokeWidthDef);
normalStrokeWidth = dipToPx(normalStrokeWidthV);
float runningStrokeWidthV = typedArray.getFloat(R.styleable.RotateView_rotate_view_stroke_width_running, runningStrokeWidthDef);
runningStrokeWidth = dipToPx(runningStrokeWidthV);
sweepAngle = typedArray.getFloat(R.styleable.RotateView_rotate_view_angle_of_each_sweep, sweepAngleDef);
countStep = (int) (360 / sweepAngle); //计算分成几份
// 读取自定义属性中的 旋转方向 ,默认逆时针
rotateDirection = typedArray.getInt(R.styleable.RotateView_rotate_view_rotate_direction, ANTI_CLOCK_WISE);
//typedArray.recycle();
}
}
private void init() {
paintRing = new Paint();
paintRing.setAntiAlias(true);
paintRing.setDither(true);
paintRing.setStrokeWidth(normalStrokeWidth);
paintRing.setStyle(Paint.Style.STROKE);
paintMic = new Paint();
paintMic.setAntiAlias(true);
paintMic.setDither(true);
paintMic.setStrokeWidth(normalStrokeWidth / 2);
paintMic.setStyle(Paint.Style.FILL);
ringRectF = new RectF(); // 这个矩形区域用来确定圆环的位置
roundRectF = new RectF();
micRectF = new RectF();
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
// 比较宽 高 取小的值得一半 作为圆环的半径
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float minValue = (float) Math.min(width, height);
float radius = minValue / 2; //半径
circlePointX = (float) (width / 2); //圆心横坐标
circlePointY = (float) (height / 2); //圆心纵坐标
float spaceOfRing = radius * 0.12f;
float ringRectFLeft = circlePointX - radius + spaceOfRing;
float ringRectFTop = circlePointY - radius + spaceOfRing;
float ringRectFRight = circlePointX + radius - spaceOfRing;
float ringRectFBottom = circlePointY + radius - spaceOfRing;
// 设置这个矩形区域的位置
ringRectF.set(ringRectFLeft, ringRectFTop, ringRectFRight, ringRectFBottom);
//canvas.drawRect(ringRectF, paintRing);
canvas.save();
if (getRotateDirection() == CLOCK_WISE) {
// 顺时针
canvas.rotate(rotateDegrees, ringRectF.centerX(), ringRectF.centerY());
} else {
// 逆时针
canvas.rotate(-rotateDegrees, ringRectF.centerX(), ringRectF.centerY());
}
// Log.i("RotateView", "" + ringRectF.centerX() + "" + ringRectF.centerY());
for (int i = 0; i < countStep; i++) {
if (isRunning()) {
// 动态(旋转时)
if (i % 2 == 0) {
// 偶数位置 用第 1 种颜色
paintRing.setColor(runningColorOne);
// paint.setAlpha(128);// 半透明
paintRing.setStrokeWidth(runningStrokeWidth);
} else {
// 奇数位置 用第 2 种颜色
paintRing.setColor(runningColorTwo);
// paint.setAlpha(128);// 半透明
paintRing.setStrokeWidth(runningStrokeWidth);
}
} else {
// 静态
if (i % 2 == 0) {
paintRing.setColor(normalColorOne);
// paint.setAlpha(255);// 不透明
paintRing.setStrokeWidth(normalStrokeWidth);
} else {
paintRing.setColor(normalColorTwo);
// paint.setAlpha(255);// 不透明
paintRing.setStrokeWidth(normalStrokeWidth);
}
}
// Log.i("PanView", " " + startAngle + " rotate " + ringRectF.toString());
// 画弧线 画圆环
canvas.drawArc(ringRectF, startAngle, sweepAngle, false, paintRing);
startAngle = startAngle + sweepAngle;
}
startAngle = 0; //画好圆环后 将开始角度重置
canvas.restore();
if (isRunning()) {
paintMic.setColor(runningColorTwo);
} else {
paintMic.setColor(normalColorOne);
}
float roundLeft = circlePointX - circlePointX / 19;
float roundTop = circlePointY - circlePointY / 5 + dipToPx(paddingTopRect);
float roundRight = circlePointX + circlePointX / 19;
float roundBottom = circlePointY + dipToPx(paddingTopRect - 2);
// 设置矩形区域,用于确定麦克风上部的位置
roundRectF.set(roundLeft, roundTop, roundRight, roundBottom);
// paintMic.setStrokeWidth(normalStrokeWidth);
paintMic.setStyle(Paint.Style.FILL);
// 画一个圆角矩形 圆角度数16
canvas.drawRoundRect(roundRectF, 16, 16, paintMic);
// paintMic.setStrokeWidth(normalStrokeWidth);
float vLineStartY = circlePointY + dipToPx(paddingTopRect + 3);
float vLineStopY = circlePointY + circlePointY / 11 + dipToPx(paddingTopRect + 3);
// 画一段竖线
canvas.drawLine(circlePointX, vLineStartY, circlePointX, vLineStopY, paintMic);
float rectLeftOfArc = circlePointX - circlePointX / 10;
float rectTopOfArc = circlePointY - circlePointY / 8 + dipToPx(paddingTopRect + 3);
float rectRightOfArc = circlePointX + circlePointX / 10;
float rectBottomOfArc = circlePointY + dipToPx(paddingTopRect + 3);
micRectF.set(rectLeftOfArc, rectTopOfArc, rectRightOfArc, rectBottomOfArc);
/***
* path.arcTo 用来绘制一段圆弧,实际上是截取圆或者椭圆的一部分
* 该方法接收三个参数,
* 第一个表示弧形所在的矩形,
* 如果矩形为正方形,则画出的弧形为圆的一部分,
* 如果矩形宽高不等,画出的弧形为椭圆的一部分,
* 第二个参数表示绘制的起点位置,
* 0 度为钟表三点位置, 顺时针方向为正
* 第三个参数表示绘制的度数。
* */
// paintMic.setStrokeWidth(normalStrokeWidth);
paintMic.setStyle(Paint.Style.STROKE);
//画一段圆弧
canvas.drawArc(micRectF, -10, 180 + 10 + 10, false, paintMic);
float hStartX = circlePointX - circlePointX / 15;
float hStartY = circlePointY + circlePointY / 11 + dipToPx(paddingTopRect + 5);
float hStopX = circlePointX + circlePointX / 15;
float hStopY = circlePointY + circlePointY / 11 + dipToPx(paddingTopRect + 5);
// 画一段横线
canvas.drawLine(hStartX, hStartY, hStopX, hStopY, paintMic);
}
/**
* 更新角度
*/
private float updateRotateDegrees() {
if (isRunning()) {
// rotateDegrees = (float) (rotateDegrees + 1.828)
rotateDegrees = (float) (rotateDegrees + 0.225);
if (rotateDegrees > 360) {
rotateDegrees = rotateDegrees % 360;
}
} else {
rotateDegrees = 0;
}
// Log.i("RotateView", "角度 " + rotateDegrees);
return rotateDegrees;
}
/**
* 开始 旋转
* <p>
* requestLayout 方法会导致View的onMeasure、onLayout、onDraw方法被调用;
* <p>
* 在非UI线程中调用 postInvalidate(); 来请求重绘
* <p>
* 在UI线程中调用 Invalidate(); 来请求重绘
*/
public void startRotate() {
if (isRunning()) {
return;
}
setRunning(true);
handlerForRotate.removeCallbacks(runnableForRotate);
handlerForRotate.post(runnableForRotate);
}
/**
* 停止 旋转
*/
public void stopRotate() {
handlerForRotate.removeCallbacks(runnableForRotate);
handlerForRotate.removeCallbacksAndMessages(null);
setRunning(false);
rotateDegrees = 0;
//requestLayout();
invalidate();
}
}
四、测试一下
写个测试布局
R.layout.activity_test
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.linkpoon.hamlauncher.view.RotateView
android:id="@+id/test_rotate_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:rotate_view_angle_of_each_sweep="18"
app:rotate_view_stroke_width_normal="8"
app:rotate_view_stroke_width_running="8" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#565656" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/test_button_stat_ni"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:minHeight="45dp"
android:text="逆时针旋转" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#565656" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/test_button_stat_shun"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:minHeight="45dp"
android:text="顺时针旋转" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#565656" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/test_button_stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:minHeight="45dp"
android:text="停止旋转" />
</LinearLayout>
再写个TestActivity 。
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatTextView;
import com.linkpoon.hamlauncher.R;
import com.linkpoon.hamlauncher.view.RotateView;
public class TestActivity extends AppCompatActivity implements View.OnClickListener {
private RotateView rotateView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
rotateView = findViewById(R.id.test_rotate_view);
AppCompatTextView buttonStartNi = findViewById(R.id.test_button_stat_ni);
AppCompatTextView buttonStartShun = findViewById(R.id.test_button_stat_shun);
AppCompatTextView buttonStop = findViewById(R.id.test_button_stop);
buttonStartNi.setOnClickListener(this);
buttonStartShun.setOnClickListener(this);
buttonStop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.test_button_stat_ni) {
startNiButtonClicked();
} else if (id == R.id.test_button_stat_shun) {
startShunButtonClicked();
} else if (id == R.id.test_button_stop) {
stopButtonClicked();
}
}
private void startNiButtonClicked() {
if (rotateView == null) {
return;
}
rotateView.setRunningColorOne(Color.argb(255, 111, 55, 0));
rotateView.setRunningColorTwo(Color.argb(255, 23, 128, 35));
rotateView.setRotateDirection(RotateView.ANTI_CLOCK_WISE);
rotateView.startRotate();
}
private void startShunButtonClicked() {
if (rotateView == null) {
return;
}
rotateView.setRunningColorOne(Color.parseColor("#fe8812"));
rotateView.setRunningColorTwo(Color.argb(255, 200, 33, 33));
rotateView.setRotateDirection(RotateView.CLOCK_WISE);
rotateView.startRotate();
}
private void stopButtonClicked() {
if (rotateView == null) {
return;
}
rotateView.stopRotate();
}
}
Nice ,效果不错,收工,以后需求有变化再改了!
534

被折叠的 条评论
为什么被折叠?



