Android 學習筆記之自定義 View

本文整理自: Google 官方文檔之自定義 View,筆者省略了對自己幫助不大的章節,拜讀原文請點鏈接。

一、繼承一個View

Android Framework 里面定義的 View 類都繼承自 View 。你自定義的 View 也可以直接繼承 View,或者你可以通過繼承既有的一個子類(例如 Button )來節約一點時間。
為了讓 Android Developer Tools 能夠識別你的 View,你必須至少提供一個 constructor,它包含一個 Context 與一個 AttributeSet 對象作為參數。這個 constructor 允許 layout editor 創建并編輯你的 View 的實例。

class PieChart extends View {
    public PieChart(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

1.定義自定義屬性

為了添加一個內置的 View 到你的 UI 上,你需要通過 XML 屬性來指定它的樣式與行為。良好的自定義 Views 可以通過 XML 添加和改變樣式,為了讓你的自定義的 View 也有如此的行為,你應該:

  • 為你的 View 在資源標簽下定義自設的屬性
  • 在你的 XML layout 中指定屬性值
  • 在運行時獲取屬性值
  • 把獲取到的屬性值應用在你的 View 上

為了定義自設的屬性,添加 資源到你的項目中。放置于 res/values/attrs.xml 文件中。下面是一個 attrs.xml 文件的示例:

<resources>
   <declare-styleable name="PieChart">
       <attr name="showText" format="boolean" />
       <attr name="labelPosition" format="enum">
           <enum name="left" value="0"/>
           <enum name="right" value="1"/>
       </attr>
   </declare-styleable>
</resources>

上面的代碼聲明了 2 個自設的屬性,showText 與 labelPosition,它們都歸屬于 PieChart 的項目下的 styleable 實例。styleable 實例的名字,通常與自定義的 View 名字一致。盡管這并沒有嚴格規定要遵守這個 convention,但是許多流行的代碼編輯器都依靠這個命名規則來提供 statement completion。

一旦你定義了自設的屬性,你可以在 layout XML 文件中使用它們,就像內置屬性一樣。唯一不同的是你自設的屬性是歸屬于不同的命名空間。不是屬于「 http://schemas.android.com/apk/res/android 」 的命名空間,它們歸屬于「 http://schemas.android.com/apk/res/你的包名 」。例如,下面演示了如何為 PieChart 使用上面定義的屬性:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
 <com.example.customviews.charting.PieChart
     custom:showText="true"
     custom:labelPosition="left" />
</LinearLayout>

為了避免輸入長串的 namespace 名字,示例上面使用了 xmlns 指令,這個指令可以指派 custom 作為「http://schemas.android.com/apk/res/com.example.customviewsnamespace 」的別名。你也可以選擇其他的別名作為你的 namespace。請注意,如果你的 View 是一個 inner class,你必須指定這個 View 的 outer class。同樣的,如果 PieChart 有一個 inner class 叫做 PieView 。為了使用這個類中自設的屬性,你應該使用 com.example.customviews.charting.PieChart$PieView.

2.應用自定義屬性

當 View 從 XML layout 被創建的時候,在 xml 標簽下的屬性值都是從 resource 下讀取出來并傳遞到 View 的 constructor 作為一個 AttributeSet 參數。盡管可以從 AttributeSet 中直接讀取數值,可是這樣做有些弊端:

  • 擁有屬性的資源并沒有經過解析
  • Styles 并沒有運用上

翻譯注:通過 attrs 的方法是可以直接獲取到屬性值的,但是不能確定值類型,如:

String title = attrs.getAttributeValue(null, "title");
int resId = attrs.getAttributeResourceValue(null, "title", 0);
title = context.getText(resId));

都能獲取到 "title" 屬性,但你不知道值是字符串還是 resId,處理起來就容易出問題,下面的方法則能在編譯時就發現問題

取而代之的是,通過 obtainStyledAttributes() 來獲取屬性值。這個方法會傳遞一個 TypedArray 對象,它是間接 referenced 并且 styled 的。

Android資源編譯器幫你做了許多工作來使調用 [obtainStyledAttributes()](http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)) 更簡單。對 res 目錄里的每一個 <declare-styleable>
資源,自動生成的 R.java 文件定義了存放屬性 ID 的數組和常量,常量用來索引數組中每個屬性。你可以使用這些預先定義的常量來從
TypedArray 中讀取屬性。這里就是 PieChart 類如何讀取它的屬性:

