在Android上仿百度貼吧用戶端Loading表徵圖小球

來源:互聯網
上載者:User

標籤:ram   bit   開始   理解   忘記   預覽   小夥伴   animate   als   

       封面預覽

  前言

  使用百度貼吧用戶端的時候發發現載入的小動畫挺有意思的,於是自己動手寫寫看。想學習自訂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上了,講解部分儘快補到此文中

三種方式對比

 

在Android上仿百度貼吧用戶端Loading表徵圖小球

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.