天天看點

android自定義元件(手機加速球+水面波動效果)

android自定義元件(手機加速球+水面波動效果)

通過上一篇的部落格,相信你對Android中的坐标系和繪制刻度的實作原理有了一個認識(是以這一篇可能沒有那麼詳細。。。),接下來就是另外一部分内容,如何去繪制水波加速球。

自定義View确定一個正方形

public class WaterView extends View {
    private int len;

    public WaterView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //以最小值為正方形的長
        len = Math.min(width, height);
        //設定測量高度和寬度(必須要調用,不然無效果)
        setMeasuredDimension(len, len);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}
           

同樣這裡內建了View,并通過設定測量值,限定空間為正方形。

布局中使用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/ll_parent"
    android:orientation="vertical"
    android:background="@color/colorPrimary"
    android:padding="20dp"
    tools:context="com.example.huaweiview.MainActivity">

    <com.example.huaweiview.WaterView
        android:layout_gravity="center"
        android:background="@color/colorAccent"
        android:layout_width="200dp"
        android:layout_height="300dp" />

</LinearLayout>
           
android自定義元件(手機加速球+水面波動效果)

ok,我們設定的長度和寬度并不一樣,但是他顯示的是一個正方形,并且,根據上一篇部落格的介紹,它是有自己的坐标系的,我們繪制的所有東西都在這個坐标系内,并且依靠它去确定位置。

回憶正餘弦

大家通過查資料和聯想心電圖等可以知道,水波其實就是在繪制一條正弦或者餘弦波,如果讓這條波移動就是

android自定義元件(手機加速球+水面波動效果)

這裡我們使用正弦實作需要如下公式:

y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;

畫圖就少不了要确定不同的點,通過這個公式,我們可以得到Y軸坐标點的值,那麼X軸坐标點的值該如何得到呢?

通過觀察圖我們可以發現,這些點連起來就是一條曲線,也就是說每個點之間的距離是非常小的,是不是可以用,這些所有的點都在X軸上有值,剛好是i(i從0加到len的長度(View的長度也就是圓的直徑))

android自定義元件(手機加速球+水面波動效果)

比如圖中的中間點的坐标(y=0,x=i=len/2)

當然Y值是通過公式得到的,既然有很多點,我們就需要用數組來儲存這些點,水波效果最好是有兩條效果會好些,是以需要個數組:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //以最小值為正方形的長
        len = Math.min(width, height);
        //定義兩個數組,儲存Y值
        firstWaterLine = new float[len];
        secondWaterLine = new float[len];
        //設定測量高度和寬度(必須要調用,不然無效果)

        setMeasuredDimension(len, len);
    }
           

這裡定義了兩個全局數組,用來儲存Y軸值,個數和View長度相等(直徑相等)

然後就是利用公式擷取每個點的Y軸坐标值

@Override
    protected void onDraw(Canvas canvas) {
        // y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;
        // 将周期定為view總寬度
        float mCycleFactorW = (float) ( * Math.PI / len);

        // 得到第一條波的y值
        for (int i = ; i < len; i++) {
            firstWaterLine[i] = (float) ( * Math
                    .sin(mCycleFactorW * i));
        }
        // 得到第二條波的y值(第二條波的初相偏左)
        for (int i = ; i < len; i++) {
            secondWaterLine[i] = (float) ( * Math.sin(mCycleFactorW * i + ));
        }
    }
           

在onDraw()方法中分别得到了兩個數組中Y軸的值,并且将他一個周期的長度定位len(和直徑相等)每個值都對應着一個X軸的值,也就是說我們得到兩個正弦波上的所有點的坐标值了。并且第二天波偏左一點

而且還要增加一下他們的振幅,不然0-1之間的值太小,顯示在螢幕上像一條直線。

android自定義元件(手機加速球+水面波動效果)

上圖是什麼意思呢?我們的水波效果是下面有填充色的,那麼這些填充色其實就是波上的每個點,往View的底邊畫的一條一條的直線(數學中的細分法或者微積分吧)然後線就組成了面。

ok,劃線需要知道起點坐标,和終點坐标,如圖中的兩個綠色的坐标點。(i,y)到(i,len);接下來開始畫直線

