自定義view 波浪效果

来源:https://www.cnblogs.com/sskbskdrin/archive/2019/03/11/10511878.html
-Advertisement-
Play Games

實現波浪效果view,可以自定義view,也可以自定義drawable,我個人比較喜歡重寫drawable,因此這裡是自定義drawable實現效果,費話少說,先看效果。 這裡用了兩種方式實現波浪效果,一種是通過正弦函數去畫路徑,一種是通過三階貝塞爾曲線畫出類似正弦曲線的效果先看看實現波浪效果需要用 ...


實現波浪效果view,可以自定義view,也可以自定義drawable,我個人比較喜歡重寫drawable,因此這裡是自定義drawable實現效果,費話少說,先看效果。效果圖

這裡用了兩種方式實現波浪效果,一種是通過正弦函數去畫路徑,一種是通過三階貝塞爾曲線畫出類似正弦曲線的效果
先看看實現波浪效果需要用到的一些參數,看註釋大概就能瞭解

/**
* 畫布的寬
*/
int mWidth;
/**
* 畫布的高
*/
int mHeight;
/**
* 初始偏移量
*/
float offset = 0;
/**
* 線的寬度,當lineWidth>0時,是畫線模式,否則是填充模式
*/
float lineWidth = 0;
/**
* 顯示的周期數
*/
float period = 1;
/**
* 移動速度,每秒鐘移動的周期數
*/
float speedPeriod = 0.5f;
/**
* 波浪的振幅,單位px
*/
float mSwing = 20;

再來看看正弦函數的實現方式

private class WaveSin extends Wave {

    /**
     * 初始偏移量
     */
    float offRadian = 0;
    /**
     * 每個像素占的弧度
     */
    double perRadian;
    /**
     * 每秒移動的弧度數
     */
    float speedRadian;

