Android ListView OnItemLongClick和OnItemClick事件內部細節分享以及幾個比較特別的屬性

muzhi121發表於2014-07-20

本文轉自 http://blog.sina.com.cn/s/blog_783ede030101bnm4.html 作者kiven

 辭職3,4個月在家休息,本以為樓主要程式設計師逆襲,結果失敗告終繼續碼農生涯今天開始更新部落格。

    正文。
    專案中有個ListView內容比較複雜現在要新增長按刪除功能。樓主自然想到利用ListView的onItemLongClick事件來處理。結果可想而知在實際體驗中很不好,會出現失靈有些選項能觸發onItemLongClick事件有些卻沒反應。樓主去網上看了都是focusable要設定成false。但依照樓主的經驗這隻能解決都不能觸發onItemLongClick事件問題,像樓主遇到的有些可以有些失靈的估計不是一劑良藥。所以樓主具體分析了一下內部細節,希望能給後來的人提供一些幫助。
    首先看一下foucsable造成的事件沒反應的問題。
原因:
若你的item中有button或者checkbox等控制元件,預設情況下焦點focus是最先交給這些子控制元件,而ListView的Item能被選中的基礎是它能獲取Focus焦點,所以,我們可以通過將ListView中Item中包含的所有控制元件的focusable屬性設定為false,這樣ListView的Item就自動獲得了Focus焦點的許可權,也就可以被選中了,同時也會響應onItemClickListener中的onItemClick()方法。
解決辦法:(以下兩種辦法任意一種都可)
1.將ListView的Item Layout中的所有子控制元件focusable屬性設定為false
2.將item layout的根控制元件設定屬性(推薦輕量級特別是修改別人的程式碼)
android:descendantFocusability=”blocksDescendant”
這樣Item Layout就遮蔽了所有子控制元件獲取Focus焦點的許可權,不需要針對Item Layout中的每一個控制元件重新設定focusable屬性
分析:
    為什麼不設定focusable為false就不會觸發onItemLongClick事件呢。通過檢視AbsListView的原始碼你會發現如果focusable不為false跟本就不分發touch事件。程式碼如下:
 
Android <wbr>ListView <wbr>OnItemLongClick和OnItemClick事件內部細節分享

附android:descendantFocusability用法簡析
    開發中很常見的一個問題,專案中的listview不僅僅是簡單的文字,常常需要自己定義listview,自己的Adapter去繼承BaseAdapter,在adapter中按照需求進行編寫,問題就出現了,可能會發生點選每一個item的時候沒有反應,無法獲取的焦點。原因多半是由於在你自己定義的Item中存在諸如ImageButton,Button,CheckBox等子控制元件(也可以說是Button或者Checkable的子類控制元件),此時這些子控制元件會將焦點獲取到,所以常常當點選item時變化的是子控制元件,item本身的點選沒有響應。
    這時候就可以使用descendantFocusability來解決啦,API描述如下:
 Android <wbr>ListView <wbr>OnItemLongClick和OnItemClick事件內部細節分享

該屬性是當一個為view獲取焦點時,定義viewGroup和其子控制元件兩者之間的關係。
屬性的值有三種:
        beforeDescendants:viewgroup會優先其子類控制元件而獲取到焦點
        afterDescendants:viewgroup只有當其子類控制元件不需要獲取焦點時才獲取焦點
        blocksDescendants:viewgroup會覆蓋子類控制元件而直接獲得焦點
通常我們用到的是第三種,即在Item佈局的根佈局加上android:descendantFocusability=”blocksDescendants”的屬性就好了
 
