在Android上仿百度貼吧客戶端Loading圖標小球

来源:http://www.cnblogs.com/idcjia/archive/2017/09/12/7512203.html
-Advertisement-
Play Games

封面預覽 前言 使用百度貼吧客戶端的時候發發現載入的小動畫挺有意思的,於是自己動手寫寫看。想學習自定義View以及自定義動畫的小伙伴一定不要錯過哦。 讀者朋友需要有最基本的canvas繪圖功底,比如畫筆Paint的簡單使用、Path如何畫直線等簡單的操作,不熟悉也沒關係,下文帶大家擼代碼的時候會簡單 ...


       封面預覽

  前言

  使用百度貼吧客戶端的時候發發現載入的小動畫挺有意思的,於是自己動手寫寫看。想學習自定義View以及自定義動畫的小伙伴一定不要錯過哦。

  讀者朋友需要有最基本的canvas繪圖功底,比如畫筆Paint的簡單使用、Path如何畫直線等簡單的操作,不熟悉也沒關係,下文帶大家擼代碼的時候會簡單的講一下。

  此篇文章用到如下知識點:



    • 1)、自定義View的測量

 

    • 2)、自定義View屬性的自定義及使用

 

    • 3)、Path繪製貝塞爾曲線

 

    • 4)、Canvas的裁剪

 

    • 5)、用ValueAnimator控制動畫

 

    • 6)、Canvas文字居中

 

  好了,開始正文!

  一、準備工作

  1、效果圖

loading小球

  2、動畫拆解

  直觀的看我們要實現三個方面

  1)、波浪動畫(藍色部分)

  2)、不規則的文字(白色的半個“貼”字)

  3)、控制項顯示部分限製成圓形

  3、技術分析

  1)、波浪動畫

  要實現波浪動畫,首先要繪製出波浪的形狀,其次再讓他動起來。波浪線看起來有點像是正弦或者餘弦函數,但是Android的Path並沒有提供繪製正餘弦圖形的函數,但是提供了一個功能更強大的曲線——貝塞爾曲線,貝塞爾曲線分為二階、三階及多階,本案例里使用的是二次貝塞爾曲線,如下圖所示,二階貝塞爾曲線需要三個點才可以確定

 

二階貝塞爾曲線

  我們來看一下Android里貝塞爾曲線的源碼:

  1. /* @param x1 The x-coordinate of the control point on a quadratic curve
  2. * @param y1 The y-coordinate of the control point on a quadratic curve
  3. * @param x2 The x-coordinate of the end on a quadratic curve
  4. * @param y2 The y-coordinate of the end point on a quadratic curve
  5. */
  6. public void quadTo(float x1, float y1, float x2, float y2) {
  7. isSimplePath = false;
  8. native_quadTo(mNativePath, x1, y1, x2, y2);
  9. }

  由註解可以看出來quadTo(float x1, float y1, float x2, float y2)的四個參數分別是控制點的x,y坐標,結束點的x,y坐標,少了一個開始點呀!不要著急,開始點是Path路徑的上一次結束的點,如果你的Path沒有繪製過路徑,那麼Path的最後一個點坐標就是(0,0)如果想自己定義起始點位置,就用Path.moveTo(float x, float y)即可。

  但是每次都需要指定具體的控制點和結束點既麻煩又容易出錯,那麼就需要rQuadTo(float dx1, float dy1, float dx2, float dy2)出馬了,rQuadTo跟quadTo的區別在於rQuadTo使用的是相對起始點的坐標,而不是具體的坐標點,舉個例子,如下代碼效果等價:

  1. //使用quadTo
  2. Path path=new Path();
  3. path.moveTo(100,100);
  4. path.quadTo(150,0,200,100);
  5. //使用rQuadTo
  6. Path path=new Path();
  7. path.moveTo(100,100);
  8. path.rQuadTo(50,-100,100,0);

  此時畫筆最後的落點都為(200,100)。

  畫波浪線的技術難點解決了那麼如何讓波浪動起來呢,想動起來肯定需要波浪在水平方向移動,那麼我們需要畫一個很長很長的波浪讓他移動,這樣就實現了上下起伏效果,但是這樣需要畫無數多條貝塞爾曲線,肯定不行,這時就用到萬能的數學理論——周期函數了,如果我們繪製兩個周期的貝塞爾曲線,每次只讓它顯示一個周期,然後等第二周期顯示結束的時候再從頭開始,這樣就造成了無限周期的假象,如下圖

  初始位置為1,向右前進,當走到2位置的時候重置成3的位置,即1原始的位置,如此往複就成了綿綿不絕的波浪了

 

