原文地址:http://blog.csdn.net/lintax/article/details/72598805
手機屏幕畢竟有限,當我們要顯示較多數據時便不得不捨去一些次要信息,將主要信息優先顯示,也使顯示效果更加簡潔美觀。遇到類似的需求,我們使用最多的就是 ListView ,而如果每次點擊一個 Item 都要跳轉到下一頁查看詳情,查看另一個時還要返回列表重新進入另一條詳情,使得操作繁瑣體驗降低。此時可隱藏和展開 Item 的 ListView 便應運而生,具體實現可以參考這裏:
http://blog.csdn.net/a_running_wolf/article/details/50617094
主要採用的方法就是給每個Item添加一個標誌,記錄是否要展開。在getView()中,判斷該標誌,若是不需要展開的,隱藏不需要顯示的;若需要顯示,則將隱藏部分也顯示出來。
但是這樣做之後,確實實現了item展開的功能,卻有一個用戶體驗的問題:
點擊屏幕中靠下的幾個訂單,雖然代碼中確實將隱藏項設置爲可見了,可是在屏幕上看不到完整的展開後的效果。也就是說,用戶點擊了,但是看不到完全的展開的效果。
這時,就需要有一個自動向上滾動,來將這個item完整的展示給用戶看。
先上一個效果圖:
下面就是如何實現了。
先整理好思路:
想把item展開後顯示完整,分兩種情況,就是展開後,當前item就已經全部顯示了。這種情況,就不需要做特別的動作,來滑動屏幕了。
另一種情況,就是展開後,當前item只能看見一部分。我們希望能將該item的餘下部分都展示出來。
這樣,就有幾個問題需要解決:
1,怎麼判斷該item能全部展示?
2,若不能全部展示,該滑動屏幕多大距離來實現恰好的展示呢?
3,如何實現自動滑動呢?
首先要明白幾個概念,屏幕的高度,與listView的高度,是兩個概念。我們關注的是listView的高度。
我們可以通過跟蹤展開前後的listView與item的位置信息,可以瞭解view的展示情況。
點擊第一個item展開,再次點擊收起,我跟蹤打印信息:
主要是查看 item.getTop(),item.getBottom(),listView.getHeight(),item.getHeight()。
第一次:
onItemClick:65: position=0
onItemClick:77: item.getTop=0 getBottom=89
onItemClick:78: listView.getHeight=1024 item.getHeight=89
第二次:
onItemClick:65: position=0
onItemClick:77: item.getTop=0 getBottom=290
onItemClick:78: listView.getHeight=1024 item.getHeight=290
查看item的高度:item.getHeight,第一次爲89,第二次爲290。最開始可能不知道哪次是展開,哪次是未展開,但是通過這個高度值,就可以判斷出來了,較大的那次爲展開狀態。
我們重點關注的是,item展開後,能否在listView中完整顯示。
以listView爲一個容器,看能否完整包含某個item,其實就是做一個比較:item.bottom<=listView.height
若符合條件的,就是能全部顯示的;
若不滿足條件的,則不能全部顯示,需要進行滑動。
第二個問題:滑動多少?
很簡單,就是滑動出不能顯示的那部分:
滑動距離 = item.bottom - listView.height
3,如何實現自動滑動呢?
listView的滑動,有多種方法,我測試了7種滑動方法,簡單介紹如下:
1,將View滑動到指定位置:
void scrollTo(int x, int y)
使用這個方法,執行之後,看起來視圖變了位置,再次操作進行滑動時,發現它又先回到原來位置了,再開始滑動。就是在視覺上給人畫面一閃的感覺,且回到原來位置,不符合操作者的想法。
原因:scrollTo()是View的方法,它用於滑動View的內容,而不是改變View本身所處的位置。
單獨的View滑動很少見,通常是ViewGroup調用scroll方法滑動子控件的位置。
下面要說的幾個滑動方法,是ListView自帶的,或者繼承自AbsListView的,都是ViewGroup。
我們跟蹤一下代碼,可以知道ListView是繼承自ViewGroup的,繼承關係如下:
ListView->AbsListView->AdapterView->ViewGroup
2,指定某個子項顯示到listView的頂部:
void setSelection(int position)
這個方法沒有動畫,是直接到位。並且只能顯示到頂部,太過簡單粗暴,不是我們期望的優雅的姿勢。
3,指定某項顯示到距離listView的頂部一個距離的位置:
void setSelectionFromTop(int position, int y)
可以通過設置偏移值來實現恰好完整展示item:
y=listView.getHeight()-item.getHeight();
這個方法沒有過渡動畫,但是已經能實現我們的功能了。
4,做相對滑動,滑動距離爲指定的item的個數,平滑滑動:
void smoothScrollByOffset(int offset)
注意這個參數offset,它不是指滑動距離,而是指的item的個數。
我通過試驗,發現是這樣調用:
listView.smoothScrollByOffset(1);
能夠實現將底部展開的item顯示完整的效果,但是有個小的問題:
若未展示部分較少時,會多展示一個item出來。
如果這個多出來的item是之前展開過的,那麼,這個多出來的部分,會導致屏幕滑動相當長一段距離,會讓用戶感覺比較奇怪,所以,這個方法也不太合適。
5,顯示出指定項,平滑滑動(若該項已經完整顯示,則不滑動):
void smoothScrollToPosition(int position)
通過測試,這個方法其實是相當符合我們的要求的。如果顯示全了的item,沒有屏幕滑動,如果指定position的item沒顯示全,會進行相應的滑動,進行完整的展示出來。
繼續研究:
6,在指定時間內,滑動相對距離
void smoothScrollBy(int distance, int duration)
可以通過設置distance來實現恰好完整展示item:
distance=item.getBottom()-listView.getHeight();
另外一個參數duration,是設置滑動的時間,以ms爲單位。
這個方法相比較於上一個方法,好處在於可以指定滑動的時間,也就是平滑移動的效果我們可以自己控制,更靈活一些。
7,指定某項從頂部向下偏移的距離,在指定時間內滑動到位
void smoothScrollToPositionFromTop(int position, int offset, int duration)
可以通過設置offset來實現恰好完整展示item:
offset=listView.getHeight()-item.getHeight();
它也有設置滑動的時間的參數duration,對於實現本功能而言,與上一個方法效果相同。
通過試驗這7種滑動,最後有4種方法可以實現目標。
void setSelectionFromTop(int position, int y)
void smoothScrollToPosition(int position)
void smoothScrollBy(int distance, int duration)
void smoothScrollToPositionFromTop(int position, int offset, int duration)
如果考慮到平滑滑動的效果的話,去掉快速滑動的setSelectionFromTop(),還有3種。
在這3種方法中,
void smoothScrollToPosition(int position)
是最簡單的選擇,只有一個參數position,不需要計算滑動的距離。
然而,再仔細對比這3種平滑移動的效果,就會發現smoothScrollToPosition()由於沒有設置移動時間的參數,導致平滑移動的效果比較一般,不如另外兩個靈活。
然而,smoothScrollToPosition()有一個好處,就是對於頂部未顯示完整的item,它也能調整爲完整顯示!
那麼,既然我們爲了追求最佳的效果,就算是使用下面兩個函數,
void smoothScrollBy(int distance, int duration)
void smoothScrollToPositionFromTop(int position, int offset, int duration)
也要實現同樣的效果:不僅完整展示底部的item,也要展示完整頂部的item。
其實只要真的關注到了這個問題,解決起來也並不複雜:
添加上頂部未顯示全的判斷 item.top<0
然後向下滑動指定距離即可,具體到這兩個方法,按如下形式調用:
listView.smoothScrollToPositionFromTop(position, 0, 300);
listView.smoothScrollBy(itemTop, 300);
現在,怎麼滑動的問題已經解決了。
那麼,在那個地方來調用這個滑動的方法呢?
首先,想一下,何時滑動?
自然是在點擊之後。所以滑動方法的調用,也是在點擊動作之後。
這個點擊,通常在onItemClick()中處理。不過,有時我們會發現ListView的onItemClick執行不到,這是爲什麼呢?
在網上查到的通常的說法,是由於item中有button或checkBox。可是在實際使用ListView過程中,我遇到過不符合這種說法的情況,我做了一個測試:
在一個沒有button、checkBox的item中,對一個LinearLayout使用了onClickListener,就不能走onItemClick了。
而對於一個有button的item,只要adapter裏面沒有出現onClickListener,就仍然會走onItemClick。
由此可見,核心問題在於,就是在ListView的適配器類中有setOnClickListener(),截獲了OnClick 事件,導致ListView獲取不到OnClick 事件了。OnClick 的響應優先級:子控件(元控件)> 父佈局,但是不像 onTouch 事件有Boolean 返回值那樣,OnClick 事件是沒有返回值的,即是“阻斷式響應”,不會再響應它所歸屬的上層控件。
這樣,我們也就明確了,調用滑動方法的地方,有兩處:
1,ListView的onItemClick()中;
2,adapter中展開動作的OnClick()中;
確定調用的地方了,還有一個調用的時機。直接在onItemClick()中調用,是不能實現完全展示Item的效果的。其實在點擊的那個時刻,雖然設置了View的可見,但是ListView還沒有做Measure動作,所以獲取不到展開後的Item的尺寸。我們可以使用一個延遲來實現:
parent.postDelayed(new Runnable() {
然後在裏面的run()方法中獲取Item的新的尺寸。例如,我測試了,使用延遲100ms就能獲取到執行Measure之後的值。
下面是我在onItemClick()中的實現:
@Override
public void onItemClick(final AdapterView<?> parent, final View item, final int position, long id) {
LogUtil.logWithMethod(new Exception(),"position="+position);
int flagExpand = 1-Integer.parseInt(datas.get(position).get("flagExpand"));
datas.get(position).put("flagExpand",""+flagExpand);
// 查看ListView與當前item的位置信息(點擊時刻的)
LogUtil.logWithMethod(new Exception(),"item.getTop="+item.getTop()+" getBottom="+item.getBottom());
LogUtil.logWithMethod(new Exception(),"listView.getHeight="+listView.getHeight()+" item.getHeight="+item.getHeight());
//要獲取點擊之後的item的視圖信息,需要做一個延遲,等待系統測量完成
parent.postDelayed(new Runnable() {
@Override
public void run() {
// 查看ListView與當前item的位置信息(點擊動作之後的--完成展開或收回動作的)
int listViewHeight = listView.getHeight();
int viewBottom = item.getBottom();//item.getTop() + item.getHeight(); //展開後的高度
LogUtil.logWithMethod(new Exception(),"listView.getHeight="+listViewHeight+" item.getHeight="+item.getHeight());
LogUtil.logWithMethod(new Exception(),"viewBottom="+viewBottom+" item.getBottom="+item.getBottom());
int itemTop = item.getTop();
if(itemTop < 0){
LogUtil.logWithMethod(new Exception(),"scroll offset="+itemTop);
// listView.smoothScrollToPositionFromTop(position, 0, 300);//指定某項從頂部向下偏移的距離,在指定時間內滑動到位
listView.smoothScrollBy(itemTop, 300);//做相對移動,在指定時間內完成
// listView.setSelectionFromTop(position,0);//快速滑動
// listView.setSelection(position);//快速滑動
} else {
//只有不能完整顯示的,才需要上滑
if( viewBottom> listViewHeight) {
LogUtil.logWithMethod(new Exception(),"scroll offset="+(viewBottom-listViewHeight));
listView.smoothScrollBy(viewBottom-listViewHeight, 300);//做相對移動,在指定時間內完成
// listView.smoothScrollToPositionFromTop(position, listViewHeight-item.getHeight(), 300);//指定某項從頂部向下偏移的距離,在指定時間內滑動到位
// listView.setSelectionFromTop(position,listViewHeight-item.getHeight());//快速滑動,參數不同,指定希望達到的位置
// listView.smoothScrollByOffset(1);//若展示不全的,向下滑動到展示全。若未展示部分較少的,會多展示一個item
// listView.scrollTo(0, viewBottom-listViewHeight);//快速滑動。且效果不好,item的位置會閃回。只是顯示的畫面變了,沒有改變View本身所處的位置
}
}
//雖然是平滑移動,但是速度還是比較快的,滑動速度不能指定
// listView.smoothScrollToPosition(position);//顯示出指定項,平滑滑動 //點擊的,就已經是可見的,不需要滑動了 //最簡便的方法,頂部顯示不全的,也向下滑動
}
},100);
adapter.notifyDataSetChanged();
}
而對於adapter的getView()裏面的OnClick(),需要做一些調整:
1,調整item、listView的獲取;
2,轉移到 onClick()中,使用final傳遞參數;
這裏就不展示代碼了,前面的例子已經將邏輯講清楚了。
有需要的同學,可以到demo中去看具體的實現。
demo地址:
http://download.csdn.net/detail/lintax/9848239
參考:
http://blog.csdn.net/a_running_wolf/article/details/50617094
http://blog.csdn.net/lilybaobei/article/details/8142987