天天看點

Android 水波紋顯示進度效果

關于水波紋的效果想必大家見的已經很多了,我就在這裡再啰嗦一次,為了加深自己的印象。先來看看效果圖

Android 水波紋顯示進度效果

關于這個效果的實作不必想的太過複雜了,要想實作這個效果,我們還需要了解一下PorterDuff及Xfermode

Android 水波紋顯示進度效果

關于上面的這張圖想必大家也見過很多次了。這其實就是PorterDuff的16種模式。效果想比大家已經見到了,下面我們就來一一了解如何使用。

PorterDuff.Mode.CLEAR (所繪制不會送出到畫布上)

PorterDuff.Mode.SRC(顯示上層繪制圖檔)

PorterDuff.Mode.DST(顯示下層繪制圖檔)

PorterDuff.Mode.SRC_OVER(正常繪制顯示,上下層繪制疊蓋)

PorterDuff.Mode.DST_OVER(上下層都顯示。下層居上顯示)

PorterDuff.Mode.SRC_IN(取兩層繪制交集。顯示上層)

PorterDuff.Mode.DST_IN(取兩層繪制交集。顯示下層)

PorterDuff.Mode.SRC_OUT( 取上層繪制非交集部分)

PorterDuff.Mode.DST_OUT(取下層繪制非交集部分)

PorterDuff.Mode.SRC_ATOP(取下層非交集部分與上層交集部分)

PorterDuff.Mode.DST_ATOP(取上層非交集部分與下層交集部分)

PorterDuff.Mode.XOR( 異或:去除兩圖層交集部分)

PorterDuff.Mode.DARKEN( 取兩圖層全部區域,交集部分顔色加深)

PorterDuff.Mode.LIGHTEN(取兩圖層全部,點亮交集部分顔色)

PorterDuff.Mode.MULTIPLY(取兩圖層交集部分疊加後顔色)

PorterDuff.Mode.SCREEN( 取兩圖層全部區域,交集部分變為透明色)

Xfermode有三個子類 :

AvoidXfermode 指定了一個顔色和容差,強制Paint避免在它上面繪圖(或者隻在它上面繪圖)。

PixelXorXfermode 當覆寫已有的顔色時,應用一個簡單的像素異或操作。

PorterDuffXfermode 這是一個非常強大的轉換模式,使用它可以使用圖像合成的上圖中的任意一種來控制Paint如何與已有的Canvas圖像進行互動。要應用轉換模式,可以使用setXferMode方法

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));

這些隻能夠顯示一些合成的效果也就是上面的16種的任意一種效果而已,要想實作水波紋的效果還是不夠的,我們還需要借助于貝塞爾曲線來實作水波效果。我們使用到的是Path類的quadTo(x1, y1, x2, y2)方法,屬于二階貝塞爾曲線,使用一張圖來展示二階貝塞爾曲線,這裡的(x1,y1)是控制點,(x2,y2)是終止點,起始點預設是Path的起始點(0,0)。關于使用貝塞爾曲線來實作水波效果的原理就是:通過for循環畫出兩個波紋,我們以WL代表水波紋的長度。需要波紋的-WL點、-3/4*WL點、-1/2*WL、-1/4*WL四個點,通過path的quadTo畫出,并無限重複。實作的效果其實也就是我們平時的正弦效果。那麼我們也需要了解一下path的使用。先來看一下效果圖

Android 水波紋顯示進度效果

實作代碼為

public class WaveView extends View {
        private Paint mPaint;
        private Path mPath;
    private Point assistPoint;
        public WaveView(Context context) {
            super(context);
        }
        public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        public WaveView(Context context, AttributeSet attrs) {
            super(context, attrs);
            mPath = new Path();
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            assistPoint = new Point(, );
            mPaint.setColor(Color.RED);
            mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPath.reset();
            mPath.moveTo(, );
            mPath.quadTo(assistPoint.x, assistPoint.y, , );
            canvas.drawPath(mPath, mPaint);
        }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 case MotionEvent.ACTION_MOVE:
                     assistPoint.x = (int) event.getX();
                     assistPoint.y = (int) event.getY();
                  invalidate();
                  break;
              }
          return true;
    }
}
           

