Android:會呼吸的懸浮氣泡

Fidelia0391 8年前發布 | 15K 次閱讀 安卓開發 Android開發 移動開發

寫在前面

這個標題看起來玄乎玄乎的,其實一張圖就明白了:

懸浮氣泡演示圖

最早看到這個效果是 MIUI6 系統升級界面,有很多五顏六色的氣泡懸浮著,覺得很好看。可惜現在找不到動態圖了。雖然 MIUI8 更新界面也有類似的氣泡,不過是靜態的,不咋好看。

MIUI8

再次見到這個效果是在 Pure 天氣這款軟件中,可惜開發者不開源。不過萬能的 Github 上有類似的實現,于是果斷把自定義 View 部分抽出來學習學習。

Pure

懷著敬意放上原項目地址,很好看的一款天氣 APP:

還是那句話,學習自定義 View 沒有什么捷徑,就是看源碼、模仿、動手。

具體實現

先思考

在看源碼之前,我自己想了一下該怎樣去實現,思路如下:

  • 自定義一個圓形 View ,支持大小、顏色、位置等屬性
  • 浮動利用最簡單的平移動畫來實現
  • 平移的范圍通過自定義圓心的移動范圍來確定
  • 最后給動畫一個循環就行了

雖然看起來比較簡單,但是實現起來還是遇到不少坑。首先畫圓一點問題都沒有,問題出在動畫上。動畫看起來很遲鈍,根本就不是呼吸效果,像哮喘一樣。

所以不能用動畫,就想到了不斷重繪。于是仍然給圓心設置一個小圓,讓圓心在小圓上移動,在這個過程中不斷重繪,結果直接 Crash 了,看了看 Log ,發現是線程阻塞了,但是這里并沒有開啟子線程啊,一看,我去,主線程。

那這條路行不通,又想到用貝塞爾去做,結果突然想起來之前繪制阻塞了主線程,那開子線程繪制不就完了, Android View 里面能開子線程繪制的不就是 SurfaceView 。于是看了看作者源碼,果然是自定義 SurfaceView

早已看穿一切

關于 SurfaceView 我只在以前學習的視頻案例、撕MM衣服案例、還有手寫板案例中遇到過,學的不是很深,加上本文它不是重點,所以就不詳細說了,如果不了解這個或者想深入了解一下的話,可以點擊文末的相關鏈接,這里只簡單提一下比較重要的一點,也就是 SurfaceViewView 的主要區別:

SurfaceView在一個新起的單獨線程中重新繪制畫面,而 View 必須在 UI 線程中更新畫面。

這就決定了 SurfaceView 的一些特定使用場景:

  • 需要界面迅速更新;

  • 對幀率要求較高的情況;

  • 渲染 UI 需要較長的時間。

所以綜合來看, SurfaceView 無疑是實現這類效果的最佳選擇。

再分析

廢話不多說,來分析一下思路。

1、首先光從界面上能看到就是圓,且是能浮動的圓,所以不管能不能動,先得把圓畫出來。要是我的話,我直接就拿著 PaintCanvas 上開畫了。在源碼中開發者單獨抽取了繪制圓的類,但這個類的作用不僅僅是繪制圓,后面我們再說。

2、其次就是自定義 SurfaceView ,我們需要把畫出來的圓放到 SurfaceView 中。而自定義 SurfaceView 需要實現 SurfaceHolder.Callback 接口,就是一些回調方法。同時需要開子線程去不斷刷新界面,因為這些圓是需要動起來的.

3、另外重要的一點就是, SurfaceView 在渲染過程中需要消耗大量資源,比如內存啊、 CPU 啊之類的,所以最好提供一個生命周期相關的方法,讓它和 Activity 的生命周期保持一致,盡量保證及時回收資源,減少消耗。

4、最后需要提一點的是, SurfaceView 本身并不需要繪制內容,或者說在這里它的主要作用就是刷新界面就行了。就好像在放視頻的時候,只需要刷新視頻頁面就行,它并不參與視頻具體內容的繪制。

所以這樣來說的話,我們最好定義一個繪制過程的中間者,主要作用就是把繪制出來的圓放在 SurfaceView 上,同時也能做一些其他的工作,比如繪制背景、設置尺寸等。這樣做的好處就是能讓 SurfaceView 專心的做一件事:不斷刷新,這就夠了。