綿延原理

  做成效果如下:黃色區域就是要顯示的區域,藍色豎線是波浪線兩個周期的總長度

 

連綿不絕的波浪線

  2)、不規則的文字

  我們可以看到圓球里的“貼”字在波浪區域顯示的是白色,波浪區域之外顯示的是藍色,Android並不支持給文字部分區域著色的功能,那麼我們只能靠控制顯示區域讓文字只顯示特定形狀,強大的Canvas正好有畫布裁剪功能,通過裁剪畫布就能控制繪製區域,畫布的裁剪可以用Canvas.clipPath(Path path)實現,傳入一個閉合的Path既可以隨心所欲裁剪畫布,裁剪示意圖如下

 

裁剪文字

  利用波浪形閉合路徑講畫布裁剪成波浪形,那麼在此接下來的Canvas繪製操的內容只能在這個波浪形區域里顯示,這樣就解決了文字的部分區域顯示問題。那麼接下來我們只用在相同位置繪製相同字體、字型大小不同色的文字即可實現一個文字顯示兩種顏色了(註意:實際操作的時候,被裁剪的文字要蓋在未被裁減的文字的上邊,即先在畫布裁剪之前繪製藍色的“貼”字,然後再裁剪畫布再在裁剪後的畫布上繪製白色的“貼”)

  3)、控制項顯示部分限製成圓形

  經過2)的分析,將顯示部分限制在圓形區域里不是易如反掌嗎,使用一個圓形的Path裁剪畫布即可。感興趣的同學也可以嘗試BitmapShader或者Xfermode來將顯示區域變成圓形

  好了,最主要的步驟都分析完了,上一張圖更直觀地展示一下繪製流程

 

整體分析圖

  圖中可以看出波浪形的閉合Path有兩個作用,一個是負責裁剪畫布,一個是負責繪製藍色,其實只用第一個功能即可,此處只是方便分解步驟。

  二、代碼實現

  文章只貼出主要代碼,完整代碼文末提供鏈接

  既然是自定義控制項,那就要有通用性比如下邊的效果:

 

各種顏色的球球

  loading小球需文字和顏色都可以改變,所以我們要給自己的控制項添加這兩個屬性。首先在“res/values/”路徑下新建一個attrs.xml文件,在裡邊定義如下屬性:

  1. <declare-styleable name="Wave">
  2. <attr name="color" format="color"/>
  3. <attr name="text" format="string"/>
  4. </declare-styleable>

  接下來開始自定義View

  覆寫三個構造函數,將單參數和雙參數的構造函數的super方法都改為this,保證無論調用哪個構造方法都會跳到三個參數的構造方法中,這樣就可以偷懶只用在三個參數的構造方法里初始化各種參數了

  1. public class Wave extends View {
  2. public Wave(Context context) {
  3. this(context,null);
  4. }
  5. public Wave(Context context, AttributeSet attrs) {
  6. this(context, attrs,0);
  7. }
  8. public Wave(Context context, AttributeSet attrs, int defStyleAttr) {
  9. super(context, attrs, defStyleAttr);
  10. //初始化參數
  11. init(context,attrs);
  12. }
  13. }

  接下來是初始化函數,在此處我們獲取到自定義的顏色及文字參數,並初始化各種畫筆,代碼比較簡單,看註釋內容即可

  1. private void init(Context context, AttributeSet attrs) {
  2. //獲取自定義參數值
  3. TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.Wave);
  4. //自定義顏色和文字
  5. color = array.getColor(R.styleable.Wave_color, Color.rgb(41, 163, 254));
  6. text = array.getString(R.styleable.Wave_text);
  7. array.recycle();
  8. //圖形及路徑填充畫筆(抗鋸齒、填充、防抖動)
  9. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  10. mPaint.setStyle(Paint.Style.FILL);
  11. mPaint.setColor(color);
  12. mPaint.setDither(true);
  13. //文字畫筆(抗鋸齒、白色、粗體)
  14. textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  15. textPaint.setColor(Color.WHITE);
  16. textPaint.setTypeface(Typeface.DEFAULT_BOLD);
  17. //閉合波浪路徑
  18. path = new Path();
  19. }

  接下來是生成波浪線的方法,示意圖如下:

 