先來說一下mPath.moveTo(50, 300);這個方法的作用是将起點移動到螢幕坐标為(50, 300)的位置。mPath.quadTo(assistPoint.x, assistPoint.y, 450, 300);這個方法就是重點了,對應的源碼為

public void quadTo(float x1, float y1, float x2, float y2) {
        isSimplePath = false;
        native_quadTo(mNativePath, x1, y1, x2, y2);
    }
           

第一個坐标是對應的控制點的坐标(assistPoint.x, assistPoint.y),第二個坐标是終點坐标也就是我們看到的水準線的終點位置坐标。上圖之是以會出現移動的效果就是因為控制點的位置随着滑鼠的位置在移動而産生的。

下面我們再來看一個效果圖

Android 水波紋顯示進度效果

這個圖形的實作代碼為

public class WaveViewTest extends View {
    private int width;
    private int height;

    private Path mPath;
    private Paint mPathPaint;

    private int mWaveHight = ;//水波紋的高度
    private int mWaveWidth = ;//水波紋的寬度
    private String mWaveColor = "#FFFFFF";

    private int maxProgress = ;
    private int currentProgress = ;
    private float currentY;

    public WaveViewTest(Context context) {
        this(context,null,);
    }
    public WaveViewTest(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }
    public WaveViewTest(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void setCurrent(int currentProgress,String currentText) {
        this.currentProgress = currentProgress;
    }
    public void setWaveColor(String mWaveColor){
        this.mWaveColor = mWaveColor;
    }

    private void init() {
        mPath = new Path();
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);
    }

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

        width = MeasureSpec.getSize(widthMeasureSpec);
        currentY = height = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        float currentMidY = height*(maxProgress-currentProgress)/maxProgress;
        if(currentY>currentMidY){
            currentY = currentY - (currentY-currentMidY)/;
        }
        mPath.reset();
        mPath.moveTo(,);
        //顯示的區域内的水波紋的數量
        int waveNum = width/mWaveWidth;
        int num = ;
        for(int i =;i<waveNum;i++){
            mPath.quadTo(mWaveWidth*(num+),-mWaveHight,mWaveWidth*(num+),);
            mPath.quadTo(mWaveWidth*(num+),+mWaveHight,mWaveWidth*(num+),);
            num+=;
        }
        canvas.drawPath(mPath, mPathPaint);
    }
}
           

再來一個效果圖以及實作代碼

Android 水波紋顯示進度效果
public class WaveViewTest extends View {
    private int width;
    private int height;
    private Path mPath;
    private Paint mPathPaint;
    private String mWaveColor = "#FF49C12E";


    public WaveViewTest(Context context) {
        this(context,null,);
    }
    public WaveViewTest(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }
    public WaveViewTest(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    private void init() {
        mPath = new Path();
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        mPath.reset();
        mPath.moveTo(,);
        mPath.lineTo(,height);
        mPath.lineTo(width,height);
        mPath.lineTo(width,);
        mPath.close();
        canvas.drawPath(mPath, mPathPaint);
    }
}
           
Android 水波紋顯示進度效果

上圖是我們看到的一個靜态的圖,但是距離我們實作的效果已經很接近了,實作代碼為

public class WaveViewTest extends View {
    private int width;
    private int height;
    private Path mPath;
    private Paint mPathPaint;

    private float mWaveWidth = f;//水波紋的寬度
    private String mWaveColor = "#FF49C12E";