public PieChart(Context context, AttributeSet attrs) {
   super(context, attrs);
   TypedArray a = context.getTheme().obtainStyledAttributes(
        attrs,
        R.styleable.PieChart,
        0, 0);

   try {
       mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
       mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
   } finally {
       a.recycle();
   }
}

清注意 TypedArray 對象是一個共享資源,必須被在使用后進行回收。

3.添加屬性和事件

Attributes 是一個強大的控制view的行為與外觀的方法,但是他們僅僅能夠在 View 被初始化的時候被讀取到。為了提供一個動態的行為,需要暴露出一些合適的 getter 與 setter 方法。下面的代碼演示了如何使用這個技巧:

public boolean isShowText() {
   return mShowText;
}

public void setShowText(boolean showText) {
   mShowText = showText;
   invalidate();
   requestLayout();
}

請注意,在 setShowText 方法里面有調用 invalidate()
and requestLayout()。 這兩個調用是確保穩定運行的關鍵。當 View 的某些內容發生變化的時候,需要調用 invalidate 來通知系統對這個 View 進行 redraw,當某些元素變化會引起組件大小變化時,需要調用 requestLayout 方法。調用時若忘了這兩個方法,將會導致 hard-to-find bugs。
自定義的 View 也需要能夠支持響應事件的監聽器。例如,PieChart 暴露了一個自定義的事件 OnCurrentItemChanged 來通知監聽器,用戶已經切換了焦點到一個新的組件上。我們很容易忘記了暴露屬性與事件,特別是當你是這個 View 的唯一用戶時。請花費一些時間來仔細定義你的
View 的交互。一個好的規則是總是暴露任何屬性與事件。


二、實現自定義 View 的繪制

重繪一個自定義的 View 的最重要的步驟是重寫 onDraw() 方法。onDraw() 的參數是一個 Canvas 對象。Canvas 類定義了繪制文本,線條,圖像與許多其他圖形的方法。你可以在 onDraw 方法里面使用那些方法來創建你的 UI。

在你調用任何繪制方法之前,你需要創建一個 Paint 對象。

1.創建繪圖對象

android.graphics framework 把繪制定義為下面兩類:

  • 繪制什么,由 Canvas 處理
  • 如何繪制,由 Paint 處理

例如 Canvas 提供繪制一條直線的方法,Paint 提供直線顏色。Canvas 提供繪制矩形的方法,Paint 定義是否使用顏色填充。簡單來說:Canvas 定義你在屏幕上畫的圖形,而 Paint 定義顏色,樣式,字體,所以在繪制之前,你需要創建一個或者多個 Paint 對象。在這個 PieChart 的例子,是在 init() 方法實現的,由 constructor 調用。

private void init() {
   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mTextPaint.setColor(mTextColor);
   if (mTextHeight == 0) {
       mTextHeight = mTextPaint.getTextSize();
   } else {
       mTextPaint.setTextSize(mTextHeight);
   }

   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mPiePaint.setStyle(Paint.Style.FILL);
   mPiePaint.setTextSize(mTextHeight);

   mShadowPaint = new Paint(0);
   mShadowPaint.setColor(0xff101010);
   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));

   ...

剛開始就創建對象是一個重要的優化技巧。Views 會被頻繁的重新繪制,初始化許多繪制對象需要花費昂貴的代價。在 onDraw 方法里面創建繪制對象會嚴重影響到性能并使得你的 UI 顯得卡頓。

2.處理布局事件

