Android自定义波浪加载圆形进度条——(自定义控件 一)

本文详细介绍了一种自定义波浪形圆形进度加载控件的实现过程,包括绘制圆形背景、进度条及波浪曲线的方法,并提供了完整的源码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

自定义控件—— 波浪形状圆形进度加载

时间管理的基础是精力管理,精力的高低、正负分影响到我们的效率

而时间是无法管理的,能够管理的只有自己,透过管理自己的习惯,管理自己的事件来达成对时间的管理。

而在每一天中,人生不丰于做多少事,而在于把重要的事情专注做、用心做,把它做到极致。

1、效果简阅

2、实现思路设计

3、初始化操作

private void init(Context context) {

        //绘制圆形的 Paint
        mCicrlPaint = new Paint();
        mCicrlPaint.setAntiAlias(true);
        mCicrlPaint.setColor(Color.BLUE);

        //绘制进度的 Paint
        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true);
        mProgressPaint.setColor(Color.RED);
        //设置只绘制重叠的部分
        mProgressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));


        //绘制文字的 Paint
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(20);


        //绘制进度的Path
        mPath = new Path();

        /**
         * 手势识别监听
         */
        final GestureDetector gestureDetector = new GestureDetector(listener);
        /**
         * 将触摸识别事件传递给 GestureDetector
         */
        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return gestureDetector.onTouchEvent(event);
            }
        });

        this.setClickable(true);


        //设置布局加载监听 
        this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //更新控件的大小
                viewHeight = ProgressView.this.getHeight();
                viewWidth = ProgressView.this.getWidth();
                //创建 bitmap 与 canvas
                bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
                mCanvas = new Canvas(bitmap);
                /**
                 * 更新默认设置
                 */
                updateDefaulBuildValue();
            }
        });

    }


    GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
        /**
         * 单击点击监听
         * @param e
         * @return
         */
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            System.out.println("sing click ");
            if (clickListener != null) {
                clickListener.onSingleClick(e);
                return true;
            }
            return super.onSingleTapConfirmed(e);
        }

        /**
         * 双击点击监听
         * @param e
         * @return
         */
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            if (clickListener != null) {
                clickListener.onDoubleClick(e);
            }
            return super.onDoubleTap(e);
        }

        /**
         * 常按事件监听
         * @param e
         */
        @Override
        public void onLongPress(MotionEvent e) {
            if (clickListener != null) {
                clickListener.onLongPressClick(e);
            }
            super.onLongPress(e);
        }
    };

同时设置了点击事件的监听接口回调


    /**
     * 控件 点击事件监听 回调
     */
    public interface OnWaveProgressClickListener {
        /**
         * 单击回调接口
         * @param e
         */
        public void onSingleClick(MotionEvent e);
        /**
         * 双击回调接口
         * @param e
         */
        public void onDoubleClick(MotionEvent e);
        /**
         * 找按回调接口
         * @param e
         */
        public void onLongPressClick(MotionEvent e);
    }

    private OnWaveProgressClickListener clickListener;


    /**
     * 设置控件的点击事件
     *
     * @param listener
     */
    public void setWaveOnClickListener(OnWaveProgressClickListener listener) {
        this.clickListener = listener;
    }

4、测量


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        /**
         * 绘制的为正方形进度条,测量比较较小的长度为圆形的直径长度
         */
        int width;
        int height;
        width = Math.min(widthSize, heightSize);
        height = Math.min(widthSize, heightSize);
        
        setMeasuredDimension(width, height);
    }

5、绘制

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制整体圆形
        mCanvas.drawCircle(viewWidth / 2, viewWidth / 2, viewWidth / 2, mCicrlPaint);
        //绘制进度图形
        mPath.reset();
        //进度图形的高度   随着加载进度的改变
        float y = (1 - (float) currentProgress / maxProgress) * viewHeight;
        mPath.moveTo(viewWidth, y);
        mPath.lineTo(viewWidth, viewHeight);
        mPath.lineTo(0, viewHeight);
        mPath.lineTo(0, y);
//
        /**
         * 计算振幅的比例
         * 振幅 的大小 是随着进度的大小动态改变的
         */
        int amplitude = (int) ((1 - (float) currentProgress / maxProgress) * mDefaulAmplitude);
        if (currentProgress % 2 == 0) {
            for (int i = 0; i < mquadLineDrawCount; i++) {
                mPath.rQuadTo(mDefaulPriodic / 2, -amplitude, mDefaulPriodic, 0);
                mPath.rQuadTo(mDefaulPriodic / 2, amplitude, mDefaulPriodic, 0);
            }
        } else {
            for (int i = 0; i < mquadLineDrawCount; i++) {
                mPath.rQuadTo(mDefaulPriodic / 2, amplitude, mDefaulPriodic, 0);
                mPath.rQuadTo(mDefaulPriodic / 2, -amplitude, mDefaulPriodic, 0);
            }
        }

        mPath.close();

        mCanvas.drawPath(mPath, mProgressPaint);
