<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android自定義View實(shí)現(xiàn)橫向的雙水波紋進(jìn)度條

          共 16752字,需瀏覽 34分鐘

           ·

          2022-06-26 17:07

          先看效果圖:



          網(wǎng)上垂直的水波紋進(jìn)度條很多,但橫向的很少,將垂直的水波紋改為水平的還遇到了些麻煩,現(xiàn)在完善后發(fā)布出來,希望遇到的人少躺點(diǎn)坑。


          思路分析


          整體效果可分為三個(gè),繪制圓角背景和圓角矩形,繪制第一條和第二條水波浪,根據(jù)自定義進(jìn)度變化效果。


          功能實(shí)現(xiàn)


          1、繪制圓角背景和圓角矩形邊框


          圓角矩形邊框:

          private RectF rectBorder;if (rectBorder == null) {    rectBorder = new RectF(0.5f * dp1, 0.5f * dp1, waveActualSizeWidth - 0.5f * dp1, waveActualSizeHeight - 0.5f * dp1);}canvas.drawRoundRect(rectBorder, dp27, dp27, borderPaint);


          我們創(chuàng)建一個(gè)新的畫布,然后在畫布里畫上圓角矩形背景和第一條和第二條水波浪:

          //這里用到了緩存 根據(jù)參數(shù)創(chuàng)建新位圖if (circleBitmap == null) {    circleBitmap = Bitmap.createBitmap(waveActualSizeWidth, waveActualSizeHeight, Bitmap.Config.ARGB_8888);}//以該bitmap為底創(chuàng)建一塊畫布if (bitmapCanvas == null) {    bitmapCanvas = new Canvas(circleBitmap);}// 圓角矩形背景,為了能讓波浪填充完整個(gè)圓形背景if (rectBg == null) {    rectBg = new RectF(0, 0, waveActualSizeWidth, waveActualSizeHeight);}bitmapCanvas.drawRoundRect(rectBg, dp27, dp27, backgroundPaint);//裁剪圖片canvas.drawBitmap(circleBitmap, 0, 0, null);


          2、通過貝塞爾曲線實(shí)現(xiàn)雙水波


          1)實(shí)現(xiàn)第一條水波

          /** * 繪制波浪線 */private Path canvasWavePath() {    //要先清掉路線    wavePath.reset();    //起始點(diǎn)移至(0,0) p0 -p1 的高度隨著進(jìn)度的變化而變化    wavePath.moveTo((currentPercent) * waveActualSizeWidth, -moveDistance);    //最多能繪制多少個(gè)波浪    //其實(shí)也可以用 i < getWidth() ;i+=waveLength來判斷 這個(gè)沒那么完美    //繪制p0 - p1 繪制波浪線 這里有一段是超出View的,在View右邊距的右邊 所以是* 2    for (int i = 0; i < waveNumber * 2; i++) {        wavePath.rQuadTo(waveHeight, waveLength / 2, 0, waveLength);        wavePath.rQuadTo(-waveHeight, waveLength / 2, 0, waveLength);    }    //連接p1 - p2    wavePath.lineTo(0, waveActualSizeHeight);    //連接p2 - p0    wavePath.lineTo(0, 0);    //封閉起來填充    wavePath.close();    return wavePath;}


          moveDistance為水波垂直方向移動的距離。


          waveLength為水波長度,一個(gè)上弧加一個(gè)下弧為一個(gè)波長。


          path的起始點(diǎn)為(0,0)可根據(jù)進(jìn)度動態(tài)改變,然后循環(huán)畫曲線,長度是有幾個(gè)波浪就是多長,然后連接到view高度的位置,最后到(0,0),形成一個(gè)封閉的區(qū)域,這樣就實(shí)現(xiàn)了一個(gè)填充的水波效果。


          2)繪制第二條水波,第二條水波和第一條類似,只是起始點(diǎn)變了:

          /** * 繪制第二層波浪 */private Path canvasSecondPath() {    secondWavePath.reset();    //初始點(diǎn)移動到下方    secondWavePath.moveTo((currentPercent) * waveActualSizeWidth, waveActualSizeHeight + moveDistance);    for (int i = 0; i < waveNumber * 2; i++) {        secondWavePath.rQuadTo(waveHeight, -waveLength / 2, 0, -waveLength);        secondWavePath.rQuadTo(-waveHeight, -waveLength / 2, 0, -waveLength);    }    secondWavePath.lineTo(0, 0);    secondWavePath.lineTo(0, waveActualSizeHeight);    secondWavePath.close();    return secondWavePath;}


          3、設(shè)置動畫使進(jìn)度和水波紋變化

          /** * 設(shè)置進(jìn)度 * * @param currentProgress 進(jìn)度 * @param duration        達(dá)到進(jìn)度需要的時(shí)間 */public void setProgress(int currentProgress, long duration, AnimatorListenerAdapter listenerAdapter) {    float percent = currentProgress * 1f / maxProgress;    this.currentProgress = currentProgress;    //從0開始變化    currentPercent = 0;    moveDistance = 0;    mProgressAnimator = ValueAnimator.ofFloat(0, percent);    //設(shè)置動畫時(shí)間    mProgressAnimator.setDuration(duration);    //讓動畫勻速播放,避免出現(xiàn)波浪平移停頓的現(xiàn)象    mProgressAnimator.setInterpolator(new LinearInterpolator());    mProgressAnimator.addUpdateListener(listener);    mProgressAnimator.addListener(listenerAdapter);    mProgressAnimator.start();
          // 波浪線 startWaveAnimal();}
          /** * 波浪動畫 */private void startWaveAnimal() { //動畫實(shí)例化 if (waveProgressAnimator == null) { waveProgressAnimator = new WaveProgressAnimal(); //設(shè)置動畫時(shí)間 waveProgressAnimator.setDuration(2000); //設(shè)置循環(huán)播放 waveProgressAnimator.setRepeatCount(Animation.INFINITE); //讓動畫勻速播放,避免出現(xiàn)波浪平移停頓的現(xiàn)象 waveProgressAnimator.setInterpolator(new LinearInterpolator()); //當(dāng)前視圖開啟動畫 this.startAnimation(waveProgressAnimator); }}


          其中波浪動畫是通過改變moveDistance的值改變縱坐標(biāo)達(dá)到,進(jìn)度主要是通過改變百分比currentPercent改變波浪的橫坐標(biāo)達(dá)到。


          完整源碼:

          /** * 橫向雙水波浪進(jìn)度條 * * @author jingbin **/public class HorizontalWaveProgressView extends View {
          //繪制波浪畫筆 private Paint wavePaint; //繪制波浪Path private Path wavePath; //波浪的寬度 private final float waveLength; //波浪的高度 private final float waveHeight; //波浪組的數(shù)量 一個(gè)波浪是一低一高 private int waveNumber; //自定義View的波浪寬高 private int waveDefaultWidth; private int waveDefaultHeight; //測量后的View實(shí)際寬高 private int waveActualSizeWidth; private int waveActualSizeHeight; //當(dāng)前進(jìn)度值占總進(jìn)度值的占比 private float currentPercent; //當(dāng)前進(jìn)度值 private int currentProgress; //進(jìn)度的最大值 private int maxProgress; //動畫對象 private WaveProgressAnimal waveProgressAnimator; private ValueAnimator mProgressAnimator; private ValueAnimator mEndAnimator; //波浪平移距離 private float moveDistance = 0; //圓形背景畫筆 private Paint backgroundPaint; // 邊框 private Paint borderPaint; //bitmap private Bitmap circleBitmap; //bitmap畫布 private Canvas bitmapCanvas; //波浪顏色 private final int wave_color; //圓形背景進(jìn)度框顏色 private final int backgroundColor; //進(jìn)度條顯示值監(jiān)聽接口 private UpdateTextListener updateTextListener; //是否繪制雙波浪線 private boolean isShowSecondWave; //第二層波浪的顏色 private final int secondWaveColor; //邊框色 private final int borderColor; //第二層波浪的畫筆 private Paint secondWavePaint; private Path secondWavePath; private int dp1; // 圓角角度 private int dp27;

          public HorizontalWaveProgressView(Context context) { this(context, null); }
          public HorizontalWaveProgressView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); }
          public HorizontalWaveProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //獲取attrs文件下配置屬性 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorizontalWaveProgressView); //獲取波浪寬度 第二個(gè)參數(shù),如果xml設(shè)置這個(gè)屬性,則會取設(shè)置的默認(rèn)值 也就是說xml沒有指定wave_length這個(gè)屬性,就會取Density.dip2px(context,25) waveLength = typedArray.getDimension(R.styleable.HorizontalWaveProgressView_wave_length, DensityUtil.dip2px(context, 25)); //獲取波浪高度 waveHeight = typedArray.getDimension(R.styleable.HorizontalWaveProgressView_wave_height, DensityUtil.dip2px(context, 5)); //獲取波浪顏色 wave_color = typedArray.getColor(R.styleable.HorizontalWaveProgressView_wave_color, Color.parseColor("#B76EFF")); //圓形背景顏色 backgroundColor = typedArray.getColor(R.styleable.HorizontalWaveProgressView_wave_background_color, Color.WHITE); //當(dāng)前進(jìn)度 currentProgress = typedArray.getInteger(R.styleable.HorizontalWaveProgressView_currentProgress, 0); //最大進(jìn)度 maxProgress = typedArray.getInteger(R.styleable.HorizontalWaveProgressView_maxProgress, 100); //是否顯示第二層波浪 isShowSecondWave = typedArray.getBoolean(R.styleable.HorizontalWaveProgressView_second_show, false); //第二層波浪的顏色 secondWaveColor = typedArray.getColor(R.styleable.HorizontalWaveProgressView_second_color, Color.parseColor("#DEBCFF")); //邊框色 borderColor = typedArray.getColor(R.styleable.HorizontalWaveProgressView_border_color, Color.parseColor("#DEBCFF")); //記得把TypedArray回收 //程序在運(yùn)行時(shí)維護(hù)了一個(gè) TypedArray的池,程序調(diào)用時(shí),會向該池中請求一個(gè)實(shí)例,用完之后,調(diào)用 recycle() 方法來釋放該實(shí)例,從而使其可被其他模塊復(fù)用。 //那為什么要使用這種模式呢?答案也很簡單,TypedArray的使用場景之一,就是上述的自定義View,會隨著 Activity的每一次Create而Create, //因此,需要系統(tǒng)頻繁的創(chuàng)建array,對內(nèi)存和性能是一個(gè)不小的開銷,如果不使用池模式,每次都讓GC來回收,很可能就會造成OutOfMemory。 //這就是使用池+單例模式的原因,這也就是為什么官方文檔一再的強(qiáng)調(diào):使用完之后一定 recycle,recycle,recycle typedArray.recycle(); init(context); }

          /** * 初始化一些畫筆路徑配置 */ private void init(Context context) { //設(shè)置自定義View的寬高 waveDefaultWidth = DensityUtil.dip2px(context, 152); waveDefaultHeight = DensityUtil.dip2px(context, 40); dp1 = DensityUtil.dip2px(getContext(), 1); dp27 = DensityUtil.dip2px(getContext(), 27);
          wavePath = new Path(); wavePaint = new Paint(); //設(shè)置畫筆為取交集模式 wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //設(shè)置波浪顏色 wavePaint.setColor(wave_color); //設(shè)置抗鋸齒 wavePaint.setAntiAlias(true);
          //矩形背景 backgroundPaint = new Paint(); backgroundPaint.setColor(backgroundColor); backgroundPaint.setAntiAlias(true);
          //邊框 borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); borderPaint.setColor(borderColor); borderPaint.setAntiAlias(true); borderPaint.setStrokeWidth(dp1); borderPaint.setStyle(Paint.Style.STROKE);
          if (isShowSecondWave) { //是否繪制雙波浪線 secondWavePath = new Path(); //初始化第二層波浪畫筆 secondWavePaint = new Paint(); secondWavePaint.setColor(secondWaveColor); secondWavePaint.setAntiAlias(true); //因?yàn)橐采w在第一層波浪上,且要讓半透明生效,所以選SRC_ATOP模式 secondWavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); }
          //占比一開始設(shè)置為0 currentPercent = currentProgress * 1f / maxProgress; }

          @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //這里用到了緩存 根據(jù)參數(shù)創(chuàng)建新位圖 circleBitmap = Bitmap.createBitmap(waveActualSizeWidth, waveActualSizeHeight, Bitmap.Config.ARGB_8888); //以該bitmap為底創(chuàng)建一塊畫布 bitmapCanvas = new Canvas(circleBitmap); // 繪制背景,為了能讓波浪填充完整個(gè)圓形背景 RectF rectBg = new RectF(0, 0, waveActualSizeWidth, waveActualSizeHeight); bitmapCanvas.drawRoundRect(rectBg, dp27, dp27, backgroundPaint);

          if (isShowSecondWave) { //繪制第二層波浪 bitmapCanvas.drawPath(canvasSecondPath(), secondWavePaint); }//繪制波浪形 bitmapCanvas.drawPath(canvasWavePath(), wavePaint); //裁剪圖片 canvas.drawBitmap(circleBitmap, 0, 0, null); // 繪制邊框 RectF rectBorder = new RectF(0.5f * dp1, 0.5f * dp1, waveActualSizeWidth - 0.5f * dp1, waveActualSizeHeight - 0.5f * dp1); canvas.drawRoundRect(rectBorder, dp27, dp27, borderPaint); }
          /** * 繪制波浪線 */ private Path canvasWavePath() { //要先清掉路線 wavePath.reset(); //起始點(diǎn)移至(0,0) p0 -p1 的高度隨著進(jìn)度的變化而變化 wavePath.moveTo((currentPercent) * waveActualSizeWidth, -moveDistance);// wavePath.moveTo(-moveDistance,(1-currentPercent) * waveActualSize); //最多能繪制多少個(gè)波浪 //其實(shí)也可以用 i < getWidth() ;i+=waveLength來判斷 這個(gè)沒那么完美 //繪制p0 - p1 繪制波浪線 這里有一段是超出View的,在View右邊距的右邊 所以是* 2 for (int i = 0; i < waveNumber * 2; i++) { wavePath.rQuadTo(waveHeight, waveLength / 2, 0, waveLength); wavePath.rQuadTo(-waveHeight, waveLength / 2, 0, waveLength); } //連接p1 - p2 wavePath.lineTo(waveActualSizeWidth, waveActualSizeHeight); //連接p2 - p3 wavePath.lineTo(0, waveActualSizeHeight); //連接p3 - p0 p3-p0d的高度隨著進(jìn)度變化而變化 wavePath.lineTo(0, 0); //封閉起來填充 wavePath.close(); return wavePath; }
          /** * 繪制第二層波浪方法 */ private Path canvasSecondPath() { float secondWaveHeight = waveHeight; secondWavePath.reset(); //移動到右上方,也就是p1點(diǎn) secondWavePath.moveTo((currentPercent) * waveActualSizeWidth, waveActualSizeHeight + moveDistance); //p1 - p0 for (int i = 0; i < waveNumber * 2; i++) { secondWavePath.rQuadTo(secondWaveHeight, -waveLength / 2, 0, -waveLength); secondWavePath.rQuadTo(-secondWaveHeight, -waveLength / 2, 0, -waveLength); } //p3-p0的高度隨著進(jìn)度變化而變化 secondWavePath.lineTo(0, 0); //連接p3 - p2 secondWavePath.lineTo(0, waveActualSizeHeight); secondWavePath.lineTo(waveActualSizeHeight, waveActualSizeWidth); //連接p2 - p1 secondWavePath.lineTo(waveActualSizeWidth, waveActualSizeHeight + moveDistance); //封閉起來填充 secondWavePath.close(); return secondWavePath; }
          @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = measureSize(waveDefaultWidth, widthMeasureSpec); int height = measureSize(waveDefaultHeight, heightMeasureSpec); //把View改為正方形 setMeasuredDimension(width, height); //waveActualSize是實(shí)際的寬高 waveActualSizeWidth = width; waveActualSizeHeight = height; //Math.ceil(a)返回求不小于a的最小整數(shù) // 舉個(gè)例子: // Math.ceil(125.9)=126.0 // Math.ceil(0.4873)=1.0 // Math.ceil(-0.65)=-0.0 //這里是調(diào)整波浪數(shù)量 就是View中能容下幾個(gè)波浪 用到ceil就是一定讓View完全能被波浪占滿 為循環(huán)繪制做準(zhǔn)備 分母越小就約精準(zhǔn) waveNumber = (int) Math.ceil(Double.parseDouble(String.valueOf(waveActualSizeHeight / waveLength / 2))); }
          /** * 返回指定的值 * * @param defaultSize 默認(rèn)的值 * @param measureSpec 模式 */ private int measureSize(int defaultSize, int measureSpec) { int result = defaultSize; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec);
          //View.MeasureSpec.EXACTLY:如果是match_parent 或者設(shè)置定值就 //View.MeasureSpec.AT_MOST:wrap_content if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } return result; }
          //新建一個(gè)動畫類 public class WaveProgressAnimal extends Animation {
          //在繪制動畫的過程中會反復(fù)的調(diào)用applyTransformation函數(shù), // 每次調(diào)用參數(shù)interpolatedTime值都會變化,該參數(shù)從0漸 變?yōu)?,當(dāng)該參數(shù)為1時(shí)表明動畫結(jié)束 @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); //左邊的距離 moveDistance = interpolatedTime * waveNumber * waveLength * 2; //重新繪制 invalidate(); } }
          /** * 直接結(jié)束 * * @param duration 結(jié)束時(shí)間 */ public void setProgressEnd(long duration, AnimatorListenerAdapter listenerAdapter) { // 如果是100會不滿,因?yàn)樵诓▌?/span> if (currentProgress == maxProgress) { // 到底了就從頭開始 currentPercent = 0; } mEndAnimator = ValueAnimator.ofFloat(currentPercent, 1.1f); mEndAnimator.setInterpolator(new DecelerateInterpolator()); mEndAnimator.setDuration(duration); mEndAnimator.addUpdateListener(listener); mEndAnimator.addListener(listenerAdapter); mEndAnimator.start();
          // 波浪線 startWaveAnimal(); }
          /** * 設(shè)置進(jìn)度 * * @param currentProgress 進(jìn)度 * @param duration 達(dá)到進(jìn)度需要的時(shí)間 */ public void setProgress(int currentProgress, long duration, AnimatorListenerAdapter listenerAdapter) { float percent = currentProgress * 1f / maxProgress; this.currentProgress = currentProgress; //從0開始變化 currentPercent = 0; moveDistance = 0; mProgressAnimator = ValueAnimator.ofFloat(0, percent); //設(shè)置動畫時(shí)間 mProgressAnimator.setDuration(duration); //讓動畫勻速播放,避免出現(xiàn)波浪平移停頓的現(xiàn)象 mProgressAnimator.setInterpolator(new LinearInterpolator()); mProgressAnimator.addUpdateListener(listener); mProgressAnimator.addListener(listenerAdapter); mProgressAnimator.start();
          // 波浪線 startWaveAnimal(); }
          /** * 波浪動畫 */ private void startWaveAnimal() { //動畫實(shí)例化 if (waveProgressAnimator == null) { waveProgressAnimator = new WaveProgressAnimal(); //設(shè)置動畫時(shí)間 waveProgressAnimator.setDuration(2000); //設(shè)置循環(huán)播放 waveProgressAnimator.setRepeatCount(Animation.INFINITE); //讓動畫勻速播放,避免出現(xiàn)波浪平移停頓的現(xiàn)象 waveProgressAnimator.setInterpolator(new LinearInterpolator()); //當(dāng)前視圖開啟動畫 this.startAnimation(waveProgressAnimator); } }
          /** * 進(jìn)度的監(jiān)聽 */ ValueAnimator.AnimatorUpdateListener listener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 當(dāng)前進(jìn)度百分比,[0,1] currentPercent = (float) animation.getAnimatedValue(); //這里直接根據(jù)進(jìn)度值顯示 if (updateTextListener != null) { updateTextListener.updateText(currentPercent, maxProgress); } } };

          public interface UpdateTextListener { /** * 提供接口 給外部修改數(shù)值樣式 等 * * @param currentPercent 當(dāng)前進(jìn)度百分比 * @param maxProgress 進(jìn)度條的最大數(shù)值 */ void updateText(float currentPercent, float maxProgress); }
          /** * 設(shè)置監(jiān)聽 */ public void setUpdateTextListener(UpdateTextListener updateTextListener) { this.updateTextListener = updateTextListener;
          }
          /** * 停止動畫,銷毀對象 */ public void stopAnimal() { if (waveProgressAnimator != null) { waveProgressAnimator.cancel(); } if (mProgressAnimator != null && mProgressAnimator.isStarted()) { mProgressAnimator.removeAllListeners(); mProgressAnimator.cancel(); } if (mEndAnimator != null && mEndAnimator.isStarted()) { mEndAnimator.removeAllListeners(); mEndAnimator.cancel(); } }}


          到這里就結(jié)束啦。

          瀏覽 85
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天天天天天天天天干 | 五月天色婷婷综合 | 久久久亚洲AV成人电影 | 久热网站| 天天网综合 |