實際情況根據上面的設定樓主的問題依然沒有解決,樓主堅信上面的文章的權威性,再次分析自己的程式碼。發現在adapter中convertView被設定clickable為true(另一位同事為了遮蔽item的黃色selector設定的待會兒樓主會介紹修改listview點選黃色背景修改)這必然導致touch事件被convertView截獲,由於convertView是item的根容器,所以就無法點選到listView使listview獲取touch事件。去掉item跟容器的clickable設定。果然能觸發onItemLongClick事件。失靈問題解決後發現在內容很少簡單的item沒問題,但在複雜的item時item內的子view不可避免的要設定click事件,而這個view又佔item的大部分地方,總不能每次要長按長按item狹小的空餘地方來觸發onItemLongClick事件吧。樓主這裡要給一個比較輕量級的解決方案。程式碼片段如下:
//Adapter 內部程式碼:
//holder.audioPanel這個view佔很大的地方但又必須實現click事件,還好它有也有longclick事件
holder.audioPanel.setOnClickListener(this);
holder.audioPanel.setOnLongClickListener(this);
//關鍵在這裡手動觸發listview的長按方法自然又回到了正常的邏輯了。
@Override
public boolean onLongClick(View v) {
mListView.performLongClick();
return false;
}
這下就完美解決觸發onItemLongClick事件了不管listview介面多麼複雜。
對了如何去除listview預設的黃色背景。樓主是這樣解決的:
Android <wbr>ListView <wbr>OnItemLongClick和OnItemClick事件內部細節分享

 
附錄一下樓主網上看到的一些小細節:
1.ListView本身可不可以呼叫setOnClickListner()? 
程式碼上可以,但是執行馬上會丟出異常,所以是不可以攔截Listview本身的click事件。 
2.ListView.setOnItemClickListener設定的listener什麼時候會被呼叫? 
當點選某行內容是會被呼叫,但是如果這行內容中包含Button,ImgButton等控制元件時就不會被呼叫,為什麼以及怎麼解決見後面。 
3.ListView.setOnItemLongClickListener設定的listener什麼時候被呼叫? 
當長按某一行時會被呼叫,而且在抬起之前就已經呼叫了。 
4.收到LongClick的呼叫後還會呼叫click嗎? 
這個要根據LongClick listener的返回值來決定。
Java程式碼 
1.lv.setOnItemLongClickListener(new OnItemLongClickListener() {   
2.            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {  
3.                System.out.println("Item LONG clicked. Position:" + position);   
4.                return false;   
5.            }   
6.        });  
如果返回false那麼click仍然會被呼叫。而且是先呼叫Long click,然後呼叫click。 
如果返回true那麼click就會被吃掉,click就不會再被呼叫了。 
5.監聽click以及long click影響彈出選單嗎? 
click不影響;long click如果返回true那麼就會吃掉click事件,導致選單不能彈出。 
 
附錄:
Android使用ListView應該注意的地方
在ListView中設定Selector為null會報空指標? 
mListView.setSelector(null);//空指標 
試試下面這種: 
mListView.setSelector(new ColorDrawable(Color.TRANSPARENT)); 
 
如何讓ListView初始化的時候就選中一項? 
ListView需要在初始化好資料後,其中一項需要呈選中狀態。所謂"選中狀態"就是該項底色與其它項不同,setSelection(position)只能定位到某個item,但是無法改變底色呈高亮。setSelection(position)只能讓某個item顯示在可見Item的最上面(如果Item超過一屏的話)! 就是所謂的firstVisibleItem啦! 
如果想要實現效果可以在listview所繫結的adapter裡的getView函式裡去完成一些具體的工作。可以記下你要高亮的那個item的index,在getView函式裡判斷index(也就是position),如果滿足條件則載入不同的背景。 
 
ListView的右邊滾動滑塊啟用方法? 
    很多開發者不知道ListView列表控制元件的快速滾動滑塊是如何啟用的,其實輔助滾動滑塊只需要一行程式碼就可以搞定,如果你使用XML佈局只需要在ListView節點中加入  android:fastScrollEnabled="true" 這個屬性即可,而對於Java程式碼可以通過myListView.setFastScrollEnabled(true); 來控制啟用,引數false為隱藏。 
    還有一點就是當你的滾動內容較小,不到當前ListView的3個螢幕高度時則不會出現這個快速滾動滑塊,該方法是AbsListView的基礎方法,可以在ListView或GridView等子類中使用快速滾動輔助。 
 
1. 更新ListView中的資料,通過呼叫BaseAdapter物件的notifyDataSetChanged()方法: 
      mAdapter.notifyDataSetChanged(); 
 