為了正確的繪制你的 View,你需要知道 View 的大小。復雜的自定義 View 通常需要根據在屏幕上的大小與形狀執行多次 layout 計算。而不是假設這個 View 在屏幕上的顯示大小。即使只有一個程序會使用你的 View,仍然是需要處理屏幕大小不同,密度不同,方向不同所帶來的影響。
盡管 View 有許多方法是用來計算大小的,但是大多數是不需要重寫的。如果你的 View 不需要特別的控制它的大小,唯一需要重寫的方法是[ onSizeChanged() ](http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int)),當你的 View 第一次被賦予一個大小時,或者你的 View 大小被更改時會被執行。在 onSizeChanged 方法里面計算位置,間距等其他與你的 View 大小值。
當你的 View 被設置大小時,layout manager (布局管理器)假定這個大小包括所有的 View 的內邊距 (padding) 。當你計算你的 View 大小時,你必須處理內邊距的值。這段 PieChart.onSizeChanged() 中的代碼演示該怎么做:

       // Account for padding
       float xpad = (float)(getPaddingLeft() + getPaddingRight());
       float ypad = (float)(getPaddingTop() + getPaddingBottom());

       // Account for the label
       if (mShowText) xpad += mTextWidth;

       float ww = (float)w - xpad;
       float hh = (float)h - ypad;

       // Figure out how big we can make the pie.
       float diameter = Math.min(ww, hh);

如果你想更加精確的控制你的 View 的大小,需要重寫[ onMeasure() ](http://developer.android.com/reference/android/view/View.html#onMeasure(int, int))方法。這個方法的參數是 View.MeasureSpec,它會告訴你的 View 的父控件的大小。那些值被包裝成 int 類型,你可以使用靜態方法來獲取其中的信息。
這里是一個實現 onMeasure() 的例子。在這個例子中 PieChart 試著使它的區域足夠大,使pie可以像它的 label 一樣大:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   // Try for a width based on our minimum
   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);

   // Whatever the width ends up being, ask for a height that would let the pie
   // get as big as it can
   int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
   int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);

   setMeasuredDimension(w, h);
}

上面的代碼有三個重要的事情需要注意:

  • 計算的過程有把 View 的 padding 考慮進去。這個在后面會提到,這部分是 View 所控制的。
  • 幫助方法 resolveSizeAndState() 是用來創建最終的寬高值的。這個方法比較 View 的期望值與傳遞給 onMeasure 方法的 spec 值,然后返回一個合適的 View.MeasureSpec 值。
  • onMeasure() 沒有返回值。它通過調用 setMeasuredDimension() 來獲取結果。調用這個方法是強制執行的,如果你遺漏了這個方法,會出現運行時異常。

3.繪圖

每個 View 的 onDraw 都是不同的,但是有下面一些常見的操作:

  • 繪制文字使用 drawText() 。指定字體通過調用 setTypeface() ,通過 setColor() 來設置文字顏色.
  • 繪制基本圖形使用 drawRect() , drawOval() , drawArc() . 通過 setStyle() 來指定形狀是否需要 filled, outlined.
  • 繪制一些復雜的圖形,使用 Path 類. 通過給 Path 對象添加直線與曲線, 然后使用 drawPath() 來繪制圖形. 和基本圖形一樣,paths 也可以通過
    setStyle 來設置是outlined, filled, both.
  • 通過創建 LinearGradient 對象來定義漸變。調用 setShader() 來使用 LinearGradient。
  • 通過使用 drawBitmap 來繪制圖片.
protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);

   // Draw the shadow
   canvas.drawOval(
           mShadowBounds,
           mShadowPaint
   );

   // Draw the label text
   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);

   // Draw the pie slices
   for (int i = 0; i < mData.size(); ++i) {
       Item it = mData.get(i);
       mPiePaint.setShader(it.mShader);
       canvas.drawArc(mBounds,
               360 - it.mEndAngle,
               it.mEndAngle - it.mStartAngle,
               true, mPiePaint);
   }

   // Draw the pointer
   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}

三、使得 View 可交互