//
        //绘制 中间显示的百分比文字
        String text = "" + (int) (((float) currentProgress / maxProgress) * 100);

        //获取绘制文字的宽度
        float textWidth = mTextPaint.measureText(text);
        //获取绘制文字的高度
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        //Descent是baseline之下至字符最低处的距离
        //Ascent是baseline之上至字符最高处的距离
        //ascent + descent 就是测量文字的高度
        float textHeight = fontMetrics.ascent + fontMetrics.descent;

        //计算绘制中间显示进度文字的坐标
        float textY = viewHeight / 2 - textHeight / 2;
        float textX = viewWidth / 2 - textWidth / 2;

        //绘制显示进度的文字
        mCanvas.drawText(text, textX, textY, mTextPaint);

        canvas.drawBitmap(bitmap, 0, 0, null);
    }

绘制分析

5.1绘制圆形背景

234355_6Y20_2447911.png

5.2绘制进度

234438_9T2P_2447911.png

5.3绘制波浪曲线

曲线分析 一

234636_aHiT_2447911.png

曲线分析 二

234718_oudE_2447911.png

曲线分析三

234753_cc9W_2447911.png

 

绘制:

 /**
         * 计算振幅的比例
         * 振幅 的大小 是随着进度的大小动态改变的
         */
        int amplitude = (int) ((1 - (float) currentProgress / maxProgress) * mDefaulAmplitude);
        if (currentProgress % 2 == 0) {
            for (int i = 0; i < mquadLineDrawCount; i++) {
                mPath.rQuadTo(mDefaulPriodic / 2, -amplitude, mDefaulPriodic, 0);
                mPath.rQuadTo(mDefaulPriodic / 2, amplitude, mDefaulPriodic, 0);
            }
        } else {
            for (int i = 0; i < mquadLineDrawCount; i++) {
                mPath.rQuadTo(mDefaulPriodic / 2, amplitude, mDefaulPriodic, 0);
                mPath.rQuadTo(mDefaulPriodic / 2, -amplitude, mDefaulPriodic, 0);
            }
        }

效果 

234936_OuEo_2447911.png

上面的图形中绘制了四组正反曲线,也就是说分别绘制了四个开口向上的曲线,四个开口向下的曲线,

这里采用的绘制思想是每一次都 绘制一个开口向上的曲线 和一个开口向下的曲线,然后形成一个完整的类似正弦曲线的线形,这里绘制了四次,也就是在FOR循环中循环了四次,具体的循环次数是不定的,依据控件的大小来动态设置

235015_B0TF_2447911.png

 

5.4绘制显示文本 


        //获取绘制文字的宽度
        float textWidth = mTextPaint.measureText(text);
        //获取绘制文字的高度
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        //Descent是baseline之下至字符最低处的距离
        //Ascent是baseline之上至字符最高处的距离
        //ascent + descent 就是测量文字的高度
        float textHeight = fontMetrics.ascent + fontMetrics.descent;

        //计算绘制中间显示进度文字的坐标
        float textY = viewHeight / 2 - textHeight / 2;
        float textX = viewWidth / 2 - textWidth / 2;

        //绘制显示进度的文字
        mCanvas.drawText(text, textX, textY, mTextPaint);

其中涉及到一些文本测量

235424_rX1D_2447911.png

 

6、完整源码

package com.animation.androidlongs.a360animationapplication.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;

/**
 * Created by androidlongs on 16/8/9.
 */
public class WaveProgressView extends View {

    private Paint mCicrlPaint;
    private Paint mProgressPaint;
    private Paint mTextPaint;
    private Canvas mCanvas;
    private Path mPath;
    private Bitmap bitmap;

    public WaveProgressView(Context context) {
        super(context);
        init(context);
    }


    public WaveProgressView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public WaveProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

        //绘制圆形的 Paint
        mCicrlPaint = new Paint();
        mCicrlPaint.setAntiAlias(true);
        mCicrlPaint.setColor(Color.BLUE);

