1. 程式人生 > >Android動畫之——圓形進度條加波浪線

Android動畫之——圓形進度條加波浪線

效果圖
效果圖

圓形進度條

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)圖片資源
light_blue

新增到mipmap-xhdpi裡面,名字為light_blue.png