public WaterView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        waterPaint = new Paint();
        //抗鋸齒
        waterPaint.setAntiAlias(true);
        waterPaint.setColor(Color.GREEN);

    }
 @Override
    protected void onDraw(Canvas canvas) {
        // y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;
        // 将周期定為view總寬度
        float mCycleFactorW = (float) ( * Math.PI / len);
        // 得到第一條波的y值
        for (int i = ; i < len; i++) {
            firstWaterLine[i] = (float) ( * Math
                    .sin(mCycleFactorW * i));
        }
        // 得到第二條波的y值
        for (int i = ; i < len; i++) {
            secondWaterLine[i] = (float) ( * Math.sin(mCycleFactorW * i + ));

        }

        //第一條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, firstWaterLine[i], i, len, waterPaint);
        }
        //第二條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, secondWaterLine[i], i, len, waterPaint);
        }

    }
           

構造方法中執行個體化出了一個畫筆對象,顔色為綠色,抗鋸齒。在onDraw()方法中添加了兩個畫直線的方法,我們要對每個點都要繪制是以使用了循環。

android自定義元件(手機加速球+水面波動效果)

哎呀,這不是咱們想看到的效果呀,這是因為坐标的原點依舊在View的左上角,振幅小,都往下方畫直線就覆寫了整個View,接下來我們把坐标系往下放移動len/2的距離,再去繪制:

@Override
    protected void onDraw(Canvas canvas) {
        // y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;
        // 将周期定為view總寬度
        float mCycleFactorW = (float) ( * Math.PI / len);
        // 得到第一條波的y值
        for (int i = ; i < len; i++) {
            firstWaterLine[i] = (float) ( * Math
                    .sin(mCycleFactorW * i));
        }
        // 得到第二條波的y值
        for (int i = ; i < len; i++) {
            secondWaterLine[i] = (float) ( * Math.sin(mCycleFactorW * i + ));

        }
        //儲存原來的内容
       canvas.save();
       canvas.translate(,len/);
        //第一條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, firstWaterLine[i], i, len, waterPaint);
        }
        //第二條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, secondWaterLine[i], i, len, waterPaint);
        }
        //恢複到原來的狀态(會自動結合繪制的内容)
        canvas.restore();;
    }
           

我們需要先儲存畫布的狀态,再去移動坐标系,之後在恢複合并。

android自定義元件(手機加速球+水面波動效果)

現在是我們想看到的樣子了,但是還有一點,我們希望他是一個圓形的,這時候就需要另外一個功能,cavans的剪切功能

@Override
    protected void onDraw(Canvas canvas) {
        // y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;
        // 将周期定為view總寬度
        float mCycleFactorW = (float) ( * Math.PI / len);
        // 得到第一條波的y值
        for (int i = ; i < len; i++) {
            firstWaterLine[i] = (float) ( * Math
                    .sin(mCycleFactorW * i));
        }
        // 得到第二條波的y值
        for (int i = ; i < len; i++) {
            secondWaterLine[i] = (float) ( * Math.sin(mCycleFactorW * i + ));

        }
        // 裁剪成圓形區域
        Path path = new Path();
        path.reset();
        float clipRadius=len/;
        //添加圓形路徑
        //Path.Direction.CCW逆時針
        //Path.Direction.CW順時針
        path.addCircle(len / , len / , clipRadius, Path.Direction.CCW);
        // (剪裁路徑)裁剪成圓形區域
        //(REPLACE用目前要剪切的區域代替畫布中的内容的區域)
        canvas.clipPath(path, android.graphics.Region.Op.REPLACE);

       canvas.save();
       canvas.translate(,len/);

        //第一條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, firstWaterLine[i], i, len, waterPaint);
        }
        //第二條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, secondWaterLine[i], i, len, waterPaint);
        }
        canvas.restore();;
    }
           

在我們要移動畫布之前,将View剪切成了圓形

點選了解

Region.Op

android自定義元件(手機加速球+水面波動效果)

在布局中我去除了,控件的背景,效果如圖所示,接下來就是去控制讓他動起來了,水準方向移動也就是每次初相都不相同,開啟時間任務,讓它的初相值不斷變化(從右往左移動就加上一個數)