OK,總結一下我們到底需要哪些東西:

  • 專門繪制圓的類

  • 刷新過程中的子線程

  • 實現 SurfaceHolder.Callback 接口方法

  • 提供生命周期相關方法

  • 一個繪制過程的中間對象

多提一句,最后的繪制中間者也可以不定義,全部封裝到自定義 SurfaceView 中,但是從我實踐來看,我最后不得不單獨抽取出來,因為 SurfaceView 類看起來太亂了,這也是源碼中的實現方式。

 

后動手

Talk is cheap,Show me the code .

1、畫圓

既然要畫圓,我們肯定要設置一些圓的基本屬性:

  • 圓心坐標

  • 圓的半徑

  • 圓的顏色

由于需要圓動起來,也就是說它會偏移,所以要確定一個范圍。范圍確定了,就需要指定它該怎么變化,因為我們要求它緩慢而順暢的呼吸,不能瞬間大喘氣,也就是它不能瞬間移動偏移量那么多,所以最好指定它每一步變化多少,那就需要下面這兩樣東西:

  • 圓心偏移范圍

  • 每一幀的變化量

額外的,因為移動是每次都需要變的,下一次變化時不能重新開始,所以我們要記錄當前已經偏移的距離,然后根據一個標志位不斷呼氣...吐氣...呼氣...吐氣,所以需要:

  • 當前幀變化量

  • 標志位

好了,看構造函數吧:

/**

  • @author Mixiaoxiao
  • @revision xiarui 16/09/27
  • @description 圓形浮動氣泡 */

class CircleBubble { private final float cx, cy; //圓心坐標 private final float dx, dy; //圓心偏移距離 private final float radius; //半徑 private final int color; //畫筆顏色 private final float variationOfFrame; //設置每幀變化量 private boolean isGrowing = true; //根據此標志位判斷左右移動 private float curVariationOfFrame = 0f; //當前幀變化量

CircleBubble(float cx, float cy, float dx, float dy, float radius, float variationOfFrame, int color) {
    this.cx = cx;
    this.cy = cy;
    this.dx = dx;
    this.dy = dy;
    this.radius = radius;
    this.variationOfFrame = variationOfFrame;
    this.color = color;
}

//...畫圓方法先省略

}</code></pre>

好了,構造好了圓就要開始繪制圓了。之前說到,這個類的作用不僅僅是繪制圓,還要不斷更新圓的位置,也就是不斷重繪圓。更直接地說,我們需要繪制出不斷偏移的每一幀的圓。

步驟如下:

  • 確定當前幀偏移位置

  • 根據當前幀偏移位置計算圓心坐標

  • 設置圓的顏色透明度等屬性

  • 真正的開始繪制圓

代碼如下,結合上面的步驟和代碼中的注釋應該很容易看懂:

/**

  • 更新位置并重新繪制 *
  • @param canvas 畫布
  • @param paint 畫筆
  • @param alpha 透明值 */ void updateAndDraw(Canvas canvas, Paint paint, float alpha) { /**
    • 每次繪制時都根據標志位(isGrowing)和每幀變化量(variationOfFrame)進行更新
    • 說白了其實就是每幀都會變化一段距離 連在一起就產生動畫效果 */ if (isGrowing) { curVariationOfFrame += variationOfFrame; if (curVariationOfFrame > 1f) {
       curVariationOfFrame = 1f;
       isGrowing = false;
      
      } } else { curVariationOfFrame -= variationOfFrame; if (curVariationOfFrame < 0f) {
       curVariationOfFrame = 0f;
       isGrowing = true;
      
      } } //根據當前幀變化量計算圓心偏移后的位置 float curCX = cx + dx curVariationOfFrame; float curCY = cy + dy curVariationOfFrame; //設置畫筆顏色 int curColor = convertAlphaColor(alpha * (Color.alpha(color) / 255f), color); paint.setColor(curColor); //這里才真正的開始畫圓形氣泡 canvas.drawCircle(curCX, curCY, radius, paint); }</code></pre>

      其中的 convertAlphaColor() 方法是個工具方法,作用就是轉化一下顏色,不必深究:

      /**
  • 轉成透明顏色 *
  • @param percent 百分比
  • @param originalColor 初始顏色
  • @return 帶有透明效果的顏色 / private static int convertAlphaColor(float percent, final int originalColor) { int newAlpha = (int) (percent 255) & 0xFF; return (newAlpha << 24) | (originalColor & 0xFFFFFF); }</code></pre>

    到此,畫每一幀圓的工作我們就完成了。

    2、繪制中間者對象

    現在來說這個特殊的中間者對象,前文說了,單獨抽取這個類不是必須的。但最好抽取一下,讓 SurfaceView 專心做自己的事情。在這個中間者對象中我們做兩件事情:

    • 繪制背景

    • 繪制懸浮氣泡

    先來看繪制背景。為什么需要繪制背景呢,因為 SurfaceView 本身其實是個黑色,從我們日常看視頻的軟件中也能發現,視頻播放時周圍都是黑色的。有人問為什么不能直接在布局中設置呢?當然可以直接設置啊,不過要記得添加一句 setZOrderOnTop(true) ,不然會把之后繪制的懸浮氣泡遮擋住。

    在這里就來繪制一下吧,因為源碼中給出了一個漸變色的繪制,我覺得挺好玩,學一學。直接看代碼吧,都是模板代碼,沒啥好解釋的,簡單的 get/set 再畫一下就好了:

    /**
  • @author Mixiaoxiao
  • @revision xiarui 16/09/27
  • @description 繪制圓形浮動氣泡及設定漸變背景的繪制對象 */ public class BubbleDrawer {

    /===== 圖形相關 =====/ private GradientDrawable mGradientBg; //漸變背景 private int[] mGradientColors; //漸變顏色數組

    /**

    • 設置漸變背景色 *
    • @param gradientColors 漸變色數組 必須 >= 2 不然沒法漸變 */ public void setBackgroundGradient(int[] gradientColors) { this.mGradientColors = gradientColors; }

      /**

    • 獲取漸變色數組 *
    • @return 漸變色數組 */ private int[] getBackgroundGradient() { return mGradientColors; }

      /**

    • 繪制漸變背景色 *
    • @param canvas 畫布
    • @param alpha 透明值 */ private void drawGradientBackground(Canvas canvas, float alpha) { if (mGradientBg == null) {

       //設置漸變模式和顏色
       mGradientBg = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, getBackgroundGradient());
       //規定背景寬高 一般都為整屏
       mGradientBg.setBounds(0, 0, mWidth, mHeight);
      

      } //然后開始畫 mGradientBg.setAlpha(Math.round(alpha * 255f)); mGradientBg.draw(canvas); }

      //...暫時省略圓的繪制方法 }</code></pre>

      上面代碼就一點需要注意,漸變最少需要兩種顏色,不然沒法漸變,這個很好理解吧,不再多解釋了。現在我們來畫氣泡,步驟如下:

      • 設置一下圓的范圍,一般都為全屏
      • 根據圓的構造方法添加多個圓
      • 繪制添加的這些圓

      直接來看代碼,其實也很簡單:

      /===== 圖形相關 =====/
      private Paint mPaint; //抗鋸齒畫筆
      private int mWidth, mHeight;                //上下文對象
      private ArrayList<CircleBubble> mBubbles; //存放氣泡的集合

/**

  • 構造函數 *
  • @param context 上下文對象 可能會用到 */ public BubbleDrawer(Context context) { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBubbles = new ArrayList<>(); }

/**

  • 設置顯示懸浮氣泡的范圍
  • @param width 寬度
  • @param height 高度 */ void setViewSize(int width, int height) { if (this.mWidth != width && this.mHeight != height) {
     this.mWidth = width;
     this.mHeight = height;
     if (this.mGradientBg != null) {
         mGradientBg.setBounds(0, 0, width, height);
     }
    
    } //設置一些默認的氣泡 initDefaultBubble(width); }

/**

  • 初始化默認的氣泡 *
  • @param width 寬度 */ private void initDefaultBubble(int width) { if (mBubbles.size() == 0) {
     mBubbles.add(new CircleBubble(0.20f * width, -0.30f * width, 0.06f * width, 0.022f * width, 0.56f * width,
             0.0150f, 0x56ffc7c7));
     mBubbles.add(new CircleBubble(0.58f * width, -0.15f * width, -0.15f * width, 0.032f * width, 0.6f * width,
             0.00600f, 0x45fffc9e));
     //...
    
    } }

/**

  • 用畫筆在畫布上畫氣泡 *
  • @param canvas 畫布
  • @param alpha 透明值 */ private void drawCircleBubble(Canvas canvas, float alpha) { //循環遍歷所有設置的圓形氣泡 for (CircleBubble bubble : this.mBubbles) {
     bubble.updateAndDraw(canvas, mPaint, alpha);
    
    } }</code></pre>

    從代碼中看出,已經將所有添加的圓放到集合里,然后遍歷集合去畫,這就不用添加一個畫一個了,只需統一添加再統一繪制即可。

    既然背景繪制好了,氣泡也繪制好了,那就到了最后一步,需要提供方法讓 SurfaceView 去添加背景和氣泡:

    /**
  • 畫背景 畫所有的氣泡 *
  • @param canvas 畫布
  • @param alpha 透明值 */ void drawBgAndBubble(Canvas canvas, float alpha) { drawGradientBackground(canvas, alpha); drawCircleBubble(canvas, alpha); }</code></pre>

    到此,這個繪制中間者對象就完成了。

    3、自定義 SurfaceView

    終于到了重要的 SurfaceView 部分了,這部分不太好描述,因為最好的解釋方式就是看代碼。

    首先自定義 FloatBubbleView 繼承于 SurfaceView ,看一下簡單的變量定義、構造方法:

    /**
  • @author Mixiaoxiao
  • @revision xiarui 16/09/27
  • @description 用圓形浮動氣泡填充的View
  • @remark 因為氣泡需要不斷繪制 所以防止阻塞UI線程 需要繼承 SurfaceView 開啟線程更新 并實現回調類 */ public class FloatBubbleView extends SurfaceView implements SurfaceHolder.Callback {

    private DrawThread mDrawThread; //繪制線程 private BubbleDrawer mPreDrawer; //上一次繪制對象 private BubbleDrawer mCurDrawer; //當前繪制對象 private float curDrawerAlpha = 0f; //當前透明度 (范圍為0f~1f,因為 CircleBubble 中 convertAlphaColor 方法已經處理過了) private int mWidth, mHeight; //當前屏幕寬高

    public FloatBubbleView(Context context) {

     super(context);
     initThreadAndHolder(context);
    

    }

    //...省略其他構造方法

    /**

    • 初始化繪制線程和 SurfaceHolder *
    • @param context 上下文對象 可能會用到 */ private void initThreadAndHolder(Context context) { mDrawThread = new DrawThread(); SurfaceHolder surfaceHolder = getHolder(); surfaceHolder.addCallback(this); //添加回調 surfaceHolder.setFormat(PixelFormat.RGBA_8888); //漸變效果 就是顯示SurfaceView的時候從暗到明 mDrawThread.start(); //開啟繪制線程 }

      /**

    • 當view的大小發生變化時觸發 *
    • @param w 當前寬度
    • @param h 當前高度
    • @param oldw 變化前寬度
    • @param oldh 變化前高度 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; }

      //...省略其他方法 }</code></pre>

      這里其他的內容都比較好理解,重點提兩個變量:

      private BubbleDrawer mPreDrawer;    //上一次繪制對象
      private BubbleDrawer mCurDrawer;    //當前繪制對象

      這是什么意思呢,開始我也不太理解,那換個思路,大家還記得 ListView 中的 ViewHolder 么,這個 ViewHolder 其實就是用來復用的。那 SurfaceView 中也有個 SurfaceHolder ,作用可以看做是相同的,就是用來不斷復用不斷刷新界面的。

      那這里的這兩個變量是干什么的呢?就是相當于 當前刷新的中間者對象上一次刷新的中間者對象

      那獲得這兩個對象有什么用呢?注意看,還有個 curDrawerAlpha 變量,顧名思義,當前的透明度。

      三者結合在一起,再加上一個這樣的小循環:

      if (curDrawerAlpha < 1f) {
      curDrawerAlpha += 0.5f;
      if (curDrawerAlpha > 1f) {
       curDrawerAlpha = 1f;
       mPreDrawer = null;
      }
      }

      那這又有什么作用呢,別急,先看下面兩張對比圖,分別設置 curDrawerAlpha += 0.2fcurDrawerAlpha += 0.8f

      模擬器太卡,將就著看

      0.2f

      再看 0.8f ,從暗到明顯然快了點:

      0.8f

      現在知道作用了么,就是實現界面從暗到明的效果。那為什么需要這樣的效果呢,我嘗試過去掉這個,發現繪制的時候會偶爾出現閃黑屏的現象,黑色剛好是 SurfaceView 的本身顏色,加上這個效果就不會出現了。

      好,接下來看重中之重的繪制線程方法,為了方便我單獨抽取了線程類,并將 run 方法按照不同的功能分成好幾個方法,注釋寫的很清晰:

      /**

  • 繪制線程 必須開啟子線程繪制 防止出現阻塞主線程的情況 */ private class DrawThread extends Thread { SurfaceHolder mSurface; boolean mRunning, mActive, mQuit; //三種狀態 Canvas mCanvas;

    @Override public void run() {

     //一直循環 不斷繪制
     while (true) {
         synchronized (this) {
             //根據返回值 判斷是否直接返回 不進行繪制
             if (!processDrawThreadState()) {
                 return;
             }
             //動畫開始時間
             final long startTime = AnimationUtils.currentAnimationTimeMillis();
             //處理畫布并進行繪制
             processDrawCanvas(mCanvas);
             //繪制時間
             final long drawTime = AnimationUtils.currentAnimationTimeMillis() - startTime;
             //處理一下線程需要的睡眠時間
             processDrawThreadSleep(drawTime);
         }
     }
    

    }

    /**

    • 處理繪制線程的狀態問題 *
    • @return true:不結束繼續繪制 false:結束且不繪制 */ private boolean processDrawThreadState() { //處理沒有運行 或者 Holder 為 null 的情況 while (mSurface == null || !mRunning) {

       if (mActive) {
           mActive = false;
           notify();   //喚醒
       }
       if (mQuit)
           return false;
       try {
           wait();     //等待
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
      

      }

      //其他情況肯定是活動狀態 if (!mActive) {

       mActive = true;
       notify();       //喚醒
      

      } return true; }

      /**

    • 處理畫布與繪制過程 要注意一定要保證是同步鎖中才能執行 否則會出現 *
    • @param mCanvas 畫布 */ private void processDrawCanvas(Canvas mCanvas) { try {

       mCanvas = mSurface.lockCanvas(); //加鎖畫布
       if (mCanvas != null) {          //防空保護
           //清屏操作
           mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
           drawSurface(mCanvas);    //真正開始畫 SurfaceView 的地方
       }
      

      }catch (Exception ignored){

      }finally {

       if(mCanvas != null){
           mSurface.unlockCanvasAndPost(mCanvas); //釋放canvas鎖,并顯示視圖
       }
      

      } }

      /**

    • 真正的繪制 SurfaceView *
    • @param canvas 畫布 */ private void drawSurface(Canvas canvas) {

      //防空保護 if (mWidth == 0 || mHeight == 0) {

       return;
      

      }

      //如果前一次繪制對象不為空 且 當前繪制者有透明效果的話 繪制前一次的對象即可 if (mPreDrawer != null && curDrawerAlpha < 1f) {

       mPreDrawer.setViewSize(mWidth, mHeight);
       mPreDrawer.drawBgAndBubble(canvas, 1f - curDrawerAlpha);
      

      }

      //直到當前繪制完全不透明時將上一次繪制的置空 if (curDrawerAlpha < 1f) {

       curDrawerAlpha += 0.5f;
       if (curDrawerAlpha > 1f) {
           curDrawerAlpha = 1f;
           mPreDrawer = null;
       }
      

      }

      //如果當前有繪制對象 直接繪制即可 先設置繪制寬高再繪制氣泡 if (mCurDrawer != null) {

       mCurDrawer.setViewSize(mWidth, mHeight);
       mCurDrawer.drawBgAndBubble(canvas, curDrawerAlpha);
      

      } }

      /**

    • 處理線程需要的睡眠時間
    • View通過刷新來重繪視圖,在一些需要頻繁刷新或執行大量邏輯操作時,超過16ms就會導致明顯卡頓 *
    • @param drawTime 繪制時間 */ private void processDrawThreadSleep(long drawTime) { //需要睡眠時間 final long needSleepTime = 16 - drawTime;

      if (needSleepTime > 0) {

       try {
           Thread.sleep(needSleepTime);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
      

      } } }</code></pre>

      知道看這種代碼很枯燥,但不能急。首先這里有三種狀態:正在繪制、活動、退出。其中活動是一種中間狀態,指既沒有活動又沒有被銷毀。在回調類中需要根據這種狀態進行繪制線程的控制。

      那就來看回調方法:

      /========== Surface 回調方法 需要加同步鎖 防止阻塞 START==========/
      @Override
      public void surfaceCreated(SurfaceHolder holder) {
      synchronized (mDrawThread) {
       mDrawThread.mSurface = holder;
       mDrawThread.notify();       //喚醒
      }
      }

