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)图片资源

添加到mipmap-xhdpi里面,名字为light_blue.png

参考链接:http://blog.csdn.net/qq_25867141/article/details/53116545

1 thought on “Android动画之——圆形进度条加波浪线

发表回复

您的电子邮箱地址不会被公开。

粤ICP备17041560号-2