繪制 UI 僅僅是創建自定義 View 的一部分。你還需要使得你的 View 能夠以模擬現實世界的方式來進行反饋。對象應該總是與現實情景能夠保持一致。例如,圖片不應該突然消失又從另外一個地方出現,因為在現實世界里面不會發生那樣的事情。正確的應該是,圖片從一個地方移動到另外一個地方。

用戶應該可以感受到 UI 上的微小變化,并對模仿現實世界的細微之處反應強烈。例如,當用戶 fling (迅速滑動)一個對象時,應該在開始時感到摩擦帶來的阻力,在結束時感到 fling 帶動的動力。應該在滑動開始與結束的時候給用戶一定的反饋。

1.處理輸入的手勢

像許多其他 UI 框架一樣,Android 提供一個輸入事件模型。用戶的動作會轉換成觸發一些回調函數的事件,你可以重寫這些回調方法來定制你的程序應該如何響應用戶的輸入事件。在 Android 中最常用的輸入事件是 touch,它會觸發 onTouchEvent(android.view.MotionEvent) 的回調。重寫這個方法來處理 touch 事件:

@Override
public boolean onTouchEvent(MotionEvent event) {
  return super.onTouchEvent(event);
}

Touch 事件本身并不是特別有用。如今的 touch UI 定義了 touch 事件之間的相互作用,叫做 gestures 。例如 tapping,pulling,flinging 與
zooming 。為了把那些 touch 的源事件轉換成 gestures, Android 提供了 GestureDetector

通過傳入 GestureDetector.OnGestureListener 的一個實例構建一個 GestureDetector 。如果你只是想要處理幾種 gestures (手勢操作)你可以繼承 GestureDetector.SimpleOnGestureListener ,而不用實現 GestureDetector.OnGestureListener 接口。例如,下面的代碼創建一個繼承 GestureDetector.SimpleOnGestureListener 的類,并重寫 onDown(MotionEvent)

class mListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());

不管你是否使用 GestureDetector.SimpleOnGestureListener,,你必須總是實現 onDown() 方法,并返回 true 。這一步是必須的,因為所有的
gestures 都是從 onDown() 開始的。如果你在 onDown() 里面返回 false,系統會認為你想要忽略后續的 gesture,那么GestureDetector.OnGestureListener 的其他回調方法就不會被執行到了。一旦你實現了 GestureDetector.OnGestureListener 并且創建了GestureDetector 的實例, 你可以使用你的 GestureDetector 來中止你在 onTouchEven t里面收到的 touch 事件。

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = mDetector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

當你傳遞一個 touch 事件到 onTouchEvent() 時,若這個事件沒有被辨認出是何種 gesture,它會返回 false 。你可以執行自定義的 gesture-decection 代碼。

2.創建基本合理的物理運動

Gestures 是控制觸摸設備的一種強有力的方式,但是除非你能夠產出一個合理的觸摸反饋,否則將是違反用戶直覺的。一個很好的例子是 fling 手勢,用戶迅速的在屏幕上移動手指然后抬手離開屏幕。這個手勢應該使得 UI 迅速的按照 fling 的方向進行滑動,然后慢慢停下來,就像是用戶旋轉一個飛輪一樣。

但是模擬這個飛輪的感覺并不簡單,要想得到正確的飛輪模型,需要大量的物理,數學知識。幸運的是,Android 有提供幫助類來模擬這些物理行為。 Scroller 是控制飛輪式的 fling 的基類。

要啟動一個 fling,需調用 fling(),并傳入啟動速率 x 、y 的最小值和最大值,對于啟動速度值,可以使用 GestureDetector 計算得出。

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
}

Note: 盡管速率是通過 GestureDetector 來計算的,許多開發者感覺使用這個值使得 fling 動畫太快。通常把 x 與 y 設置為 4 到 8 倍的關系。