    public WaveViewTest(Context context) {
        this(context,null,);
    }
    public WaveViewTest(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }
    public WaveViewTest(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    private void init() {
        mPath = new Path();
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);
    }

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

        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(createImage(), , , null);

    }
    private Bitmap createImage()
    {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap bmp = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmp);
        mPath.reset();
        mPath.moveTo(,);
        //顯示的區域内的水波紋的數量
        int waveNum = width/((int)mWaveWidth);
        int num = ;
        for(int i =;i<waveNum;i++){
            mPath.quadTo(mWaveWidth*(num+),-,mWaveWidth*(num+),);
            mPath.quadTo(mWaveWidth*(num+),+,mWaveWidth*(num+),);
            num+=;
        }
        mPath.lineTo(width,height);
        mPath.lineTo(,height);
        mPath.close();
        canvas.drawPath(mPath, mPathPaint);
        return bmp;
    }
}
           
Android 水波紋顯示進度效果

這個效果的産生其實就是上面的圖形通過for循環産生移動距離産生的,代碼如下

public class WaveView extends View {
    private int width;
    private int height;

    private Path mPath;
    private Paint mPathPaint;

    private float mWaveHight = f;
    private float mWaveWidth = f;//水波紋的寬度
    private String mWaveColor = "#FFFFFF";
    private  int  mWaveSpeed = ;


    private int maxProgress = ;
    private int currentProgress = ;
    private float currentY;

    private float distance = ;
    private int RefreshGap = ;

    private static final int INVALIDATE = ;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case INVALIDATE:
                    invalidate();
                    sendEmptyMessageDelayed(INVALIDATE,RefreshGap);
                    break;
            }
        }
    };

    public WaveView(Context context) {
        this(context,null,);
    }
    public WaveView(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }
    public WaveView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void setCurrent(int currentProgress,String currentText) {
        this.currentProgress = currentProgress;
    }
    public void setWaveColor(String mWaveColor){
        this.mWaveColor = mWaveColor;
    }

    private void init() {
        mPath = new Path();
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);
        handler.sendEmptyMessageDelayed(INVALIDATE,);
    }

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

        width = MeasureSpec.getSize(widthMeasureSpec);
        currentY = height = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
            canvas.drawBitmap(createImage(), , , null);
    }
    private Bitmap createImage()
    {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap bmp = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(bmp);

        float currentMidY = height*(maxProgress-currentProgress)/maxProgress;
        if(currentY>currentMidY){
            currentY = currentY - (currentY-currentMidY)/;
        }
        mPath.reset();
        //之是以0-distance是因為有原點向上增加的
        mPath.moveTo(-distance,currentY);
        //顯示的區域内的水波紋的數量
        int waveNum = width/((int)mWaveWidth);
        int num = ;
        for(int i =;i<waveNum;i++){
            mPath.quadTo(mWaveWidth*(num+)-distance,currentY-mWaveHight,mWaveWidth*(num+)-distance,currentY);
            mPath.quadTo(mWaveWidth*(num+)-distance,currentY+mWaveHight,mWaveWidth*(num+)-distance,currentY);
            num+=;
        }
        distance +=mWaveWidth/mWaveSpeed;
        distance = distance%(mWaveWidth*);
        mPath.lineTo(width,height);
        mPath.lineTo(,height);
        mPath.close();
        canvas.drawPath(mPath, mPathPaint);
        return bmp;
    }
}
           

通過對比代碼你會發現,其實就是通過移動定時重新整理不停的調用onDraw方法,通過distance 的不斷變化而産生的動畫效果。如果想要實作我們最上面的動畫效果還需要借助于PorterDuff及Xfermode,關于PorterDuff及Xfermode上面已經說過了。剩下的就是它們之間的配合使用了

完整的實作的代碼如下:

自定義view

public class WaveProgressView extends View {
    private int width;
    private int height;

    private Bitmap backgroundBitmap;

    private Path mPath;
    private Paint mPathPaint;

    private float mWaveHight = f;
    private float mWaveWidth = f;
    private String mWaveColor = "#FFFFFF";
    private  int  mWaveSpeed = ;

    private Paint mTextPaint;
    private String currentText = "";
    private String mTextColor = "#FFFFFF";
    private int mTextSize = ;