2. 每個listview都有無效的位置,如第一行的前一行,最後一行的後一行,這個無效的位置是一個常量. 
     ListView.INVALID_POSITION 
 
3. 有時我們需要在程式中通過點選按鈕來控制ListView行的選中,這就用到了在程式中如何使用程式碼來選擇ListView項. 
         mListView.requestFocusFromTouch(); 
         mListView.setSelection(int index); 
 
     第一條語句並不是必須的,但是若你ListView項中含有Button,RadioButton,CheckBox等比ListView取得 焦點優先順序高的控制元件時,那麼第一條語句是你必須加的. 
 
4.  同樣的,若你ListView項中含有Button,RadioButton,CheckBox等比ListView取得 焦點優先順序高的控制元件時,ListView的setOnItemClickListener是不被執行的,這時你需要在你的xml檔案中對這些控制元件新增  android:focusable="false" 注意這條語句要放在xml檔案中修改,在程式碼中使用是無效的. 
 
5. 如何保持ListView的滾動條一直顯示,不隱藏呢:  xml檔案中做如下修改    android:fadeScrollbars="false" 
 
6. ListView本身有自己的按鍵事件,即你不需要設定方向鍵的標識,按下方向鍵ListView就會有預設的動作,那如何進行控制,編寫自己的onKey呢,你需要在Activity中重寫dispatchKeyEvent(KeyEvent event);方法,在這裡面定義你自己的動作就可以了 
 
ListView 自定義滾動條樣式: 
<ListView android:id="@android:id/list" 
        android:layout_width="match_parent" 
        android:layout_height="0dip" 
        android:layout_weight="1" 
        android:stackFromBottom="true"//從下開始顯示條目 
        android:transcriptMode="normal" 
        android:fastScrollEnabled="true" 
        android:focusable="true" 
        android:scrollbarTrackVertical="@drawable/scrollbar_vertical_track" 
        android:scrollbarThumbVertical="@drawable/scrollbar_vertical_thumb" 
/> 
//scrollbar_vertical_track,crollbar_vertical_thumb自定義的xml檔案,放在Drawable中,track是指長條,thumb是指短條 
去掉ListView Selector選種時黃色底紋一閃的效果: 
 
Xml程式碼   
<?xml version="1.0" encoding="utf-8"?>  
<shape xmlns:android="http://schemas.android.com/apk/res/android">  
    <solid android:color="@android:color/transparent"/>  
    <corners android:radius="0dip" />      
</shape>  
//listview.setSelector(R.drawable.thisShape);  
 
或者還有一種辦法: 
在Adapter中重寫public boolean isEnabled(int position)方法,將其返回false就可以了,推薦採用此種辦法,具體見http://gundumw100.iteye.com/admin/blogs/850654 
 
Java程式碼   
public boolean isEnabled(int position) {  
    // TODO Auto-generated method stub  
    return false;  
}  
 
 
ListView幾個比較特別的屬性 
首先是stackFromBottom屬性,這隻該屬性之後你做好的列表就會顯示你列表的最下面,值為true和false 
android:stackFromBottom="true"             
 
第二是transciptMode屬性,需要用ListView或者其它顯示大量Items的控制元件實時跟蹤或者檢視資訊,並且希望最新的條目可以自動滾動到可視範圍內。通過設定的控制元件transcriptMode屬性可以將Android平臺的控制元件(支援ScrollBar)自動滑動到最底部。 
android:transcriptMode="alwaysScroll"    
 