調用[ fling() ](http://developer.android.com/reference/android/widget/Scroller.html#fling(int, int, int, int, int, int, int, int))時會為 fling 手勢設置物理模型。然后,通過調用定期調用 Scroller.computeScrollOffset() 來更新 Scroller 。 computeScrollOffset() 通過讀取當前時間和使用物理模型來計算 x 和 y 的位置更新 Scroller 對象的內部狀態。調用 getCurrX() getCurrY() 來獲取這些值。

大多數 View 通過 Scroller 對象的 x , y 的位置直接到[ scrollTo() ](http://developer.android.com/reference/android/view/View.html#scrollTo(int, int)),PieChart 例子稍有不同,它使用當前滾動 y 的位置設置圖表的旋轉角度。

if (!mScroller.isFinished()) {
    mScroller.computeScrollOffset();
    setPieRotation(mScroller.getCurrY());
}

Scroller 類會為你計算滾動位置,但是他不會自動把哪些位置運用到你的View 上面。你有責任確保 View 獲取并運用到新的坐標。你有兩種方法來實現這件事情:

  • 在調用 fling() 之后執行 postInvalidate(),這是為了確保能強制進行重畫。這個技術需要每次在 onDraw 里面計算過 scroll offsets (滾動偏移量)之后調用 postInvalidate()。
  • 使用 ValueAnimator 在 fling 是展現動畫,并且通過調用addUpdateListener() 增加對 fling 過程的監聽。

這個 PieChart 的例子使用了第二種方法。這個方法使用起來會稍微復雜一點,但是它更有效率并且避免了不必要的重畫的 View 進行重繪。缺點是 ValueAnimator 是從API Level 11 才有的。因此他不能運用到 3.0 的系統之前的版本上。

Note: ValueAnimator 雖然是 API 11 才有的,但是你還是可以在最低版本低于 3.0 的系統上使用它,做法是在運行時判斷當前的 API Level,如果低于 11 則跳過。

 mScroller = new Scroller(getContext(), null, true);
 mScrollAnimator = ValueAnimator.ofFloat(0,1);
 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator valueAnimator) {
         if (!mScroller.isFinished()) {
             mScroller.computeScrollOffset();
             setPieRotation(mScroller.getCurrY());
         } else {
             mScrollAnimator.cancel();
             onScrollFinished();
         }
     }
 });

3.使過渡平滑

用戶期待一個 UI 之間的切換是能夠平滑過渡的。UI 元素需要做到漸入淡出來取代突然出現與消失。Android 從 3.0 開始有提供 property animation framework ,用來使得平滑過渡變得更加容易。

使用這套動畫系統時,任何時候屬性的改變都會影響到你的視圖,所以不要直接改變屬性的值。而是使用 ValueAnimator 來實現改變。在下面的例子中,在 PieChart 中更改選擇的部分將導致整個圖表的旋轉,以至選擇的進入選擇區內。ValueAnimator 在數百毫秒內改變旋轉量,而不是突然地設置新的旋轉值。

mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);
mAutoCenterAnimator.setIntValues(targetAngle);
mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
mAutoCenterAnimator.start();

如果你想改變的是 View 的某些基礎屬性,你可以使用 ViewPropertyAnimator ,它能夠同時執行多個屬性的動畫。

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();

四、優化自定義 View

1.Do Less, Less Frequently

為了加速你的 View,對于頻繁調用的方法,需要盡量減少不必要的代碼。先從 onDraw 開始,需要特別注意不應該在這里做內存分配的事情,因為它會導致 GC,從而導致卡頓。在初始化或者動畫間隙期間做分配內存的動作。不要在動畫正在執行的時候做內存分配的事情。

你還需要盡可能的減少 onDraw 被調用的次數,大多數時候導致 onDraw 都是因為調用了 invalidate()。因此請盡量減少調用 invaildate() 的次數。如果可能的話,盡量調用含有4個參數的 invalidate() 方法而不是沒有參數的 invalidate()。沒有參數的 invalidate 會強制重繪整個 View 。

