天天看點

六種方式讓你的List滑動更流暢

看到一篇關于不錯的文章,簡單翻譯了下。原文位址:http://leftshift.io/6-ways-to-make-your-lists-scroll-faster-than-the-wind

快樂與悲傷共存。開發一款App總是很有趣,然而比較傷心的是拿着一款舊的三星測試機的你,得到一款新的MotoX卻發現你的App在上面運作的總是不流暢。第一眼看到App如此卡頓,我真的流淚了。此時盡管夜已深,我依然決定找出fix的辦法。

以下是我找出一些條目:

1>:在Adapter中getView方法裡面,減少條件判斷;

2>:減少GC操作;

3>:避免在滑動時加載圖檔;

4>:将scrollingCache跟animateCache設定為false;

5>:簡化你的list View布局層次;

6>:使用ViewHolder模式;

如果這些你全都熟悉,可能你沒有必要再讀下文了。不管怎樣,如果還不是完全了解,請接着往下看。(這裡給出一些建議。這裡給出的方法唯一的解決方案。有些時候它能夠起作用,有時需要你嘗試其它方案。這些都取決于你的代碼。寫這些方案的目的是幫助你的App更流暢)

1:在Adapter中getView方法裡面,減少條件判斷

(在你的Adapter中,可能需要清理某些狀态資訊。每次當你的螢幕上需要顯示一項時這個方法都會被調用。是以這裡定義的結構集,在每次App運作時都會進行大量調用。是以此處減少條件判斷比較有意義。現在問題是不能在這裡做,在哪兒又比較合适呢?答案很簡單,如果你正在使用一個可序列化的類來擷取其值,你隻需要将條件判斷移動到類裡面,将值存在裡面即可。通過這種方式,條件判斷僅僅隻需執行一次,下面是一個簡單的例子)

這裡是處理備援之前的View代碼(這裡有很複雜的條件判斷,我們移除了條件判斷,使其更便于了解)

@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
    Object current_event = mObjects.get(position);
    ViewHolder holder = null;
    if (convertView == null) {
        holder = new ViewHolder();
        convertView = inflater.inflate(R.layout.row_event, null);
        holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
        holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
        convertView.setTag(holder);
    } else {
            holder = (ViewHolder) convertView.getTag();
    }

    // If you have conditions like below then you may face problem
    if (doesSomeComplexChecking()) {
            holder.ThreeDimention.setVisibility(View.VISIBLE);
    } else {
            holder.ThreeDimention.setVisibility(View.GONE); 
    }

    // This method is setting layout parameters each time when getView is getting called which is wrong.
    RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
    holder.EventPoster.setLayoutParams(imageParams);

    return convertView;
}
           

下面是更新後的getView代碼:

@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
    Object current_event = mObjects.get(position);
    ViewHolder holder = null;

    if (convertView == null) {
        holder = new ViewHolder();
        convertView = inflater.inflate(R.layout.row_event, null);
        holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
        holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
        // This will now execute only for the first time of each row
        RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
        holder.EventPoster.setLayoutParams(imageParams);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    // This way you can simply avoid conditions
    holder.ThreeDimension.setVisibility(object.getVisibility());

    return convertView;
}
           

2:檢查和減少你logs中GC的警告

當大量的建立跟銷毀對象時,GC将頻繁的出現。是以一般情況在getView中不建立大量的對象;更好的建議是-除了ViewHolder不建立任何對象。如果在Log中頻繁出現“GC已經釋放了記憶體”,那說明你的代碼中存在嚴重的問題,你有必要按照下面的方法檢查下:

1>:清單中每項的布局層次

2>:getView中的條件判斷是否較多

3>:每項View的屬性

3:避免在滑動的時候加載圖檔

當使用者快速滑動的時候,應用程式正在下載下傳圖檔,此時應該停止加載圖檔,保證滑動的流暢性;當滑動停止時再加載圖檔。這種方式的效果很明顯。

同時,Google也給出了示例代碼,參考https://code.google.com/p/iosched/ 

對于那些讨厭打開連接配接并下載下傳代碼的人,下面是一小段代碼,代碼說明了如何添加一個scroll listener來stop跟start加載圖檔隊列

<span style="font-size:18px;">listView.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView listView, int scrollState) {
        // Pause disk cache access to ensure smoother scrolling
        if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
            imageLoader.stopProcessingQueue();
        } else {
            imageLoader.startProcessingQueue();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // TODO Auto-generated method stub
    }
});</span>
           

4:将scrollingCache跟animateCache設定為false

ListView有很多的屬性,以下是一些詳細的說明,參考:http://stackoverflow.com/questions/15570041/scrollingcache

scrolling cache是一個基本的drawing cache

在Android中,你可以請求一個View将正在繪制的緩存存儲在drawing cache中(比如Bitmap)。通常,drawing cache不可用,因為它會占用記憶體;但是你也可以通過setDrawingCacheEnabled或者hardware layers(setLayerType)來請求建立一個drawing cache。

為什麼這個比較有用?因為在View架構中,使用drawing cache能夠讓你的View得以比較平滑的繪制。

這類動畫也能夠被硬體加速,因為渲染系統能夠擷取該Bitmap并且作為texture(如果使用了硬體層)傳遞給GPU,能夠做更快的矩陣操作(比如:改變alpha值、旋轉、移動)。

在ListView中,當你快速滑動時,其實本質上是對ListView中View做動畫效果(不管是向上還是向下移動),ListView對可見的子View(靠近邊緣的可見子View)使用drawing cache使其能夠更快的進行動畫操作。

使用drawing cache有什麼缺點嗎?答案是的,由于使用drawing cache需要消耗記憶體,這就是為什麼預設是關閉的原因。對于ListView來說,隻要你Touch 或者移動一點點(跟滑動做差別),緩存就自動建立了。換句話說,當ListView認為你要滑動 or 快速滑動時,它将為你建立一個scrolling cache來確定更快的scroll or fling 動畫移動。

同樣,有時AnimationCache也有不好的地方(個人認為它導緻了頻繁的GC)

對于子View,布局動畫應該建立一個drawing cache。盡管動畫緩存會占用更多的記憶體且需要更長初始化,但是它提供了更好的性能。動畫緩存預設可用。動畫緩存參考Google文檔。

這裡是我們将要在ListView中修改的。原始代碼如下:

<ListView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="@color/list_background_color"
    android:dividerHeight="0dp"
    android:listSelector="#00000000"
    android:smoothScrollbar="true" />
           

下面是修改後的代碼:

<ListView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="@color/list_background_color"
    android:dividerHeight="0dp"
    android:listSelector="#00000000"
    android:smoothScrollbar="true"
    android:scrollingCache="false"
    android:animationCache="false" />
           

5:簡化LIstView每項布局的層次

你需要保證清單view布局盡可能的小。布局的層級跟深度直接跟view的測量、繪制相關(可以使用Hierarchy Viewer工具檢視)。後續部落格可能會繼續讨論該知識點。

6:使用view holder模式

對于初級開發者,這是一個經常犯的錯誤。第一次你的App表現正常,當快速滑動時,你的App将crash,出現這個問題的原因是因為,當你快速滑動時,其View每次都需要inflate,導緻性能降低。正确的做法是使用VIew Holder模式,将View存儲起來。具體參考文章1,文章2。

如果有其它技巧,歡迎補充。謝謝~~~