第三cacheColorHint屬性,很多人希望能夠改變一下它的背景,使他能夠符合整體的UI設計,改變背景背很簡單隻需要準備一張圖片然後指定屬性 android:background="@drawable/bg",不過不要高興地太早,當你這麼做以後,發現背景是變了,但是當你拖動,或者點選list空白位置的時候發現ListItem都變成黑色的了,破壞了整體效果。 
如果你只是換背景的顏色的話,可以直接指定android:cacheColorHint為你所要的顏色,如果你是用圖片做背景的話,那也只要將android:cacheColorHint指定為透明(#00000000)就可以了 
 
第四divider屬性,該屬性作用是每一項之間需要設定一個圖片做為間隔,或是去掉item之間的分割線android:divider="@drawable/list_driver"  其中  @drawable/list_driver 是一個圖片資源,如果不想顯示分割線則只要設定為android:divider="@drawable/@null" 就可以了 
 
第五fadingEdge屬性,上邊和下邊有黑色的陰影android:fadingEdge="none" 設定後沒有陰影了~ 
 
第五scrollbars屬性,作用是隱藏listView的滾動條,android:scrollbars="none"與setVerticalScrollBarEnabled(true);的效果是一樣的,不活動的時候隱藏,活動的時候也隱藏 
 
第六fadeScrollbars屬性,android:fadeScrollbars="true"  配置ListView佈局的時候,設定這個屬性為true就可以實現滾動條的自動隱藏和顯示。 
 
如何在使用gallery在flinging拖動時候不出現選擇的情況? 
這時候需要注意使用 
gallery.setCallbackDuringFling(false) 
 
如何讓ListView自動滾動? 
注意stackFromBottom以及transcriptMode這兩個屬性。類似Market客戶端的低端不斷滾動。 
<ListView android:id="listCWJ"  
     android:layout_width="fill_parent"  
     android:layout_height="fill_parent"  
     android:stackFromBottom="true"    
     android:transcriptMode="alwaysScroll"  
/> 
如何遍歷listView 的的單選框? 
 
Java程式碼   
ListView listView = (ListView)findViewById(R.id.配置檔案中ListView的ID);  
//全選遍歷ListView的選項,每個選項就相當於佈局配置檔案中的RelativeLayout  
for(int i = 0; i < listView.getChildCount(); i++){  
      View view = listView.getChildAt(i);  
      CheckBox cb = (CheckBox)view.findViewById(R.id.CheckBoxID);  
      cb.setChecked(true);  
}  
 
如何讓ListView中TextView的字型顏色跟隨焦點的變化? 
我們通常需要ListView中某一項選中時,他的字型顏色和原來的不一樣。 如何設定字型的顏色呢? 在佈局檔案中TextColor一項來設定顏色,但是不是隻設定一種顏色,而是在不同的條件下設定不同的顏色: 下面是個例子: 
 
Xml程式碼   
<?xml version="1.0" encoding="utf-8" ?>  
<selector xmlns:android="http://schemas.android.com/apk/res/android">  
<item android:state_enabled="false" android:color="@color/orange"></item>  
<item android:state_window_focused="false" android:color="@color/orange"></item>  
<item android:state_pressed="true" android:color="@color/white"></item>  
<item android:state_selected="true" android:color="@color/white"></item>  
<item android:color="@color/orange"></item>  
</selector>   
在獲取焦點或者選中的情況下設定為白色,其他情況設定為橘黃色。  
 
如何自定義ListView行間的分割線? 
所有基於ListView或者說AbsListView實現的widget控制元件均可以通過下面的方法設定行間距的分割線,分割線可以自定義顏色、或圖片。 
在ListView中我們使用屬性android:divider="#FF0000" 定義分隔符為紅色,當然這裡值可以指向一個drawable圖片物件,如果使用了圖片可能高度大於系統預設的畫素,可以自己設定高度比如6個畫素android:dividerHeight="6px" ,當然在Java中ListView也有相關方法可以設定。 
 
ListView不通過notifyDataSetChanged()更新指定的Item 
Listview一般大都是通過notifyDataSetChanged()來更新listview,但通過notifyDataSetChanged()會把介面上現實的的item都重繪一次,這樣會影響ui效能。 
可以通過更新指定的Item提高效率,虛擬碼如下: 
 
Java程式碼   
private void updateView(int itemIndex){    
  int visiblePosition = yourListView.getFirstVisiblePosition();    
  View v = yourListView.getChildAt(itemIndex - visiblePosition);//Do something fancy with your listitem view    
  TextView tv = (TextView)v.findViewById(R.id.sometextview);  
  tv.setText("Hi! I updated you manually");  
}  

 

相關文章