波浪生成原理

  將Path起點移動到最左邊粉色點處,然後繪製兩個周期的長度的波形(一上一下是一個周期),每個周期在x軸的跨度為此控制項的寬度控制點距波形的軸線的絕對高度是整個控制項的3/20,當然想讓波形波動幅度大的話這個比例可以隨意調整,接下來就用前邊講到的rQuadTo( )來生成閉合的波浪圖形,其中mWidth為控制項的寬度,mHeight為控制項的高度

  1. private Path getActionPath(float percent) {
  2. Path path = new Path();
  3. int x = -mWidth;
  4. //當前x點坐標(根據動畫進度水平推移,一個動畫周期推移的距離為一個周期的波長)
  5. x += percent * mWidth;
  6. //波形的起點
  7. path.moveTo(x, mHeight / 2);
  8. //控制點的相對寬度
  9. int quadWidth = mWidth / 4;
  10. //控制點的相對高度
  11. int quadHeight = mHeight / 20 * 3;
  12. //第一個周期波形
  13. path.rQuadTo(quadWidth, quadHeight, quadWidth * 2, 0);
  14. path.rQuadTo(quadWidth, -quadHeight, quadWidth * 2, 0);
  15. //第二個周期波形
  16. path.rQuadTo(quadWidth, quadHeight, quadWidth * 2, 0);
  17. path.rQuadTo(quadWidth, -quadHeight, quadWidth * 2, 0);
  18. //右側的直線
  19. path.lineTo(x + mWidth * 2, mHeight);
  20. //下邊的直線
  21. path.lineTo(x, mHeight);
  22. //自動閉合補出左邊的直線
  23. path.close();
  24. return path;
  25. }

  上邊代碼所表示的閉合路徑如下圖

 

閉合的波浪圖形

  接下來就是重頭戲onDraw了

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. //底部的字
  4. textPaint.setColor(color);
  5. drawCenterText(canvas, textPaint, text);
  6. //上層的字
  7. textPaint.setColor(Color.WHITE);
  8. //生成閉合波浪路徑
  9. path = getActionPath(currentPercent);
  10. canvas.clipPath(path);
  11. //裁剪成圓形
  12. canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
  13. drawCenterText(canvas, textPaint, text);
  14. }

  這裡繪製思路是:在canvas上繪製藍色的文字 ——>將畫布裁剪成波浪形 ——>在波浪形畫布上繪製圓 ——>在波浪形畫布上繪製文字,這裡一定要註意繪製順序,先繪製的在下部,後繪製的在上部。

  細心的朋友一定看到了一個函數drawCenterText(canvas, textPaint, text)沒錯,這個函數就是講文字繪於控制項正中心的方法。有的讀者可能一直在使用Canvas.drawText( String text, float x, float y, Paint paint) 這個方法,但是參數中的(x,y)到底是哪個坐標呢,是文字左上角的點的坐標嗎?不是的,接下來我們用代碼驗證一下這個(x,y)到底在文字的哪個部位

  1. canvas.drawText(text,600,200,textPaint);
  2. canvas.drawCircle(600,200,3,paint);
  3. canvas.translate(600, 200);
  4. Rect bgRect=new Rect(0,0,1000,400);
  5. canvas.drawRect(bgRect,bgPaint);
  6. Rect textBound=new Rect();
  7. textPaint.getTextBounds(text,0,text.length(),textBound);
  8. paint.setColor(Color.RED);
  9. canvas.drawRect(textBound,paint);
  10. Paint.FontMetrics metrics=textPaint.getFontMetrics();
  11. paint.setColor(Color.RED);
  12. // ascent 橙色
  13. paint.setColor(Color.rgb(255,126,0));
  14. canvas.drawLine(0, metrics.ascent, 500,metrics.ascent, paint);
  15. // descent
  16. paint.setColor(Color.rgb(255,0,234));
  17. canvas.drawLine(0, metrics.descent, 500, metrics.descent, paint);
  18. // top
  19. paint.setColor(Color.DKGRAY);
  20. canvas.drawLine(0, metrics.top, 500, metrics.top, paint);
  21. // bottom
  22. paint.setColor(Color.GREEN);
  23. canvas.drawLine(0, metrics.bottom, 500, metrics.bottom, paint);

  首先是在畫布的(600,200)處畫上文字,為了方便觀察(600,200)在文字的什麼部位,我在(600,200)處畫了一個半徑3像素的圓圈。然後平移畫布到(600,200)的地方然後依次畫出了文字的邊框圖以及FontMetrics信息里的top、ascent、descent、bottom信息

  我把運行結果截圖做了處理,方便大家看

 