    private int maxProgress = ;
    private int currentProgress = ;
    private float currentY;

    private float distance = ;
    private int RefreshGap = ;

    private static final int INVALIDATE = ;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case INVALIDATE:
                    invalidate();
                    sendEmptyMessageDelayed(INVALIDATE,RefreshGap);
                    break;
            }
        }
    };

    public WaveProgressView(Context context) {
        this(context,null,);
    }
    public WaveProgressView(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }
    public WaveProgressView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void setCurrent(int currentProgress,String currentText) {
        this.currentProgress = currentProgress;
        this.currentText = currentText;
    }
    public void setWaveColor(String mWaveColor){
        this.mWaveColor = mWaveColor;
    }

    private void init() {

        if(null==getBackground()){
            throw new IllegalArgumentException(String.format("background is null."));
        }else{
            backgroundBitmap = getBitmapFromDrawable(getBackground());
        }

        mPath = new Path();
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setStyle(Paint.Style.FILL);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        handler.sendEmptyMessageDelayed(INVALIDATE,);
    }

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

        width = MeasureSpec.getSize(widthMeasureSpec);
        currentY = height = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if(backgroundBitmap!=null){

            canvas.drawBitmap(createImage(), , , null);
        }
    }
    private Bitmap createImage()
    {
        mPathPaint.setColor(Color.parseColor(mWaveColor));
        mTextPaint.setColor(Color.parseColor(mTextColor));
        mTextPaint.setTextSize(mTextSize);

        mPathPaint.setColor(Color.parseColor(mWaveColor));
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap bmp = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(bmp);

        float currentMidY = height*(maxProgress-currentProgress)/maxProgress;
        if(currentY>currentMidY){
            currentY = currentY - (currentY-currentMidY)/;
        }
        mPath.reset();
        //之是以0-distance是因為有原點向上增加的
        mPath.moveTo(-distance,currentY);
        //顯示的區域内的水波紋的數量
        int waveNum = width/((int)mWaveWidth);
        int num = ;
        for(int i =;i<waveNum;i++){
            mPath.quadTo(mWaveWidth*(num+)-distance,currentY-mWaveHight,mWaveWidth*(num+)-distance,currentY);
            mPath.quadTo(mWaveWidth*(num+)-distance,currentY+mWaveHight,mWaveWidth*(num+)-distance,currentY);
            num+=;
        }
        distance +=mWaveWidth/mWaveSpeed;
        distance = distance%(mWaveWidth*);
        mPath.lineTo(width,height);
        mPath.lineTo(,height);
        mPath.close();
        canvas.drawPath(mPath, mPathPaint);
        int min = Math.min(width,height);
        backgroundBitmap = Bitmap.createScaledBitmap(backgroundBitmap,min,min,false);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));

        canvas.drawBitmap(backgroundBitmap,,,paint);

        canvas.drawText(currentText, width/, height/, mTextPaint);
        return bmp;
    }

    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
        try {
            Bitmap bitmap;
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(, , canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }
}
           

MainActivity.class

public class MainActivity extends Activity {

    private WaveProgressView wpv;
    private static final int FLAG_ONE = ;
    private int max_progress = ;
    private int progress;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            progress++;
            switch (msg.what) {
                case FLAG_ONE:
                    if (progress <= max_progress){
                        wpv.setCurrent(progress, progress + "%");
                        sendEmptyMessageDelayed(FLAG_ONE, );
                     }else {
                        return;
                    }
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    private void initView() {
        wpv = (WaveProgressView) findViewById(R.id.wpv);
        wpv.setWaveColor("#FF49C12E");
        handler.sendEmptyMessageDelayed(FLAG_ONE, );
    }
}
           

布局檔案

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp">
    <com.lyxrobert.waveprogressview.WaveProgressView
        android:id="@+id/wpv"
        android:background="@drawable/bg"
        android:layout_centerInParent="true"
        android:layout_width="230dp"
        android:layout_height="230dp" />
</RelativeLayout>
           

點選下載下傳源碼