自定義一個水波球狀控件

轉(zhuǎn)載請注明出處:http://www.itdecent.cn/p/e4e59ab22324

寫在開頭

一個似水波的球狀控件,WaterWaveBallView,其實際用途同 ProgressBar 進度顯示控件。

我們先構(gòu)思,它由哪些部分構(gòu)成,它需要定義些什么屬性。

  • 水波:兩條正弦或余弦曲線。
  • 球狀:圓形視圖。
  • 顏色:兩條曲線與控件底部間的填充顏色。
  • 高度:兩條曲線的水平線位置。

當然了,這只是我們前期的設想,在實際的實現(xiàn)過程中會遇到很多的未知問題。

發(fā)車...

引狼,鎮(zhèn)貼

實現(xiàn)過程

水波

public class GoiWaveView extends View {

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTotalWidth = w; // 控件的寬度
        mTotalHeight = h; // 控件的高度
        // 定義波形的周期為控件的寬度,即 ω:周期
        FACTOR_W = (float) (2 * Math.PI / mTotalWidth); 
    }

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

        for(int i = 0; i < mTotalHeight; i ++){
            // 計算出橫坐標 i 對應的 縱坐標值 positionY
            float positionY = (float) (numberA * Math.sin(FACTOR_W *i) + OFFSET_Y);
            // 畫豎線,從起始位置(0, mTotalHeight - 0 - 100)畫到終點坐標(0, mTotalHeight)
            canvas.drawLine(i, mTotalHeight - positionY - 100, i, mTotalHeight, sinPaint);
        }

    }}
sin.png

解釋下 canvas.drawLine(i, mTotalHeight - positionY - 100, i, mTotalHeight, sinPaint);

假設控件的高度 mTotalHeight = 300,當 i = 0 時
drawLine的四個參數(shù):
startX,此時是 0;
startY,此時是 300 - 0 - 100;
stopX,此時是 0;
stopY,此時是 300。

左上角的坐標是(0, 0),所以是 mTotalHeight - positionY,-100是為了我們看到曲線的全貌。
可以看到,實際上畫正弦曲線的過程就是畫一根一根的豎線,它們是連續(xù)的,所以我們看到的是填充了的曲線圖。

再加一個余弦曲線...

sin+cos.png

然后,讓兩條曲線,都移動起來。

移動兩條曲線

其思路是,將一個完整的曲線向左或右移動,如何移動呢?
將一個周期曲線的橫縱坐標點,記錄下來。

for(int i = 0; i < mTotalWidth; i ++){
            // 計算出橫坐標 i 對應的 縱坐標值 positionY
            yOnePosition[i] = (float) (Y_FUNCTION_NUMBER_A * Math.sin(FACTOR_W *i) + OFFSET_Y);
            yTwoPosition[i] = (float) (Y_FUNCTION_NUMBER_A * Math.cos(FACTOR_W *i) + OFFSET_Y);
        }

xOneOffset 是移動偏移量,通過 System.arraycopy 方法將在移動中超出屏幕部分的點,添加到新數(shù)組的后面。
即: 1 2 3 4 5 6 7 8 9 10 向左移動兩個數(shù)
變成:3 4 5 6 7 8 9 10
這時把 1 2 追加到 10 的后面,使其變成
3 4 5 6 7 8 9 10 1 2

在正弦和余弦曲線中,他們還是首尾相連的。

int xOnePoint = yOnePosition.length - xOneOffset;
System.arraycopy(yOnePosition, xOneOffset, yOneResetPosition, 0, xOnePoint);
System.arraycopy(yOnePosition, 0, yOneResetPosition, xOnePoint, xOneOffset);

核心代碼

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

    int xOnePoint = yOnePosition.length - xOneOffset;
    System.arraycopy(yOnePosition, xOneOffset, yOneResetPosition, 0, xOnePoint);
    System.arraycopy(yOnePosition, 0, yOneResetPosition, xOnePoint, xOneOffset);

    int xTwoPoint = yTwoPosition.length - xTwoOffset;
    System.arraycopy(yTwoPosition, xTwoOffset, yTwoResetPosition, 0, xTwoPoint);
    System.arraycopy(yTwoPosition, 0, yTwoResetPosition, xOnePoint, xTwoOffset);

    for(int i = 0; i < mTotalHeight; i ++){
        // 畫豎線,從起始位置(0, mTotalHeight - 0 - 100)畫到終點坐標(0, mTotalHeight)
        canvas.drawLine(i, mTotalHeight - yOneResetPosition[i] - 50, i, mTotalHeight, sinPaint);
        canvas.drawLine(i, mTotalHeight - yTwoResetPosition[i] - 50, i, mTotalHeight, cosPaint);
    }

    // 每次移動多少個坐標
    xOneOffset += xOneSpeed;
    xTwoOffset += xTwoSpeed;
    // 橫坐標如果超過控件的寬度,則從0開始循環(huán)
    if(xOneOffset >= mTotalWidth){
        xOneOffset = 0;
    }
    if(xTwoOffset >= mTotalWidth){
        xTwoOffset = 0;
    }
    postInvalidate();

}

