ListView是我們在開發Android程序時用得比較多的一種widget,通常用來展示多條數據,這裡,我對ListView的一些功能點作一個簡單介紹。
1. Cache color hint
默認情況下,Android中的View的背景都是透明的,這是一個合理的設計,但是,當渲染到屏幕上時,這會引入許多的計算,因為所有的child的背景是透明的,這就意味著當ListView繪制它的child時,會導致child與window的背景色相混合,當ListView上下滑動或Fling時,性能就會下降。當ListView上下滑動或者是Fling時,為了避免這種情況發生或提高渲染速度,ListView引入了"scrolling cache"機制。這個機制簡單地說來就是ListView把可見的child繪制在一個bitmap上面(系統將每一個child拷貝到由cache
color hint填充的bitmap上面),然後再直接把這個bitmap繪制到屏幕上,由於這個bitmap是不透明的,所以就不會有混合計算,這樣就提高了繪制的效率。
但是,由於系統默認的cache color hint的顏色是#191919,當你滑動ListView時,你就會看到一個黑色的背景。
出現這種情況肯定是不正確的,那麼怎麼解決呢?
最簡單的方法是將cache color hint設置為透明。
android:cacheColorHint="#00000000" 或 setCacheColorHint(Color.TRANSPARENT)
以下是官方文檔的原文:
As mentioned before, ListView has a transparent/translucent background by default, and so all default widgets in the Android UI toolkit. This implies that when ListView redraws its children, it has to blend the children with the window's background. Once again,
this requires costly readbacks from memory that are particularly painful during a scroll or a fling when drawing happens dozen of times per second.
To improve drawing performance during scrolling operations, the Android framework reuses the cache color hint. When this hint is set, the framework copies each child of the list in a Bitmap filled with the hint value (assuming that another optimization, called
scrolling cache, is not turned off). ListView then blits these bitmaps directly on screen and because these bitmaps are known to be opaque, no blending is required. Also, since the default cache color hint is #191919, you get a dark background behind each
item during a scroll.
To fix this issue, all you have to do is either disable the cache color hint optimization, if you use a non-solid color background, or set the hint to the appropriate solid color value. You can do this from code (see setCacheColorHint(int)) or preferably from
XML, by using the android:cacheColorHint attribute. To disable the optimization, simply use the transparent color #00000000. The following screenshot shows a list with android:cacheColorHint="#00000000" set in the XML layout file.
2. Divider
通過android:divider XML屬性來指定。
Divider可以顏色或者Drawable,當繪制內容時,ListView會自動地計算出divider的位置。由於divider接收一個drawable,它可以是ColorDrawable,也可以是BitmapDrawable,但是,由於ColorDrawable,它是沒有高度的,所以Drawable#getIntrinsicHeight()方法返回-1,因此,為了避免這個問題, 我們可以通過android:dividerHeight屬性來設計其高度。
3. ListView繪制順序
對於ListView的繪制順序,我們很有必要了解一下。先看一下下面的圖片。
從上面的圖片可以看出,Android首先繪制背景和Dividers,然後再繪制其selector,最後,繪制所有的item在最上面。
由於通過情況下,listview的item的背景是透明的,所以,當用戶按下一個item時,黃色的selectZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcr7Nu+HNuLP2wLSho7zZyOfE47XEaXRlbcrH0ru49lRleHRWaWV3o6zE472rxuSxs76wyejWw86qsNfJq6OsxMfDtLWxsLTFpdK7uPZpdGVtysfKsaOsc2VsZWN0b3K+zbK7u+HP1Mq+s/bAtMHLo6zS8s6qy/zKx7vm1sbU2sv509BpdGVttcTPwsPmoaMKCjxicj4Ky/nS1KOsttTT2kxpc3RWaWV3wLTLtaOsxuS75tbGy7PQ8srHo7o8c3Ryb25nPkJhY2tncm91bmQgLT4gRGl2aWRlcnMgLT4gTGlzdCBzZWxlY3RvciAtPiBJdGVtdmlld3MgPC9zdHJvbmc+PGJyPgo8YnI+CjxoMj40LiBPbkl0ZW1DbGlja0xpc3RlbmVysru0pbeiPC9oMj4KCgrT0MqxuvKjrLWxTGlzdFZpZXfW0LXEw7/Su7j2aXRlbcrH19S2qNLltcRWaWV3yrGjrNPQv8nE3LvhtbzWwkxpc3RWaWV3tcRPbkl0ZW1DbGlja0xpc3RlbmVytcRsaXN0ZW5lcs7et6i199PDo6zH67+0yOfPwsfpv/ajugoKyOe5+8TjtcTX1Lao0uVMaXN0Vmlld0l0ZW3W0NPQQnV0dG9uu/LV30NoZWNrYWJsZbXE19PA4L/YvP61xLuwo6zEx8O0xKzIz2ZvY3Vzyse9u7j4wcvX07/YvP6jrLb4IExpc3RWaWV3tcRJdGVtxNyxu9Gh1tC1xLv5tKHKx8v8xNy78cihRm9jdXOjrNKyvs3Kx8u1ztLDx7/J0tTNqLn9vatMaXN0Vmlld9bQSXRlbdbQsPy6rLXEy/nT0L/YvP61xCBmb2N1c2FibGXK9NDUyejWw86qZmFsc2WjrNXi0fm1xLuwTGlzdFZpZXe1xEl0ZW3X1Lavu/G1w8HLRm9jdXO1xMioz96jrNKyvs2/ydLUsbvRodbQwcuhowoKPGJyPgoKCs7Sw8e/ydLUzai5/bbUSXRlbSBMYXlvdXS1xLj5v9i8/sno1sPG5GFuZHJvaWQ6ZGVzY2VuZGFudEZvY3VzYWJpbGl0eT0="blocksDescendants"即可,這樣Item Layout就屏蔽了所有子控件獲取Focus的權限,不需要針對Item Layout中的每一個控件重新設置focusable屬性了,如此就可以順利的響應onItemClickListener中的onItemClick()方法了。
我看一下ListView處理touch事件的方法,AbsListView#onTouchEvent()方法中,代碼如下:
[java] view
plaincopy
-
...
-
-
case MotionEvent.ACTION_UP: {
-
switch (mTouchMode) {
-
case TOUCH_MODE_DOWN:
-
case TOUCH_MODE_TAP:
-
case TOUCH_MODE_DONE_WAITING:
-
final int motionPosition = mMotionPosition;
-
final View child = getChildAt(motionPosition - mFirstPosition);
-
-
-
final float x = ev.getX();
-
final boolean inList = x > mListPadding.left
-
&& x < getWidth() - mListPadding.right;
-
-
-
if (child != null && !child.hasFocusable() && inList) {
-
if (mTouchMode != TOUCH_MODE_DOWN) {
-
child.setPressed(false);
-
}
-
-
-
if (mPerformClick == null) {
-
mPerformClick = new PerformClick();
-
}
-
-
-
...
總結如下:
原因:
ListView中的Item內部的View獲得了焦點,如Button, Checkbox等。
解決辦法:
不要讓ListView中的Item內部的View獲得焦點就OK了,這樣做:android:descendantFocusability="blocksDescendants"
public static final int descendantFocusability
Defines the relationship between the ViewGroup and its descendants when looking for a View to take focus.
Constant
Value
Description
beforeDescendants
0
The ViewGroup will get focus before any of its descendants.
afterDescendants
1
The ViewGroup will get focus only if none of its descendants want it.
blocksDescendants
2
The ViewGroup will block its descendants from receiving focus.
注意:
還有一種情況也會導致OnItemClickListener或OnItemLongClickListener回調不會執行,那就是ListView的child設置了onClickListener或onLongClickListener。我們可以通過源代碼看出,在你調用setOnClickListener()方法後,它會調用setClickable(true),在onTouchEvent裡面的實現如下:
[java] view
plaincopy
-
if (((viewFlags & CLICKABLE) == CLICKABLE ||
-
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
-
-
// ....
-
-
return true;
-
}
當一個View在onTouchEvent裡面返回true後,ListView就不會正常接收到事件。
5. ListView不要做的事
1)android:layout_height="warp_content"?
有時候我們很容易讓ListView的高度是wrap_content,這樣做,很容易導致性能問題,wrap_content意味著as big as my children,這會導致1)測量大量的children;2)當添加child時,容易引起ListView重新發生layout,這又可能引發child的layout。我曾經遇到過一個問題,就是在維護別的寫的代碼時,ListView上下滑動速度特別慢,我把adapter等地方都優化完後,仍然一樣,最終才發現ListView的高度是wrap_content,我把其改為match_parent之後,性能一下就正常了。
2)ListView套在ScrollView中,這種用法實在蛋痛,不解釋。