    @Override
    public void onDraw(Canvas canvas, boolean isBottom) {
        float y = mHeight;
        mPath.reset();
        //計算路徑點的初始位置
        if (lineWidth > 0) {
            y = (float) (mSwing * Math.sin(offRadian) + mSwing);
            mPath.moveTo(-lineWidth, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
        } else {
            mPath.moveTo(0, isBottom ? 0 : mHeight);
        }

        //步長越小越精細,當然越消耗cpu性能,過大則會有鋸齒
        int step = mWidth / 100 > 20 ? 20 : mWidth / 100;

        //通過正弦函數計算路徑點,放入mPath中
        for (int x = 0; x <= mWidth + step; x += step) {
            y = (float) (mSwing * Math.sin(perRadian * x + offRadian) + mSwing);
            mPath.lineTo(x, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
        }

        //填充模式時,畫完完整路徑
        if (lineWidth <= 0) {
            mPath.lineTo(mWidth, isBottom ? mHeight - y : y);
            mPath.lineTo(mWidth, isBottom ? 0 : mHeight);
            mPath.lineTo(0, isBottom ? 0 : mHeight);
            mPath.close();
        }

        canvas.drawPath(mPath, mPaint);
    }

    @Override
    void init() {
        perRadian = (float) (2 * Math.PI * period / mWidth);
        speedRadian = (float) (speedPeriod * Math.PI * 2);
        offRadian = (float) (offset * 2 * Math.PI);
    }

    @Override
    public void move(float delta) {
        offRadian += speedRadian * delta;
    }
}

首先`init()`方法中,perRadian是計算每弧度所占的寬度,speedRadian計算每秒移動的弧度,offRadian是當前偏移弧度,在`move(float delta)`中可以看到delta是時間變化量,所以
`下一次的偏移量 = 當前偏移量+每秒移動的弧度*時間的變化量`,即`offRadian += speedRadian * delta;`
再來看看主要的onDraw方法,Canvas是畫布,isBottom是指波浪是否在整個畫布的底部。

下麵是通過貝塞爾曲線實現波浪效果

private class WaveBezier extends Wave {
    /**
     * 根據貝塞爾曲線公式計算的一個常量值
     */
    private static final double MAX_Y = 0.28867513459481287;
    /**
     * 一個周期的寬度
     */
    float periodWidth;
    /**
     * 每秒鐘移動的寬度
     */
    float speedWidth;
    /**
     * 貝塞爾曲線控制點的Y軸坐標
     */
    float conY;
    /**
     * 當前偏移量
     */
    float currentOffset = 0;

    @Override
    public void onDraw(Canvas canvas, boolean isBottom) {
        mPath.reset();
        //  移動到第一個周期的起始點
        mPath.moveTo(-currentOffset, 0);
        float conX = periodWidth / 2;
        int w = (int) -currentOffset;
        for (int i = 0; i <= mWidth + currentOffset; i += periodWidth) {
            mPath.rCubicTo(conX, conY, conX, -conY, periodWidth, 0);//註意,這裡用的是相對坐標
            w += periodWidth;
        }

        // 閉合路徑
        if (lineWidth <= 0) {
            mPath.rLineTo(0, isBottom ? -mHeight : mHeight);
            mPath.rLineTo(-w, 0);
            mPath.close();
        }

        //  對Y軸整體偏移
        mPath.offset(0, (isBottom ? mHeight - mSwing - lineWidth / 2 : mSwing + lineWidth / 2));

        canvas.drawPath(mPath, mPaint);
    }

    @Override
    void init() {
        periodWidth = mWidth / period;
        speedWidth = speedPeriod * periodWidth;
        currentOffset = offset * periodWidth;
        conY = (float) (mSwing / MAX_Y);
        isReInit = false;
    }

    @Override
    public void move(float delta) {
        if (periodWidth <= 0) {
            isReInit = true;
            return;
        }
        currentOffset += speedWidth * delta;
        if (currentOffset < 0) {
            currentOffset += periodWidth;
        } else {
            if (currentOffset > periodWidth) {
                currentOffset -= periodWidth;
            }
        }
    }
}

 

在 `init()`方法中periodWidth為單個周期寬度,speedWidth為每秒移動的寬度,currentOffset為當前偏移量,conY為控制點的Y軸坐標。

最後貼上完整代碼

  1 package cn.sskbskdrin.wave;
  2 
  3 import android.animation.ValueAnimator;
  4 import android.graphics.Canvas;
  5 import android.graphics.ColorFilter;
  6 import android.graphics.Paint;
  7 import android.graphics.Path;
  8 import android.graphics.PixelFormat;
  9 import android.graphics.Rect;
 10 import android.graphics.drawable.Animatable;
 11 import android.graphics.drawable.Drawable;
 12 import android.view.animation.LinearInterpolator;
 13 
 14 import java.util.ArrayList;
 15 import java.util.List;
 16 import java.util.Map;
 17 import java.util.WeakHashMap;
 18 
 19 /**
 20  * Created by sskbskdrin on 2018/4/4.
 21  *
 22  * @author sskbskdrin
 23  */
 24 public class WaveDrawable extends Drawable implements Animatable {
 25 
 26     private final List<Wave> list;
 27 
 28     private int mWidth;
 29     private int mHeight;
 30 
 31     private boolean animIsStart = false;
 32 
 33     private boolean isBottom = false;
 34 
 35     public WaveDrawable() {
 36         this(1);
 37     }
 38 
 39     public WaveDrawable(int count) {
 40         this(count, false);
 41     }
 42 
 43     public WaveDrawable(int count, boolean isSin) {
 44         if (count <= 0) {
 45             throw new IllegalArgumentException("Illegal count: " + count);
 46         }
 47         list = new ArrayList<>(count);
 48         for (int i = 0; i < count; i++) {
 49             list.add(isSin ? new WaveSin() : new WaveBezier());
 50         }
 51     }
 52 
 53     public void setBottom(boolean isBottom) {
 54         this.isBottom = isBottom;
 55     }
 56 
 57     /**
 58      * 設置填充的顏色
 59      *
 60      * @param color
 61      */
 62     public void setColor(int color) {
 63         for (Wave wave : list) {
 64             wave.setColor(color);
 65         }
 66     }
 67 
 68     /**
 69      * 設置填充的顏色
 70      *
 71      * @param color
 72      */
 73     public void setColor(int color, int index) {
 74         if (index < list.size()) {
 75             list.get(index).setColor(color);
 76         }
 77     }
 78 
 79     public void setOffset(float offset) {
 80         for (Wave wave : list) {
 81             wave.offset(offset);
 82         }
 83     }
 84 
 85     /**
 86      * 設置初始相位
 87      *
 88      * @param offset
 89      * @param index
 90      */
 91     public void setOffset(float offset, int index) {
 92         if (index < list.size()) {
 93             list.get(index).offset(offset);
 94         }
 95     }
 96 
 97     /**
 98      * 波浪的大小
 99      *
100      * @param swing
101      */
102     public void setSwing(int swing) {
103         for (Wave wave : list) {
104             wave.setSwing(swing);
105         }
106     }
107 
108     /**
109      * 波浪的大小
110      *
111      * @param swing
112      * @param index
113      */
114     public void setSwing(int swing, int index) {
115         checkIndex(index);
116         list.get(index).setSwing(swing);
117     }
118 
119     /**
120      * 設置波浪流動的速度
121      *
122      * @param speed
123      */
124     public void setSpeed(float speed) {
125         for (Wave wave : list) {
126             wave.setSpeed(speed);
127         }
128     }
129 
130     /**
131      * 設置波浪流動的速度
132      *
133      * @param speed
134      */
135     public void setSpeed(float speed, int index) {
136         checkIndex(index);
137         list.get(index).setSpeed(speed);
138     }
139 
140     /**
141      * 設置波浪周期數
142      *
143      * @param period (0,--)
144      */
145     public void setPeriod(float period) {
146         for (Wave wave : list) {
147             wave.setPeriod(period);
148         }
149     }
150 
151     public void setPeriod(float period, int index) {
152         checkIndex(index);
153         list.get(index).setPeriod(period);
154     }
155 
156     private void checkIndex(int index) {
157         if (index < 0 || index >= list.size()) {
158             throw new IllegalArgumentException("Illegal index. list size=" + list.size() + " index=" + index);
159         }
160     }
161 
162     public void setLineWidth(float width) {
163         for (Wave wave : list) {
164             wave.setLineWidth(width);
165         }
166     }
167 
168     public void setLineWidth(float width, int index) {
169         if (index >= 0 && index < list.size()) {
170             list.get(index).setLineWidth(width);
171         }
172     }
173 
174     @Override
175     protected void onBoundsChange(Rect bounds) {
176         mWidth = bounds.width();
177         mHeight = bounds.height();
178         for (Wave wave : list) {
179             wave.onSizeChange(mWidth, mHeight);
180         }
181     }
182 
183     @Override
184     public void draw(Canvas canvas) {
185         for (Wave wave : list) {
186             if (wave.isReInit) {
187                 wave.init();
188                 wave.isReInit = false;
189             }
190             wave.onDraw(canvas, isBottom);
191         }
192     }
193 
194     @Override
195     public int getIntrinsicWidth() {
196         return mWidth;
197     }
198 
199     @Override
200     public int getIntrinsicHeight() {
201         return mHeight;
202     }
203 
204     private void move(float delta) {
205         for (Wave wave : list) {
206             wave.move(delta);
207         }
208     }
209 
210     @Override
211     public void setAlpha(int alpha) {
212         for (Wave wave : list) {
213             wave.mPaint.setAlpha(alpha);
214         }
215     }
216 
217     @Override
218     public void setColorFilter(ColorFilter cf) {
219         for (Wave wave : list) {
220             wave.mPaint.setColorFilter(cf);
221         }
222     }
223 
224     @Override
225     public int getOpacity() {
226         return PixelFormat.TRANSLUCENT;
227     }
228 
229     @Override
230     public boolean setVisible(boolean visible, boolean restart) {
231         if (visible) {
232             if (animIsStart) {
233                 AnimateListener.start(this);
234             }
235         } else {
236             if (animIsStart) {
237                 AnimateListener.start(this);
238             }
239         }
240         return super.setVisible(visible, restart);
241     }
242 
243     @Override
244     public void start() {
245         animIsStart = true;
246         AnimateListener.start(this);
247     }
248 
249     @Override
250     public void stop() {
251         AnimateListener.cancel(this);
252         animIsStart = false;
253     }
254 
255     @Override
256     public boolean isRunning() {
257         return AnimateListener.isRunning(this);
258     }
259 
260     private static class AnimateListener implements ValueAnimator.AnimatorUpdateListener {
261         private static WeakHashMap<WaveDrawable, Boolean> map = new WeakHashMap<>();
262         private static int lastTime = 0;
263         private static ValueAnimator valueAnimator;
264 
265         private static void initAnimation() {
266             valueAnimator = ValueAnimator.ofInt(0, 1000);
267             valueAnimator.setDuration(1000);
268             valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
269             valueAnimator.setInterpolator(new LinearInterpolator());
270             valueAnimator.addUpdateListener(new AnimateListener());
271         }
272 
273         private static void start(WaveDrawable drawable) {
274             if (!map.containsKey(drawable)) {
275                 map.put(drawable, true);
276             }
277             if (valueAnimator == null) {
278                 initAnimation();
279             }
280             if (!valueAnimator.isRunning()) {
281                 valueAnimator.start();
282             }
283         }
284 
285         private static void cancel(WaveDrawable drawable) {
286             if (map.containsKey(drawable)) {
287                 map.put(drawable, false);
288             }
289         }
290 
291         private static boolean isRunning(WaveDrawable drawable) {
292             return map.containsKey(drawable) && map.get(drawable);
293         }
294 
295         @Override
296         public void onAnimationUpdate(ValueAnimator animation) {
297             int current = (int) animation.getAnimatedValue();
298             int delta = current - lastTime;
299             if (delta < 0) {
300                 delta = current + 1000 - lastTime;
301             }
302             float deltaF = delta / 1000f;
303             lastTime = current;
304             if (map.size() == 0) {
305                 animation.cancel();
306                 valueAnimator = null;
307                 return;
308             }
309             for (Map.Entry<WaveDrawable, Boolean> wave : map.entrySet()) {
310                 if (wave != null && wave.getValue()) {
311                     WaveDrawable drawable = wave.getKey();
312                     drawable.move(deltaF);
313                     drawable.invalidateSelf();
314                 }
315             }
316         }
317     }
318 
319     private abstract class Wave {
320 
321         /**
322          * 畫布的寬
323          */
324         int mWidth;
325         /**
326          * 畫布的高
327          */
328         int mHeight;
329         Path mPath = new Path();
330         Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
331         /**
332          * 初始偏移量
333          */
334         float offset = 0;
335         /**
336          * 線的寬度,當lineWidth>0時,是畫線模式,否則是填充模式
337          */
338         float lineWidth = 0;
339         /**
340          * 顯示的周期數
341          */
342         float period = 1;
343         /**
344          * 移動速度,每秒鐘移動的周期數
345          */
346         float speedPeriod = 0.5f;
347         /**
348          * 波浪的振幅,單位px
349          */
350         float mSwing = 20;
351 
352         boolean isReInit = true;
353 
354         /**
355          * drawable 大小改變
356          *
357          * @param width
358          * @param height
359          */
360         void onSizeChange(int width, int height) {
361             mWidth = width;
362             mHeight = height;
363             isReInit = true;
364         }
365 
366         abstract void onDraw(Canvas canvas, boolean isBottom);
367 
368         abstract void init();
369 
370         /**
371          * 移動的時間變化量
372          *
373          * @param delta
374          */
375         abstract void move(float delta);
376 
377         /**
378          * 設置線的寬度
379          *
380          * @param width
381          */
382         void setLineWidth(float width) {
383             lineWidth = width;
384             if (lineWidth > 0) {
385                 mPaint.setStyle(Paint.Style.STROKE);
386                 mPaint.setStrokeWidth(lineWidth);
387             } else {
388                 mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
389             }
390             isReInit = true;
391         }
392 
393         void setColor(int color) {
394             mPaint.setColor(color);
395         }
396 
397         /**
398          * 每秒移動的像素數
399          *
400          * @param speedPeriod
401          */
402         void setSpeed(float speedPeriod) {
403             this.speedPeriod = speedPeriod;
404             isReInit = true;
405         }
406 
407         /**
408          * 振幅大小
409          *
410          * @param swing
411          */
412         void setSwing(float swing) {
413             if (swing <= 0) {
414                 throw new IllegalArgumentException("Illegal swing: " + swing);
415             }
416             mSwing = swing;
417             isReInit = true;
418         }
419 
420         /**
421          * 顯示周期數
422
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文由雲+社區發表 作者:漆洪凱 規則1 :一般情況可以選擇MyISAM存儲引擎,如果需要事務支持必須使用InnoDB存儲引擎。 註意:MyISAM存儲引擎 B tree索引有一個很大的限制:參與一個索引的所有欄位的長度之和不能超過1000位元組。另外MyISAM數據和索引是分開,而InnoDB的數據 ...
  • 存儲過程如同一門程式設計語言,同樣包含了數據類型、流程式控制制、輸入和輸出和它自己的函數庫。 一、基本數據類型:略 二、變數: 自定義變數:DECLARE a INT ; SET a=100; 可用以下語句代替:DECLARE a INT DEFAULT 100; 變數分為用戶變數和系統變數,系統變數又 ...
  • 測試是軟體開發中的基礎,它經常被數據開發者忽視,但是它很重要。在本文中會展示如何使用Python的uniittest.mock庫,對一段PySpark代碼進行測試。筆者會從數據科學家的視角來進行工作,這意味著本文將不會深入某些軟體開發的細節。 本文鏈接:https://www.cnblogs.com ...
  • mysql 5.7或以上的新版本sql_mode 預設開啟開 ONLY_FULL_GROUP_BY,如果 select 中出現的欄位,沒有使用聚合函數,或不存在group by中就會提示,this is incompatible with sql_mode=only_full_group_by。 解 ...
  • InnoDB鎖的基本概念 文章總共分為五個部分: "InnoDB的鎖機制淺析(一)—基本概念/相容矩陣" "InnoDB的鎖機制淺析(二)—探索InnoDB中的鎖(Record鎖/Gap鎖/Next key鎖/插入意向鎖)" "InnoDB的鎖機制淺析(三)—幻讀" "InnoDB的鎖機制淺析(四) ...
  • rxretrofitlibrary是一個已經寫好的網路框架庫,先以本地Module導入到自己的項目中。 1、它的初始化操作大多在自定義的application中完成,如: public class App extends Application { @Override public void onC ...
  • 【說明】 1、主界面上添加父容器:FragmentTabHost 2、顯示內容區域 3、導航區域 【註意】 1、指定id時為android:id/tabhost,綁定時使用android.R.id.tabhost. 2、每一個Tab對應的Fragment的會填充到tabcontext上 【效果】 【 ...
  • 今天我們來聊一聊有關AppCompat,作為Android Jetpack系列文章的開篇。說到Android Jetpack,我們先看一下這張圖: 從圖中我們可以看到,整個Android Jetpack分為了四大部分,而我們今天要講述的就是Foundation中的AppCompat小節,官方將該部分 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...