文字的各個邊界

  從結果看(600,200)那個藍色的點並不是在文字的左上角,而是左下角,這個點所在的y坐標即是大家常說的BaseLine的位置,那現在這個函數Canvas.drawText( String text, float x, float y, Paint paint)就可以理解為——將文字的基準點放在(x,y)處,那麼這個基準點可以改變嗎?答案是肯定的,可以通過繪製文字的畫筆的setTextAlign(Align align)方法設置為Paint.Align.CENTER或者Paint.Align.RIGHT,如果不設置的話預設是Paint.Align.LEFT。讀者朋友們有興趣的話可以試試設置成CENTER之後(600,200)的藍圈圈是不是跑到了文字的中部呢?從上圖我們也可以看出,整個文字是介於FontMetrics.top和FontMetrics.bottom之間。

  好了,貼上文字居中的代碼,相信認真看上邊那段話的朋友一定能輕鬆讀懂

  1. private void drawCenterText(Canvas canvas, Paint textPaint, String text) {
  2. Rect rect = new Rect(0, 0, mWidth, mHeight);
  3. textPaint.setTextAlign(Paint.Align.CENTER);
  4. Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
  5. //文字框最高點距離baseline的距離(負數)
  6. float top = fontMetrics.top;
  7. //文字框最低點距離baseline的距離(正數)
  8. float bottom = fontMetrics.bottom;
  9. int centerY = (int) (rect.centerY() - top / 2 - bottom / 2);
  10. canvas.drawText(text, rect.centerX(), centerY, textPaint);
  11. }

  分析好上邊的代碼 我們就能繪製出一個靜態的小球了,動畫既然要動,肯定就像汽車一樣需要一個"引擎",在上面說到的繪製波浪路徑的函數中我們忽略了getActionPath(float percent)的參數percent,這個參數即是當前動畫的進度,那麼我們如何來製造這個進度呢?需要怎樣把這個動畫“引擎”點燃呢。我們可以通過各種手段計時,生成一個計時Thread或者自己寫一個Handler等等,只要能均勻的生成進度即可。

  本文中用到一個巧妙的定時器ValueAnimator 大家常說的屬性動畫ObjectAnimator就是它的一個子類,使用它來作為動畫的引擎再方便不過了,從字面翻譯"ValueAnimator"那就是“值動畫者”直譯雖然low但是恰恰更好理解,就是讓數值動起來,從什麼值動到什麼值呢?

  1. ValueAnimator animator = ValueAnimator.ofFloat(0, 1);

  這句話就是定義一個值從0變化到1的一個animator,我們的percent值就是從0變化到1的中間過程值,那麼怎麼得到這個過程值呢?——監聽器!對!

  1. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  2. @Override
  3. public void onAnimationUpdate(ValueAnimator animation) {
  4. float percent = animation.getAnimatedFraction();
  5. }
  6. });

  那麼數值從0變到1需要多久呢?怎麼能無限重覆呢?重覆的時候是重頭開始還是反轉進行呢?別急下麵三句話就是讓動畫無限重覆,每次從頭開始,一個周期1000毫秒

  1. animator.setDuration(1000);
  2. animator.setRepeatCount(ValueAnimator.INFINITE);
  3. animator.setRepeatMode(ValueAnimator.RESTART);

  好了,引擎設置好了,發動

  animator.start();

  上效果

 