球狀

猜想:

  1. 利用 Xfermode 使一個圓形與波形圖混合;
  2. 利用 Xfermode 使一個圓形與一個與控件同等高寬的矩形混合;
  3. 找 UI 小姐姐給一個遮罩圖。

經(jīng)過測試:

  1. 圓形與波形圖混合,可能因為 Xfermode 混合圖層消耗也是很大的,再加上不斷地 draw 波形圖,每 draw 一次都會去混合一次,所以運行起來很不流暢,很卡頓的感覺。
  2. 第一個方法行不通,所以采用的這個方法。這時新建了一個圖層來混合,使其形成一個空心的圓,放在波形圖的上面。只會混合一次,所以消耗不大。
  3. 第三種方法,我是最先嘗試的,這個必然是可行的,在 layout 布局文件中處理。只是畢竟程序猿都喜歡用代碼來解決。

方法2 的核心代碼如下:

for (int i = 0; i < mTotalWidth; i++) {
    canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - getLevelNumber(),
            i, mTotalHeight, mWavePaint);

    canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - getLevelNumber(),
            i, mTotalHeight, mWavePaintN);
}
// 將當前畫布保存起來,在此行代碼以后的繪制,都是在新的圖層中。
int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
mWavePaint.setColor(Color.WHITE);
// 畫一個和控件寬高相同的矩形
canvas.drawRect(0, 0, getWidth(), getHeight(), mWavePaint);
// 設置混合模式為 DST_OUT,即在矩形中掏一個圓出來。
mWavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
// 畫一個半徑為控件寬高 1/2 的圓
canvas.drawCircle(getWidth()/2, getHeight()/2, circleRadius, mWavePaint);
mWavePaint.setXfermode(null);
// 還原保存的畫布
canvas.restoreToCount(sc);
mWavePaint.setColor(WAVE_COLOR);

本文主要講控件的實現(xiàn),關于更多的 Xfermode 學習煩請自行搜索,它還有很多神奇的用法,相信不會讓你失望。

顏色

為了使波形的顏色值可以動態(tài)控制,可通過布局文件中自定義屬性來實現(xiàn)。

  1. 創(chuàng)建資源文件
image.png

定義兩個顏色屬性名,和類型

image.png

2.在自定義控件的構(gòu)造函數(shù)中獲取屬性值

public GoiWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.GoiWaveView);
    SIN_WAVE_COLOR = typedArray.getColor(R.styleable.GoiWaveView_sin_color, Color.BLUE);
    COS_WAVE_COLOR = typedArray.getColor(R.styleable.GoiWaveView_cos_color, Color.RED);
}

3.在 layout 布局文件中給值

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.goileo.cview.view.GoiWaveView
        android:id="@+id/wave_view_goi"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:cos_color="@color/colorPrimary"
        app:sin_color="#abcdef"
        android:layout_gravity="center" />

</LinearLayout>

運行就能看到效果了。

高度

高度則需通過公共方法來傳參。

public float getLevelNumber() {
    return levelNumber;
}

public void setLevelNumber(double levelNumbers) {
    levelNumber = (int)((double)(levelNumbers * (double)mTotalHeight / 1000));
    this.invalidate();
}

這里設置成 1000 ,是為了讓波形上升時看上去更平緩。

寫在后頭

本文主要記錄了自定義球狀水波紋升級控件的實現(xiàn)過程,其中包括波形繪制、Xfermode混合技術應用、自定義屬性等。

如果有錯誤之處,煩請告知或指正。

演示:

效果

推薦閱讀:Android 熱修復 - 各框架原理學習及對比
Android 熱修復 - Tinker 實現(xiàn)及踩過的坑

記錄在此,僅為學習!
感謝您的閱讀!歡迎指正!
歡迎加入 Android 技術交流群,群號:155495090。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容