        //绘制进度的 Paint
        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true);
        mProgressPaint.setColor(Color.RED);
        //设置只绘制重叠的部分
        mProgressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));


        //绘制文字的 Paint
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(20);


        //绘制进度的Path
        mPath = new Path();

        /**
         * 手势识别监听
         */
        final GestureDetector gestureDetector = new GestureDetector(listener);
        /**
         * 将触摸识别事件传递给 GestureDetector
         */
        this.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return gestureDetector.onTouchEvent(event);
            }
        });

        this.setClickable(true);


        //设置布局加载监听 
        this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //更新控件的大小
                viewHeight = WaveProgressView.this.getHeight();
                viewWidth = WaveProgressView.this.getWidth();
                //创建 bitmap 与 canvas
                bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
                mCanvas = new Canvas(bitmap);
                /**
                 * 更新默认设置
                 */
                updateDefaulBuildValue();
            }
        });

    }


    GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
        /**
         * 单击点击监听
         * @param e
         * @return
         */
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            System.out.println("sing click ");
            if (clickListener != null) {
                clickListener.onSingleClick(e);
                return true;
            }
            return super.onSingleTapConfirmed(e);
        }

        /**
         * 双击点击监听
         * @param e
         * @return
         */
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            if (clickListener != null) {
                clickListener.onDoubleClick(e);
            }
            return super.onDoubleTap(e);
        }

        /**
         * 常按事件监听
         * @param e
         */
        @Override
        public void onLongPress(MotionEvent e) {
            if (clickListener != null) {
                clickListener.onLongPressClick(e);
            }
            super.onLongPress(e);
        }
    };


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        /**
         * 绘制的为正方形进度条,测量比较较小的长度为圆形的直径长度
         */
        int width;
        int height;
        width = Math.min(widthSize, heightSize);
        height = Math.min(widthSize, heightSize);

        setMeasuredDimension(width, height);
    }

    /**
     * 默认控件的 宽 高
     * 默认的 当前的进度    总的进度
     */
    public int viewWidth = 150;
    public int viewHeight = 150;
    public int currentProgress = 0;
    public int maxProgress = 100;

    /**
     * 计算振幅大小 所用的比例数 默认为10
     */
    private int mAplitudeCunt = 10;
    /**
     * 默认振幅的大小 为控件高度的 1/10
     */
    private int mDefaulAmplitude = viewHeight / mAplitudeCunt;

    /**
     * 计算周期长度使用的比例数 默认为8
     */
    private int mPriodicCount = 8;
    /**
     * 曲线默认的周期长度
     */
    private int mDefaulPriodic = viewHeight / mPriodicCount;

    /**
     * 曲线的绘制次数
     */
    private int mquadLineDrawCount = (int) ((float) viewWidth / mDefaulPriodic);


    /**
     * 更新默认的设置
     */
    private void updateDefaulBuildValue() {
        if (viewHeight < 400) {
            mPriodicCount = 8;
            mAplitudeCunt = 10;
        } else if (viewHeight < 600 && viewHeight >= 400) {
            mPriodicCount = 14;
            mAplitudeCunt = 16;
        } else if (viewHeight < 1000 && viewHeight >= 600) {
            mPriodicCount = 20;
            mAplitudeCunt = 20;
        } else if (viewHeight < 1400 && viewHeight >= 100) {
            mAplitudeCunt = 24;
            mPriodicCount = 26;
        } else {
            mPriodicCount = 30;
            mAplitudeCunt = 30;
        }
        /**
         * 默认振幅的大小 为控件高度的 1/10
         */
        mDefaulAmplitude = viewHeight / mAplitudeCunt;
        /**
         * 曲线默认的周期长度
         */
        mDefaulPriodic = viewHeight / 8;

        /**
         * 曲线的绘制次数
         */
        mquadLineDrawCount = (int) ((float) viewWidth / mDefaulPriodic);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制整体圆形
        mCanvas.drawCircle(viewWidth / 2, viewWidth / 2, viewWidth / 2, mCicrlPaint);
        //绘制进度图形
        mPath.reset();
        //进度图形的高度   随着加载进度的改变
        float y = (1 - (float) currentProgress / maxProgress) * viewHeight;
        mPath.moveTo(viewWidth, y);
        mPath.lineTo(viewWidth, viewHeight);
        mPath.lineTo(0, viewHeight);
        mPath.lineTo(0, y);
//
        /**
         * 计算振幅的比例
         * 振幅 的大小 是随着进度的大小动态改变的
         */
        int amplitude = (int) ((1 - (float) currentProgress / maxProgress) * mDefaulAmplitude);
        if (currentProgress % 2 == 0) {
            for (int i = 0; i < mquadLineDrawCount; i++) {
                mPath.rQuadTo(mDefaulPriodic / 2, -amplitude, mDefaulPriodic, 0);
                mPath.rQuadTo(mDefaulPriodic / 2, amplitude, mDefaulPriodic, 0);
            }
        } else {
            for (int i = 0; i < mquadLineDrawCount; i++) {
                mPath.rQuadTo(mDefaulPriodic / 2, amplitude, mDefaulPriodic, 0);
                mPath.rQuadTo(mDefaulPriodic / 2, -amplitude, mDefaulPriodic, 0);
            }
        }

        mPath.close();

        mCanvas.drawPath(mPath, mProgressPaint);
//
        //绘制 中间显示的百分比文字
        String text = "" + (int) (((float) currentProgress / maxProgress) * 100);

        //获取绘制文字的宽度
        float textWidth = mTextPaint.measureText(text);
        //获取绘制文字的高度
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        //Descent是baseline之下至字符最低处的距离
        //Ascent是baseline之上至字符最高处的距离
        //ascent + descent 就是测量文字的高度
        float textHeight = fontMetrics.ascent + fontMetrics.descent;

        //计算绘制中间显示进度文字的坐标
        float textY = viewHeight / 2 - textHeight / 2;
        float textX = viewWidth / 2 - textWidth / 2;

        //绘制显示进度的文字
        mCanvas.drawText(text, textX, textY, mTextPaint);

        canvas.drawBitmap(bitmap, 0, 0, null);
    }

    /**
     * 控件 点击事件监听 回调
     */
    public interface OnWaveProgressClickListener {
        /**
         * 单击回调接口
         *
         * @param e
         */
        public void onSingleClick(MotionEvent e);

        /**
         * 双击回调接口
         *
         * @param e
         */
        public void onDoubleClick(MotionEvent e);

        /**
         * 找按回调接口
         *
         * @param e
         */
        public void onLongPressClick(MotionEvent e);
    }

    private OnWaveProgressClickListener clickListener;


    /**
     * 控件 加载进度回调接口
     */
    public interface OnWaveProgressListener {
        public void onProgresUpdate(int progress);

        public void onFinish();
    }

    private OnWaveProgressListener progressListener;

    /**
     * 设置更新当前进度
     *
     * @param progress
     */
    public void updateProgress(int progress) {
        if (progress <= 0) {
            progress = 0;
        } else if (progress > this.maxProgress) {
            progress = maxProgress;
        }
        this.currentProgress = progress;
        //进度更新接口回调
        if (progressListener != null) {
            progressListener.onProgresUpdate(currentProgress);
        }
        if (progress == maxProgress) {
            if (progressListener != null) {
                progressListener.onFinish();
            }
        }
        //重绘
        invalidate();
    }

    /**
     * 设置最大进度
     *
     * @param max
     */
    public void setMaxProgress(int max) {
        if (max <= 0) {
            max = 100;
        }
        this.setMaxProgress(max);
    }

    /**
     * 设置控件的点击事件
     *
     * @param listener
     */
    public void setWaveOnClickListener(OnWaveProgressClickListener listener) {
        this.clickListener = listener;
    }

    /**
     * 加载进度监听设置
     *
     * @param listener
     */
    public void setWaveOnProgressListener(OnWaveProgressListener listener) {
        this.progressListener = listener;
    }

}