@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }

@Override public void surfaceDestroyed(SurfaceHolder holder) { synchronized (mDrawThread) { mDrawThread.mSurface = holder; mDrawThread.notify(); //喚醒 while (mDrawThread.mActive) { try { mDrawThread.wait(); //等待 } catch (InterruptedException e) { e.printStackTrace(); } } } holder.removeCallback(this); } /========== Surface 回調方法 需要加同步鎖 防止阻塞 END==========/</code></pre>

可以看到,在銷毀的時候繪制線程是在等待狀態。

然后就是一些生命周期相關方法了,也很簡單,就是設置相關狀態:

/========== 處理與 Activity 生命周期相關方法 需要加同步鎖 防止阻塞 START==========/
public void onDrawResume() {
    synchronized (mDrawThread) {
        mDrawThread.mRunning = true;    //運行狀態
        mDrawThread.notify();           //喚醒線程
    }
}

public void onDrawPause() { synchronized (mDrawThread) { mDrawThread.mRunning = false; //不運行狀態 mDrawThread.notify(); //喚醒線程 } }

public void onDrawDestroy() { synchronized (mDrawThread) { mDrawThread.mQuit = true; //退出狀態 mDrawThread.notify(); //喚醒線程 } } /========== 處理與 Activity 生命周期相關方法 需要加同步鎖 防止阻塞 END==========/</code></pre>

最后就是提供方法,給這個自定義的 SurfaceView 設置中間繪制者對象了:

/**

  • 設置繪制者 *
  • @param bubbleDrawer 氣泡繪制 */ public void setDrawer(BubbleDrawer bubbleDrawer) { //防空保護 if (bubbleDrawer == null) {
     return;
    
    } curDrawerAlpha = 0f; //完全透明 //如果當前有正在繪制的對象 直接設置為前一次繪制對象 if (this.mCurDrawer != null) {
     this.mPreDrawer = mCurDrawer;
    
    } //當前繪制對象 為設置的對象 this.mCurDrawer = bubbleDrawer; }</code></pre>

    到此,自定義 FloatBubbleView 就完成了,代碼很長,建議直接看文末的源碼。

    看結果

    好了, 現在只要在 Activity 中這樣:

    /**
  • 初始化Data */ private void initData() { //設置氣泡繪制者 BubbleDrawer bubbleDrawer = new BubbleDrawer(this); //設置漸變背景 如果不需要漸變 設置相同顏色即可 bubbleDrawer.setBackgroundGradient(new int[]{0xffffffff, 0xffffffff}); //給SurfaceView設置一個繪制者 mDWView.setDrawer(bubbleDrawer); }</code></pre>

    這樣就大功告成了!效果圖再貼一下吧,顏色大小位置都可以定義:

     

    Android:會呼吸的懸浮氣泡

    懸浮氣泡演示圖

    后話

    雖然效果實現了,但是我并沒有將設置氣泡的方法暴露出來,只寫死在 BubbleDrawer 中:

    if (mBubbles.size() == 0) {
     mBubbles.add(new CircleBubble(0.20f * width, -0.30f * width, 0.06f * width, 0.022f * width, 0.56f * width,0.0150f, 0x56ffc7c7));
     //...
    }

    開始我確實抽取了方法,提供給 Activity ,結果發現 Activity 中的代碼太難看。另一方面因為 SurfaceView 消耗資源太多,我們應該不會在主要界面大量使用它,所以我覺得寫死就夠了,必要的時候動一動寫死的數據就行了。

    還有一點就是,雖然效果很好看,但是確實消耗資源很大,有時候會很卡,不知道還有沒有可以優化的地方,建議只在簡單的頁面,比如關于軟件的頁面用這樣的效果,其他的主頁面還是算了吧。

    參考資料

    Weather - Mixiaoxiao

    Android之SurfaceView簡介(一)

    Android SurfaceView入門學習 - 英勇青銅5

     

    來自:http://www.jianshu.com/p/5a672bac5ba9

     

 本文由用戶 Fidelia0391 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!