鬼畜版

  WTF!這是什麼鬼,為什麼鬼畜地慢幾拍?

  列印出來橫坐標看看

  1. 07-09 18:18:47.308 E/Jcs: getActionPath: -21
  2. 07-09 18:18:47.326 E/Jcs: getActionPath: -15
  3. 07-09 18:18:47.342 E/Jcs: getActionPath: -10
  4. 07-09 18:18:47.359 E/Jcs: getActionPath: -5
  5. 07-09 18:18:47.375 E/Jcs: getActionPath: -2
  6. 07-09 18:18:47.392 E/Jcs: getActionPath: 0
  7. 07-09 18:18:47.409 E/Jcs: getActionPath: 0

  最後幾拍的數值差好像不太對呀!拍拍腦門突然一想,我的動畫不均勻是忘記設置一個均勻的插值器了!哎!

  1. animator.setInterpolator(new LinearInterpolator());

  補上一個線性插值器,整個世界都順暢了

 

  百度Loading小球Github源碼

  三、結語

  第一次寫文章,不免有些疏漏之處,望多多指教!後續我會不定期更新新的內容,爭取把寫文章當成自己生活的一部分。

  後記(2017年7月27日15:02:39)

  有不少讀者問到關於小球和邊緣鋸齒的問題,我分別用如下方式實現loading小球

  1、Canvas的clip方式限制波浪邊界(本文提到的方法)

  2、使用Xfermode方式限制波浪和圓形的邊界

  3、用Xfermode方式限制白色文字,用shader方式限制圓形的邊界

  下邊是效果預覽圖,代碼已經提交到github上了,講解部分儘快補到此文中

三種方式對比

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [1]半透明邊框 [2]縫邊效果 [3]邊框內圓角 [4]信封邊框 [5]腳註效果 [6]螞蟻線 ...
  • 一、現象: 當本地同時運行了多個angular項目時,埠占用問題 Port 4200 is already in use. Use '--port' to specify a different port. 二、解決: 1、相應找到以下目錄 node_modules/@angular/cli/li ...
  • 父頁面: 子頁面: ...
  • 上篇博文【 Js利用Canvas實現圖片壓縮 】中做了圖片壓縮上傳,但是在IOS真機測試的時候,發現圖片預覽的時候自動逆時針旋轉了90度。對於這個bug,我完全不知道問題出在哪裡,接下來就是面向百度編程了。通過度娘找到了相關資料,解決方法記錄在此。這個問題的具體因素其實我還是不清楚是為何導致的,只有 ...
  • 從廣義上來講,css3動畫可以分為兩種。 過渡動畫 第一種叫過渡(transition)動畫,就是從初始狀態過渡到結束狀態這個過程中所產生的動畫。所謂的狀態就是指大小、位置、顏色、變形(transform)等等這些屬性。css過渡只能定義首和尾兩個狀態,所以是最簡單的一種動畫。要想使一個元素產生過渡 ...
  • 程式的流程圖: 主要代碼: 服務端 app.js 先載入所需要的通信模塊: 創建用戶列表和消息列表: 綁定並監聽80埠: 客戶端連接成功後,觸發響應事件connection,完成要綁定的事件並實現客戶端出發的事件: 客戶端 index.js: 先初始化用戶信息: 然後連接伺服器端: 連接成功後,對 ...
  • 一般 直接new Date() 是不會出現相容性問題的,而 new Date(datetimeformatstring) 常常會出現瀏覽器相容性問題,為什麼,datetimeformatstring中的某些格式瀏覽器不相容。 1. 無參 a. IE > IE9-(不相容) > IE9+(相容,包含I ...
  • xml <?xml version="1.0" encoding="utf-8"?><android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:too ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...