@Override
    protected void onDraw(Canvas canvas) {
        // y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;
        // 将周期定為view總寬度
        float mCycleFactorW = (float) ( * Math.PI / len);
        // 得到第一條波的y值
        for (int i = ; i < len; i++) {
            //添加一個可變的初相值
            firstWaterLine[i] = (float) ( * Math
                    .sin(mCycleFactorW * i+move));
        }
        // 得到第二條波的y值
        for (int i = ; i < len; i++) {
            //添加一個可變的初相值
            secondWaterLine[i] = (float) ( * Math.sin(mCycleFactorW * i + move+));

        }
        // 裁剪成圓形區域
        Path path = new Path();
        path.reset();
        float clipRadius=len/;
        //添加圓形路徑
        //Path.Direction.CCW逆時針
        //Path.Direction.CW順時針
        path.addCircle(len / , len / , clipRadius, Path.Direction.CCW);
        // (剪裁路徑)裁剪成圓形區域
        //(REPLACE用目前要剪切的區域代替畫布中的内容的區域)
        canvas.clipPath(path, android.graphics.Region.Op.REPLACE);

       canvas.save();
       canvas.translate(,len/);

        //第一條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, firstWaterLine[i], i, len, waterPaint);
        }
        //第二條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, secondWaterLine[i], i, len, waterPaint);
        }
        canvas.restore();;
    }
           

在onDraw()的方法中,擷取坐标Y值的時候,添加一條可變的全局變量,動态改變初相值。開啟時間任務:

public void moveWaterLine() {
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                //不斷改變初相
                move += ;
                //重新繪制(子線程中調用)
                postInvalidate();
            }
        }, , );
    }
           

在時間任務中,這裡沒用去關閉時間任務,它會一直動,,動态去改變,并在構造方法中去調用

android自定義元件(手機加速球+水面波動效果)

效果已經很不錯了,如何讓它去增加和減少呢,讓它從下往上增加,隻要不斷去影響Y的值就好了,

android自定義元件(手機加速球+水面波動效果)

如果坐标系不改變,繪制水波的時候還要判斷是增加還是減少,為了友善計算,隻需要将坐标系移動到底部就好了,為0的時候代表什麼都沒用,有的時候讓Y值不斷的減去一個值就實作了網上增加。

@Override
    protected void onDraw(Canvas canvas) {
        // y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;
        // 将周期定為view總寬度
        float mCycleFactorW = (float) ( * Math.PI / len);
        // 得到第一條波的y值
        for (int i = ; i < len; i++) {
            //添加一個可變的初相值
            firstWaterLine[i] = (float) ( * Math
                    .sin(mCycleFactorW * i + move) - up);
        }
        // 得到第二條波的y值
        for (int i = ; i < len; i++) {
            //添加一個可變的初相值
            secondWaterLine[i] = (float) ( * Math.sin(mCycleFactorW * i + move + ) - up);

        }
        // 裁剪成圓形區域
        Path path = new Path();
        path.reset();
        float clipRadius = len / ;
        //添加圓形路徑
        //Path.Direction.CCW逆時針
        //Path.Direction.CW順時針
        path.addCircle(len / , len / , clipRadius, Path.Direction.CCW);
        // (剪裁路徑)裁剪成圓形區域
        //(REPLACE用目前要剪切的區域代替畫布中的内容的區域)
        canvas.clipPath(path, android.graphics.Region.Op.REPLACE);

        canvas.save();
        canvas.translate(, len);

        //第一條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, firstWaterLine[i], i, len, waterPaint);
        }
        //第二條波的所有直線
        for (int i = ; i < len; i++) {
            canvas.drawLine(i, secondWaterLine[i], i, len, waterPaint);
        }
        canvas.restore();
        ;
    }
           

在onDraw()方法中将坐标系移動到底部,并且聲明一個全局變量up來動态改變Y值,因為是從下往上運動,是以是減去,開啟時間任務:

//如果在運作,就不會執行下次動畫
    private boolean isRunning;
    //判斷是上升還是下降
    public int state = ;

    public void change(final int trueAngle) {
        if (isRunning) {
            return;
        }
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                switch (state) {
                    case :
                        isRunning = true;
                        up -= ;
                        if (up <= ) {
                            up = ;
                            state = ;
                        }
                        break;
                    case :
                        up += ;
                        if (up >= trueAngle) {
                            up = trueAngle;
                            state = ;
                            isRunning = false;
                            timer.cancel();
                        }
                        break;
                    default:
                        break;
                }

                postInvalidate();
            }
        }, , );

    }
           

聲明一個boolean值來判斷是否在運動,如果在動,就不進行下次運動,聲明一個state變量來判斷是上還是下

up值動态增加或減小,再重複繪制

activity調用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       final WaterView wv= (WaterView) findViewById(R.id.wv);
        wv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                wv.change();
            }
        });

    }
}
           

設定點選事件,調用動的方法

android自定義元件(手機加速球+水面波動效果)

終于大功告成了

目已經上傳到github

github點選下載下傳

最後的最後,個人淘寶店(抱歉,請見諒)。。霓裳雅閣

有喜歡的商品可以和我說下哦,,QQ:1070379530

謝謝