天天看點

listView下拉重新整理(仿sina微網誌Android用戶端效果)

  這個下拉效果在網上最早的例子恐怕就是johan nilsson的實作,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html。

      後面的很多例子應該都是仿照這個寫的,下面的這個例子就是對這個例子的修改,先看下一個點選的效果,我看到其他的分析部落格裡面沒有談到這一點,在這個代碼中,我們一直看到是listview的第二項,而listview的第一項被遮擋了起來,滑動至第一項:

listView下拉重新整理(仿sina微網誌Android用戶端效果)

       點選頭條,頭條會變成以下:

listView下拉重新整理(仿sina微網誌Android用戶端效果)

然後,過一段時間,重新整理完成以後,listview又setselection(1),增加一條資料,同時,把頂部給遮擋住:

listView下拉重新整理(仿sina微網誌Android用戶端效果)

這是點選重新整理,然後是下拉重新整理:

listView下拉重新整理(仿sina微網誌Android用戶端效果)
listView下拉重新整理(仿sina微網誌Android用戶端效果)

最後結果和點選重新整理相同。那現在開始看下代碼:

首先看下所用到的控件和變量:

[java] view

plaincopy

// 狀态  

    private static final int tap_to_refresh = 1;//點選重新整理  

    private static final int pull_to_refresh = 2;  //拉動重新整理   

    private static final int release_to_refresh = 3; //釋放重新整理  

    private static final int refreshing = 4;  //正在重新整理  

    // 目前滑動狀态  

    private int mcurrentscrollstate;  

    // 目前重新整理狀态   

    private int mrefreshstate;  

    //頭視圖的高度  

    private int mrefreshviewheight;  

    //頭視圖 原始的top padding 屬性值  

    private int mrefreshoriginaltoppadding;  

    private int mlastmotiony;  

    // 監聽對listview的滑動動作  

    private onrefreshlistener monrefreshlistener;  

    //箭頭圖檔  

    private static  int refreshicon = r.drawable.goicon;      

    //listview 滾動監聽器  

    private onscrolllistener monscrolllistener;  

    private layoutinflater minflater;  

    private relativelayout mrefreshview;  

    //頂部重新整理時出現的控件  

    private textview mrefreshviewtext;  

    private imageview mrefreshviewimage;  

    private progressbar mrefreshviewprogress;  

    private textview mrefreshviewlastupdated;  

    // 箭頭動畫效果  

    //變為向下的箭頭  

    private rotateanimation mflipanimation;  

    //變為逆向的箭頭  

    private rotateanimation mreverseflipanimation;  

    //是否反彈  

    private boolean mbouncehack;  

看下點選重新整理的代碼過程:

在init()方法中初始化各個控件及設定監聽:

private void init(context context) {  

        // load all of the animations we need in code rather than through xml     

        mflipanimation = new rotateanimation(0, -180,rotateanimation.relative_to_self,   

                0.5f,rotateanimation.relative_to_self, 0.5f);  

        mflipanimation.setinterpolator(new linearinterpolator());  

        mflipanimation.setduration(250);  

        mflipanimation.setfillafter(true);  

        mreverseflipanimation = new rotateanimation(-180, 0,rotateanimation.relative_to_self, 0.5f,rotateanimation.relative_to_self, 0.5f);  

        mreverseflipanimation.setinterpolator(new linearinterpolator());  

        mreverseflipanimation.setduration(250);  

        mreverseflipanimation.setfillafter(true);  

        minflater = (layoutinflater) context.getsystemservice(context.layout_inflater_service);  

        mrefreshview = (relativelayout) minflater.inflate(r.layout.pull_to_refresh_header, this, false);  

        mrefreshviewtext =(textview) mrefreshview.findviewbyid(r.id.pull_to_refresh_text);  

        mrefreshviewimage =(imageview) mrefreshview.findviewbyid(r.id.pull_to_refresh_image);  

        mrefreshviewprogress =(progressbar) mrefreshview.findviewbyid(r.id.pull_to_refresh_progress);  

        mrefreshviewlastupdated =(textview) mrefreshview.findviewbyid(r.id.pull_to_refresh_updated_at);  

        mrefreshviewimage.setminimumheight(50);  

        mrefreshview.setonclicklistener(new onclickrefreshlistener());  

        mrefreshoriginaltoppadding = mrefreshview.getpaddingtop();  

        mrefreshstate = tap_to_refresh;  

        //為listview頭部增加一個view    

        addheaderview(mrefreshview);  

        super.setonscrolllistener(this);  

        measureview(mrefreshview);  

        mrefreshviewheight = mrefreshview.getmeasuredheight();    

    }  

我們看到,mrefreshview控件既是listview用于重新整理的頭控件,這裡它設定了監聽事件:

mrefreshview.setonclicklistener(new onclickrefreshlistener());  

我們再來看下監聽事件的定義:

private class onclickrefreshlistener implements onclicklistener {  

        @override  

        public void onclick(view v) {  

            if (mrefreshstate != refreshing) {  

                prepareforrefresh();    

                onrefresh();   

            }  

        }  

調用了preparforrefresh()(準備重新整理)和onrefresh()(重新整理)兩個方法,然後在檢視這兩個方法的定義:

public void prepareforrefresh() {  

        resetheaderpadding();   // 恢複header的邊距   

        mrefreshviewimage.setvisibility(view.gone);  

        // we need this hack, otherwise it will keep the previous drawable.  

        // 注意加上,否則仍然顯示之前的圖檔    

        mrefreshviewimage.setimagedrawable(null);  

        mrefreshviewprogress.setvisibility(view.visible);  

        // set refresh view text to the refreshing label  

       mrefreshviewtext.settext(r.string.pull_to_refresh_refreshing_label);  

        mrefreshstate = refreshing;  

    public void onrefresh() {  

        if (monrefreshlistener != null) {  

            monrefreshlistener.onrefresh();  

其中,後者還是回調方法。

我們看下preparforrefresh()方法中,引用了resetheadpadding()方法:

/**  

     * sets the header padding back to original size. 

     * 将head的邊距重置為初始的數值  

     */   

    private void resetheaderpadding() {  

        mrefreshview.setpadding(  

                mrefreshview.getpaddingleft(),  

                mrefreshoriginaltoppadding,  

                mrefreshview.getpaddingright(),  

                mrefreshview.getpaddingbottom());  

    }      

從新設定下header距上下左右的距離。

最重要的方法應該是:onscroll()和ontouchevent()方法,先看下ontouchevent()方法:

@override  

    public boolean ontouchevent(motionevent event) {  

        //目前手指的y值  

        final int y = (int) event.gety();  

        mbouncehack = false;     

        switch (event.getaction()) {  

            case motionevent.action_up:  

                //将垂直滾動條設定為可用狀态  

                if (!isverticalscrollbarenabled()) {  

                    setverticalscrollbarenabled(true);  

                }  

                if (getfirstvisibleposition() == 0 && mrefreshstate != refreshing) {  

                    // 拖動距離達到重新整理需要  

                    if ((mrefreshview.getbottom() >= mrefreshviewheight  

                            || mrefreshview.gettop() >= 0)  

                            && mrefreshstate == release_to_refresh) {    

                        // 把狀态設定為正在重新整理  

                        // initiate the refresh  

                        mrefreshstate = refreshing; //将标量設定為,正在重新整理  

                        // 準備重新整理  

                        prepareforrefresh();    

                        // 重新整理    

                        onrefresh();    

                    } else if (mrefreshview.getbottom() < mrefreshviewheight  

                            || mrefreshview.gettop() <= 0) {  

                        // abort refresh and scroll down below the refresh view  

                        //停止重新整理,并且滾動到頭部重新整理視圖的下一個視圖  

                        resetheader();  

                        setselection(1); //定位在第二個清單項  

                    }  

                break;  

            case motionevent.action_down:  

                // 獲得按下y軸位置   

                mlastmotiony = y;    

                break;              

            case motionevent.action_move:  

                //更行頭視圖的toppadding 屬性  

                applyheaderpadding(event);  

        return super.ontouchevent(event);  

當按下的時候,記錄按下y軸的位置,然後在move中調用了applyheaderpadding()方法,我們再看下這個方法:

// 獲得header距離   

    private void applyheaderpadding(motionevent ev) {  

        //擷取累積的動作數  

        int pointercount = ev.gethistorysize();  

        for (int p = 0; p < pointercount; p++) {  

            //如果是釋放将要重新整理狀态  

            if (mrefreshstate == release_to_refresh) {     

                if (isverticalfadingedgeenabled()) {     

                    setverticalscrollbarenabled(false);  

                //曆史累積的高度  

                int historicaly = (int) ev.gethistoricaly(p);  

                // calculate the padding to apply, we divide by 1.7 to  

                // simulate a more resistant effect during pull.  

                // 計算申請的邊距,除以1.7使得拉動效果更好  

                int toppadding = (int) (((historicaly - mlastmotiony)- mrefreshviewheight) / 1.7);  

                mrefreshview.setpadding(  

                        mrefreshview.getpaddingleft(),  

                        toppadding,  

                        mrefreshview.getpaddingright(),  

                        mrefreshview.getpaddingbottom());  

通過記錄滑動距離,實時變化頭部mrefreshview的上下左右的距離。

最後,看下手指松開的action_up:

case motionevent.action_up:  

                        mrefreshstate = refreshing; //将标量設定為:正在重新整理  

當滑動距離大于一個item的距離時,添加一個item,否則,彈回。

看完ontouchevent(),然後再看一下onscroll()方法:

    public void onscroll(abslistview view, int firstvisibleitem,int visibleitemcount, int totalitemcount) {  

        // when the refresh view is completely visible, change the text to say  

        // "release to refresh..." and flip the arrow drawable.  

        // 在refreshview完全可見時,設定文字為松開重新整理,同時翻轉箭頭   

        //如果是接觸滾動狀态,并且不是正在重新整理的狀态  

        if (mcurrentscrollstate == scroll_state_touch_scroll&& mrefreshstate != refreshing) {  

            if (firstvisibleitem == 0) {    

                //如果顯示出來了第一個清單項,顯示重新整理圖檔  

                mrefreshviewimage.setvisibility(view.visible);  

                //如果下拉了listiview,則顯示上拉重新整理動畫  

                if ((mrefreshview.getbottom() >= mrefreshviewheight + 20|| mrefreshview.gettop() >= 0)  

                        && mrefreshstate != release_to_refresh) {   

                    mrefreshviewtext.settext(r.string.pull_to_refresh_release_label);  

                    mrefreshviewimage.clearanimation();  

                    mrefreshviewimage.startanimation(mflipanimation);  

                    mrefreshstate = release_to_refresh;     

                  //如果下拉距離不夠,則回歸原來的狀态  

                } else if (mrefreshview.getbottom() < mrefreshviewheight + 20  

                        && mrefreshstate != pull_to_refresh) {      

                    mrefreshviewtext.settext(r.string.pull_to_refresh_pull_label);  

                    if (mrefreshstate != tap_to_refresh) {  

                        mrefreshviewimage.clearanimation();  

                        mrefreshviewimage.startanimation(mreverseflipanimation);  

                    mrefreshstate = pull_to_refresh;  

            } else {     

                mrefreshviewimage.setvisibility(view.gone);    

                resetheader();     

          //如果是滾動狀态+ 第一個視圖已經顯示+ 不是重新整理狀态  

        } else if (mcurrentscrollstate == scroll_state_fling  && firstvisibleitem == 0  

                && mrefreshstate != refreshing) {  

            setselection(1);  

            mbouncehack = true;     

        } else if (mbouncehack && mcurrentscrollstate == scroll_state_fling) {  

            setselection(1);         

        if (monscrolllistener != null) {  

            monscrolllistener.onscroll(view, firstvisibleitem,visibleitemcount, totalitemcount);  

該方法是在滑動過程中,各種狀況的處理。

       onscroll()方法和ontouchevent()方法的執行過程應該是,先ontouchevent()的action_down,然後是action_move和onscroll()方法同時進行,最後是ontouchevent()的action_up。也可以自己打log看一下。這樣在ontouchevent()處理header,就是mrefreshview的外部的各個熟悉,onscroll()裡面處理header(mrefreshview)裡面内部的控件變化,從邏輯上來說比較清晰。

       在onscroll()中,引用方法resetheader()方法:

     * resets the header to the original state. 

     * 重置header為之前的狀态  

    private void resetheader() {  

        if (mrefreshstate != tap_to_refresh) {  

            mrefreshstate = tap_to_refresh;   

            resetheaderpadding();  

            // 将重新整理圖示換成箭頭   

            // set refresh view text to the pull label  

            mrefreshviewtext.settext(r.string.pull_to_refresh_tap_label);  

            // replace refresh drawable with arrow drawable  

            // 清除動畫   

            mrefreshviewimage.setimageresource(refreshicon);  

            // clear the full rotation animation  

            mrefreshviewimage.clearanimation();  

            // hide progress bar and arrow.  

            // 隐藏圖示和進度條   

            mrefreshviewimage.setvisibility(view.gone);  

            mrefreshviewprogress.setvisibility(view.gone);  

resethead就是header(mrefreshview)的内部的具體操作。

       當一切都完成以後,就可以調用onrefreshcomplete()方法:

     * resets the list to a normal state after a refresh. 

     * 重置listview為普通的listview 

     * @param lastupdated  

     * last updated at.  

     */    

    public void onrefreshcomplete(charsequence lastupdated) {  

        setlastupdated(lastupdated);  

        onrefreshcomplete();   

    /**  

     * 重置listview為普通的listview, 

     */  

    public void onrefreshcomplete() {          

        resetheader();  

        // if refresh view is visible when loading completes, scroll down to  

        // the next item.  

        if (mrefreshview.getbottom() > 0) {  

            invalidateviews();  //重繪視圖  

重新繪制listivew,然後setselection(1)。完成!

繼續閱讀