ListView的item展開後完整顯示

原文地址: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


發佈了6 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章