另外一個非常耗時的操作是請求layout。任何時候執行 requestLayout(),會使得 Android UI 系統去遍歷整個 View 的層級來計算出每一個 View 的大小。如果找到有沖突的值,它會需要重新計算好幾次。另外需要盡量保持 View 的層級是扁平化的,這樣對提高效率很有幫助。

如果你有一個復雜的 UI,你應該考慮寫一個自定義的 ViewGroup 來執行他的 layout 操作。與內置的 View 不同,自定義的 View 可以使得程序僅僅測量這一部分,這避免了遍歷整個view的層級結構來計算大小。這個 PieChart 例子展示了如何繼承 ViewGroup 作為自定義 View 的一部分。PieChart 有子 views,但是它從來不測量它們。而是根據他自身的 layout 法則,直接設置它們的大小。

2.使用硬件加速

從 Android 3.0 開始,Android 的 2D 圖像系統可以通過 GPU (Graphics Processing Unit)) 來加速。GPU 硬件加速可以提高許多程序的性能。但是這并不是說它適合所有的程序。Android Framework 讓你能過隨意控制你的程序的各個部分是否啟用硬件加速。

參考 Android Developers Guide 中的 Hardware Acceleration 來學習如何在 application,activity,或 window 層啟用加速。注意除了 Android Guide 的指導之外,你必須要設置你的應用的 target API 為 11,或更高,通過在你的 AndroidManifest.xml 文件中增加 < uses-sdk android:targetSdkVersion="11"/> 。

一旦你開啟了硬件加速,性能的提示并不一定可以明顯察覺到。移動設備的 GPU 在某些例如 scaling,rotating 與 translating 的操作中表現良好。但是對其他一些任務,比如畫直線或曲線,則表現不佳。為了充分發揮 GPU 加速,你應該最大化 GPU 擅長的操作的數量,最小化 GPU 不擅長操作的數量。

在下面的例子中,繪制 pie 是相對來說比較費時的。解決方案是把 pie 放到一個子 View 中,并設置 View 使用 LAYER_TYPE_HARDWARE 來進行加速。

private class PieView extends View {

       public PieView(Context context) {
           super(context);
           if (!isInEditMode()) {
               setLayerType(View.LAYER_TYPE_HARDWARE, null);
           }
       }

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

           for (Item it : mData) {
               mPiePaint.setShader(it.mShader);
               canvas.drawArc(mBounds,
                       360 - it.mEndAngle,
                       it.mEndAngle - it.mStartAngle,
                       true, mPiePaint);
           }
       }

       @Override
       protected void onSizeChanged(int w, int h, int oldw, int oldh) {
           mBounds = new RectF(0, 0, w, h);
       }

       RectF mBounds;
   }

通過這樣的修改以后,PieChart.PieView.onDraw() 只會在第一次現實的時候被調用。之后,pie chart 會被緩存為一張圖片,并通過 GPU 來進行重畫不同的角度。GPU 特別擅長這類的事情,并且表現效果突出。

緩存圖片到 hardware layer 會消耗 video memory,而 video memory 又是有限的。基于這樣的考慮,僅僅在用戶觸發 scrolling 的時候使用LAYER_TYPE_HARDWARE,在其他時候,使用 LAYER_TYPE_NONE。


不要給自己的人生設限
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,242評論 25 708
  • 原文地址:http://www.android100.org/html/201606/06/241682.html...
    AFinalStone閱讀 983評論 0 1
  • 故事太早 年輕的心 還不能一一整理開場的序言 順流而來的風 拂過臉頰 這一瞬 是給 故事的開場 是給過往 云煙里自...
    凡夢之愿閱讀 194評論 0 0
  • 一整天的陰雨,夜晚的空氣又沉又涼,心情壓抑極了,抽支煙發暈,路邊姑娘跟男友無言的擁抱在路燈下,總覺著一切都似曾相識...
    dryas閱讀 283評論 0 0
  • 看完星爺和徐老怪的作品《西游2——伏妖篇》后,出于一名教師的職業本能,我首先想到的居然是孩子的教育問題。 先說一下...
    蝸牛十年閱讀 879評論 2 6