Android動畫之——圓形進度條加波浪線
阿新 • • 發佈:2019-01-03
效果圖
圓形進度條
public class RecordView extends View {
//View預設最小寬度
private static final int DEFAULT_MIN_WIDTH = 500;
public final static int MODEL_PLAY = 2;
public final static int MODEL_RECORD = 1;
private final TypedArray typedArray;
//圓環的邊距
private int pandding = 10;
//圓環的寬度
private int widthing = 5;
private Context mContext;
private Paint mPaint;
private final String TAG = "RecordView";
private int countdownTime = 9;//倒計時時間,預設時間10秒
private int countdownTime2 = 9;//倒計時時間,預設時間10秒.這是會變的
private float progress = 0;//總進度360
private boolean canDrawProgress = false ;
private double r;
private Timer timeTimer = new Timer(true);
private Timer progressTimer = new Timer(true);
private long lastTime = 0;
private int lineSpeed = 100;
private float translateX = 0;
/**
* 圓環顏色
* */
private int[] doughnutColors = new int[]{0xFFDAF6FE,0xFF45C3E5 ,0xFF45C3E5,0xFF45C3E5,0xFF45C3E5};
/**
* 預設是錄製模式
* */
private int model = MODEL_RECORD;
/**
* 計時器提示時間
* */
private String hintText = "";
/**
* 進度條終點圖片
* */
private Drawable progressDrawable;
/**
* 振幅
*/
private float amplitude = 1;
/**
* 音量
*/
private float volume = 10;
private int fineness = 1;
private float targetVolume = 1;
private float maxVolume = 100;
private boolean isSet = false;
/**
* 靈敏度
*/
private int sensibility = 4;
private boolean canSetVolume = true;
private TimerTask timeTask;
private TimerTask progressTask;
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 1){
countdownTime2--;
if(countdownTime2 == 0){
listener.onCountDown();
canSetVolume = false;
timeTask.cancel();
postInvalidate();
}
}else if(msg.what == 2){
progress += 360.00/(countdownTime*950.00/5.00);
// Log.d(TAG,"progress:"+progress);
if(progress >360){
targetVolume = 1;
postInvalidate();
progressTask.cancel();
}else
postInvalidate();
}
}
};
private OnCountDownListener listener;
private float textHintSize;
private float middleLineHeight;
private int middleLineColor;
private int voiceLineColor;
private ArrayList<Path> paths;
private int timeTextColor;
private String unit;
private String playHintText;
public RecordView(Context context) {
this(context,null);
}
public RecordView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
typedArray = context.obtainStyledAttributes(attrs,R.styleable.recordView);
initAtts();
mPaint = new Paint();
mPaint.setAntiAlias(true);//消除鋸齒
mPaint.setStyle(Paint.Style.STROKE);
}
private void initAtts(){
model = typedArray.getInt(R.styleable.recordView_model,MODEL_RECORD);
hintText = typedArray.getString(R.styleable.recordView_hintText);
progressDrawable = typedArray.getDrawable(R.styleable.recordView_progressSrc) == null?
getResources().getDrawable(R.mipmap.light_blue):typedArray.getDrawable(R.styleable.recordView_progressSrc);
textHintSize = typedArray.getDimension(R.styleable.recordView_hintTextSize,15);
middleLineColor = typedArray.getColor(R.styleable.recordView_middleLineColor, getResources().getColor(R.color.RoundFillColor));
voiceLineColor = typedArray.getColor(R.styleable.recordView_middleLineColor, getResources().getColor(R.color.RoundFillColor));
middleLineHeight = typedArray.getDimension(R.styleable.recordView_middleLineHeight, 2);
timeTextColor = typedArray.getColor(R.styleable.recordView_timeTextColor, getResources().getColor(R.color.TimeTextColor));
unit = typedArray.getString(R.styleable.recordView_unit);
playHintText = typedArray.getString(R.styleable.recordView_playHintText);
paths = new ArrayList<>(20);
for (int i = 0; i <20; i++) {
paths.add(new Path());
}
}
/**
* 當佈局為wrap_content時設定預設長寬
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
}
private int measure(int origin) {
int result = DEFAULT_MIN_WIDTH;
int specMode = MeasureSpec.getMode(origin);
int specSize = MeasureSpec.getSize(origin);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(model == MODEL_RECORD){
drawDefaultView(canvas);
drawVoiceLine(canvas);
}else{
drawDefaultForPlay(canvas);
drawVoiceLine2(canvas);
}
//這邊開啟畫進度條
if(canDrawProgress){
drawProgress(canvas);
}
}
private void drawDefaultForPlay(Canvas canvas){
/**
* 先畫一個大圓
*/
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dip2px(mContext, widthing));
mPaint.setColor(mContext.getResources().getColor(R.color.RoundColor));
RectF oval = new RectF( dip2px(mContext, pandding)
, dip2px(mContext, pandding)
, getWidth()-dip2px(mContext, pandding)
, getHeight()-dip2px(mContext, pandding));
canvas.drawArc(oval, 0, 360, false, mPaint); //繪製圓弧
/**
* 播放的點
* */
drawImageDot(canvas);
/**
* 畫時間
* */
Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint2.setTextSize(dip2px(mContext,14));
paint2.setColor(mContext.getResources().getColor(R.color.RoundFillColor));
paint2.setTextAlign(Paint.Align.CENTER);
if(playHintText == null){
playHintText = "正在播放錄音.";
}
canvas.drawText(playHintText, getWidth()/2, getHeight()*1/3, paint2);
}
private void drawDefaultView(Canvas canvas){
/**
* 畫提示的文字
* */
if(hintText!=null&&!hintText.equals("")){
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(dip2px(mContext,textHintSize));
paint.setColor(mContext.getResources().getColor(R.color.RoundHintTextColor));
// 下面這行是實現水平居中,drawText對應改為傳入targetRect.centerX()
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(hintText, getWidth()/2, getHeight()/2+50, paint);
}else{
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(dip2px(mContext,textHintSize));
paint.setColor(mContext.getResources().getColor(R.color.RoundHintTextColor));
// 下面這行是實現水平居中,drawText對應改為傳入targetRect.centerX()
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("剩餘錄製時間", getWidth()/2, getHeight()/2+50, paint);
}
/**
* 畫時間
* */
Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint2.setTextSize(dip2px(mContext,60));
paint2.setColor(mContext.getResources().getColor(R.color.TimeTextColor));
paint2.setTextAlign(Paint.Align.CENTER);
canvas.drawText(countdownTime2+"", getWidth()/2, getHeight()/2-20, paint2);
/**
* 畫單位,預設s
* */
Paint paint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint1.setTextSize(dip2px(mContext,40));
paint1.setColor(mContext.getResources().getColor(R.color.TimeTextColor));
paint1.setTextAlign(Paint.Align.CENTER);
float timeWidth = getWidth()/2f+paint2.measureText(countdownTime2+"")*2/3;
canvas.drawText(unit == null ?"s":unit,timeWidth, getHeight()/2-20, paint1);
/**
* 畫一個大圓(純色)
*/
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dip2px(mContext, widthing));
mPaint.setColor(mContext.getResources().getColor(R.color.RoundColor));
RectF oval1 = new RectF( dip2px(mContext, pandding)
, dip2px(mContext, pandding)
, getWidth()-dip2px(mContext, pandding)
, getHeight()-dip2px(mContext, pandding));
canvas.drawArc(oval1, progress, 360, false, mPaint); //繪製圓弧
}
public void setModel(int model){
this.model = model;
postInvalidate();
}
/**
* 根據手機的解析度從 dp 的單位 轉成為 px(畫素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 繪製圓弧
* */
private void drawProgress(Canvas canvas){
/**
* 這邊畫進度
*/
if(progress > 90){
mPaint.setColor(getResources().getColor(R.color.RoundFillColor));
mPaint.setStrokeWidth(dip2px(mContext, widthing));
RectF oval = new RectF( dip2px(mContext, pandding)
, dip2px(mContext, pandding)
, getWidth()-dip2px(mContext, pandding)
, getHeight()-dip2px(mContext, pandding));
canvas.drawArc(oval, 0, progress-90, false, mPaint); //繪製圓弧
r = getHeight()/2f-dip2px(mContext,pandding);
}
/**
* 畫一個大圓(漸變)
*/
mPaint.setStyle(Paint.Style.STROKE);
canvas.rotate(-90, getWidth() / 2, getHeight() / 2);
mPaint.setStrokeWidth(dip2px(mContext, widthing));
mPaint.setShader(new SweepGradient(getWidth() / 2, getHeight() / 2, doughnutColors, null));
RectF oval = new RectF( dip2px(mContext, pandding)
, dip2px(mContext, pandding)
, getWidth()-dip2px(mContext, pandding)
, getHeight()-dip2px(mContext, pandding));
//看這裡,其實這裡當progress大於90以後就一直只畫0-90度的圓環
canvas.drawArc(oval, 0, progress<90?progress:90, false, mPaint); //繪製圓弧
canvas.rotate(90, getWidth() / 2, getHeight() / 2);
mPaint.reset();
drawImageDot(canvas);
}
private void drawImageDot(Canvas canvas){
/**
* 畫一個點(圖片)
* */
if(r>0){
if(progress >360)
return;
double hu = Math.PI*Double.parseDouble(String.valueOf(progress))/180.0;
Log.d(TAG,"hu: "+hu);
double p = Math.sin(hu)*r;
Log.d(TAG,"p: "+p);
double q = Math.cos(hu)*r;
Log.d(TAG,"q: "+q);
float x = (float) ((getWidth()-progressDrawable.getIntrinsicWidth())/2f+p);
Log.d(TAG,"x: "+x);
float y = (float) ((dip2px(mContext,pandding)-progressDrawable.getIntrinsicHeight()/2f)+r-q);
Log.d(TAG,"y: "+y);
canvas.drawBitmap(((BitmapDrawable)progressDrawable).getBitmap(),x,y,mPaint);
}
}
/**
* 畫聲紋(錄製)
* */
private void drawVoiceLine(Canvas canvas) {
lineChange();
mPaint.setColor(voiceLineColor);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(2);
canvas.save();
int moveY = getHeight()*3/4;
for (int i = 0; i < paths.size(); i++) {
paths.get(i).reset();
paths.get(i).moveTo(getWidth()*5/6, getHeight() *3/4);
}
for (float j = getWidth()*5/6 - 1; j >= getWidth()/6; j -= fineness) {
float i = j-getWidth()/6;
//這邊必須保證起始點和終點的時候amplitude = 0;
amplitude = 5 * volume *i / getWidth() - 5 * volume * i / getWidth() * i/getWidth()*6/4;
for (int n = 1; n <= paths.size(); n++) {
float sin = amplitude * (float) Math.sin((i - Math.pow(1.22, n)) * Math.PI / 180 - translateX);
paths.get(n - 1).lineTo(j, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY));
}
}
for (int n = 0; n < paths.size(); n++) {
if (n == paths.size() - 1) {
mPaint.setAlpha(255);
} else {
mPaint.setAlpha(n * 130 / paths.size());
}
if (mPaint.getAlpha() > 0) {
canvas.drawPath(paths.get(n), mPaint);
}
}
canvas.restore();
}
/**
* 畫聲紋(播放)
* */
private void drawVoiceLine2(Canvas canvas) {
lineChange();
mPaint.setColor(voiceLineColor);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
canvas.save();
int moveY = getHeight() / 2;
int pandY = getWidth()/12;
for (int i = 0; i < paths.size(); i++) {
paths.get(i).reset();
paths.get(i).moveTo(getWidth()-pandY, getHeight() / 2);
}
for (float j = getWidth()*11/12 - 1; j >= getWidth()/12; j -= fineness) {
float i = j-getWidth()/12;
//這邊必須保證起始點和終點的時候amplitude = 0;
amplitude = 4 * volume *i / getWidth() - 4 * volume * i / getWidth() * i/getWidth()*12/10;
for (int n = 1; n <= paths.size(); n++) {
float sin = amplitude * (float) Math.sin((i - Math.pow(1.22, n)) * Math.PI / 180 - translateX);
paths.get(n - 1).lineTo(j, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY));
}
}
for (int n = 0; n < paths.size(); n++) {
if (n == paths.size() - 1) {
mPaint.setAlpha(255);
} else {
mPaint.setAlpha(n * 130 / paths.size());
}
if (mPaint.getAlpha() > 0) {
canvas.drawPath(paths.get(n), mPaint);
}
}
canvas.restore();
}
public void start(){
//重置計時器顯示的時間
canSetVolume = true;
canDrawProgress = true;
progress = 0;
countdownTime2 = countdownTime;
//啟動定時器
timeTimer.schedule(timeTask = new TimerTask() {
public void run() {
Message msg = new Message();
msg.what = 1;
mHandler.sendMessage(msg);
}
}, 1000, 1000);
progressTimer.schedule(progressTask = new TimerTask() {
public void run() {
Message msg = new Message();
msg.what = 2;
mHandler.sendMessage(msg);
}
},0,5);
}
private void lineChange() {
if (lastTime == 0) {
lastTime = System.currentTimeMillis();
translateX += 5;
} else {
if (System.currentTimeMillis() - lastTime > lineSpeed) {
lastTime = System.currentTimeMillis();
translateX += 5;
} else {
return;
}
}
if (volume < targetVolume && isSet) {
volume += getHeight() / 30;
} else {
isSet = false;
if (volume <= 10) {
volume = 10;
} else {
if (volume < getHeight() / 30) {
volume -= getHeight() / 60;
} else {
volume -= getHeight() / 30;
}
}
}
}
public void setVolume(int volume) {
if(volume >100)
volume = volume/100;
volume = volume*2/5;
if(!canSetVolume)
return;
if (volume > maxVolume * sensibility / 30) {
isSet = true;
this.targetVolume = getHeight() * volume / 3 / maxVolume;
Log.d(TAG,"targetVolume: "+targetVolume);
}
}
interface OnCountDownListener{
void onCountDown();
}
public void setOnCountDownListener(OnCountDownListener listener){
this.listener = listener;
}
public void setCountdownTime(int countdownTime) {
this.countdownTime = countdownTime;
this.countdownTime2 = countdownTime;
postInvalidate();
}
public void cancel(){
listener.onCountDown();
canSetVolume = false;
timeTask.cancel();
targetVolume = 1;
postInvalidate();
progressTask.cancel();
}
}
MainActivity呼叫
public class MainActivity extends AppCompatActivity implements View.OnTouchListener,View.OnClickListener{
private RecordView mRecorfView;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int db = (int) (Math.random()*100);
mRecorfView.setVolume(db);
}
};
private int nowModel = RecordView.MODEL_RECORD;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt_touchme).setOnTouchListener(this);
mRecorfView = (RecordView) findViewById(R.id.recordView);
mRecorfView.setCountdownTime(9);
mRecorfView.setModel(RecordView.MODEL_RECORD);
findViewById(R.id.bt_module).setOnClickListener(this);
}
private TimerTask timeTask;
private Timer timeTimer = new Timer(true);
@Override
public void onClick(View v) {
if(nowModel == MODEL_PLAY){
mRecorfView.setModel(RecordView.MODEL_RECORD);
nowModel = RecordView.MODEL_RECORD;
}else{
mRecorfView.setModel(MODEL_PLAY);
nowModel = MODEL_PLAY;
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
mRecorfView.start();
timeTimer.schedule(timeTask = new TimerTask() {
public void run() {
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}
}, 20, 20);
mRecorfView.setOnCountDownListener(new RecordView.OnCountDownListener() {
@Override
public void onCountDown() {
Toast.makeText(MainActivity.this,"計時結束啦~~",Toast.LENGTH_SHORT).show();
}
});
}else if(event.getAction() == MotionEvent.ACTION_UP){
mRecorfView.cancel();
}
return false;
}
}
activity_main佈局xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context="com.example.recordinganimation.MainActivity">
<com.example.recordinganimation.RecordView
android:id="@+id/recordView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
app:model="play_model" />
<Button
android:id="@+id/bt_touchme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:text="按住我" />
<Button
android:id="@+id/bt_module"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/bt_touchme"
android:layout_centerHorizontal="true"
android:layout_marginBottom="18dp"
android:text="切換模式" />
</RelativeLayout>
資源設定:
(1)view選項設定
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="recordView">
<attr name="hintText" format="string"/>
<attr name="playHintText" format="string"/>
<attr name="timeTextColor" format="reference"/>
<attr name="hintTextSize" format="dimension"/>
<attr name="middleLineHeight" format="dimension"/>
<attr name="progressSrc" format="reference"/>
<attr name="middleLineColor" format="reference"/>
<attr name="unit" format="string"/>
<attr name="model">
<enum name="record_model" value="1"/>
<enum name="play_model" value="2"/>
</attr>
</declare-styleable>
</resources>
(2)colors.xml新增顏
<color name="RoundColor">#DAF6FE</color>
<color name="TimeTextColor">#FF84AD</color>
<color name="RoundFillColor">#45C3E5</color>
<color name="RoundHintTextColor">#666666</color>
(3)圖片資源
新增到mipmap-xhdpi裡面,名字為light_blue.png