7、Activity中的使用

package com.animation.androidlongs.a360animationapplication;

import android.content.Intent;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;

import com.animation.androidlongs.a360animationapplication.view.WaveProgressView;

public class MainActivity extends AppCompatActivity {

    private WaveProgressView customWaveProgressView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, FloatViewService.class);
        //startService(intent);

        customWaveProgressView = (WaveProgressView) findViewById(R.id.progress);

        handler.postDelayed(runnable,2000);

        //设置点击事件监听
        customWaveProgressView.setWaveOnClickListener(new WaveProgressView.OnWaveProgressClickListener() {
            @Override
            public void onSingleClick(MotionEvent e) {
                System.out.println("onSingleClick");
            }

            @Override
            public void onDoubleClick(MotionEvent e) {
                System.out.println("onDoubleClick");
            }

            @Override
            public void onLongPressClick(MotionEvent e) {
                System.out.println("onLongPressClick");
            }
        });
        //设置进度更新监听
        customWaveProgressView.setWaveOnProgressListener(new WaveProgressView.OnWaveProgressListener() {
            @Override
            public void onProgresUpdate(int progress) {
                
            }

            @Override
            public void onFinish() {

            }
        });
    }

    private Handler handler = new Handler();

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {

            currentNum++;
            customWaveProgressView.updateProgress(currentNum);

            if (currentNum<maxNum){
                handler.postDelayed(runnable,200);
            }else {
                handler.removeCallbacks(runnable);
                currentNum = 0;
            }
        }
    };



    private int maxNum = 100;
    private int currentNum =0;
}

 

 

 

 

转载于:https://my.oschina.net/u/2447911/blog/730908

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值