2025-05-24

 

該效果是一名國外工程師(johannilsson)的代碼,拿來研究瞭下,自己整合瞭一下,現在拿出來,跟大傢一起分享。

 

再次感謝這位國外工程師(johannilsson),謝謝!

 

新浪微博,和QQ空間裡面,都有那個下拉刷新的效果,另很多人眼前一亮,細細分析,原理原來如此。

 

在原作者的基礎上,寫瞭一些註釋,和幫助大傢更好的閱讀理解,(可能其中有些地方註釋不準,歡迎指正,謝謝)

 

下面,就亮出關鍵代碼:

 

 

 

 ****  自定義listivew   (關鍵代碼)

 

 

 

 

public class PullToRefreshListView extends ListView implements OnScrollListener {

 

    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 static final String TAG = "PullToRefreshListView";

    //刷新接口

    private OnRefreshListener mOnRefreshListener;

 

    //箭頭圖片

    private static  int REFRESHICON = R.drawable.goicon;

  

    /**

     * listview 滾動監聽器

     */

    private OnScrollListener mOnScrollListener;

   

    //視圖索引器

    private LayoutInflater mInflater;

    /**

     * 頭部視圖  內容  — start

     */

    private RelativeLayout mRefreshView;

    private TextView mRefreshViewText;

    private ImageView mRefreshViewImage;

    private ProgressBar mRefreshViewProgress;

    private TextView mRefreshViewLastUpdated;

    /**

     * 頭部視圖  內容  — end

     */

    //當前listivew 的滾動狀態

    private int mCurrentScrollState;

   

    //當前listview 的刷新狀態

    private int mRefreshState;

 

    //動畫效果

    //變為向下的箭頭

    private RotateAnimation mFlipAnimation;

    //變為逆向的箭頭

    private RotateAnimation mReverseFlipAnimation;

    //頭視圖的高度

    private int mRefreshViewHeight;

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

    private int mRefreshOriginalTopPadding;

    //

    private int mLastMotionY;

    //是否反彈

    private boolean mBounceHack;

 

    public PullToRefreshListView(Context context) {

        super(context);

        init(context);

    }

 

    public PullToRefreshListView(Context context, AttributeSet attrs) {

        super(context, attrs);

        init(context);

    }

 

    public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {

        super(context, attrs, defStyle);

        init(context);

    }

 

    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);//(R.layout.pull_to_refresh_header, null);

    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;

 

        addHeaderView(mRefreshView);

 

        super.setOnScrollListener(this);

 

        measureView(mRefreshView);

        mRefreshViewHeight = mRefreshView.getMeasuredHeight();  //獲取頭文件的測量高度

    }

 

    @Override

    protected void onAttachedToWindow() {

        setSelection(1);

    }

 

    @Override

    public void setAdapter(ListAdapter adapter) {

        super.setAdapter(adapter);

 

        setSelection(1);

    }

 

    /**

     * Set the listener that will receive notifications every time the list

     * scrolls.

     *

     * @param l The scroll listener.

     */

    @Override

    public void setOnScrollListener(AbsListView.OnScrollListener l) {

        mOnScrollListener = l;

    }

 

    /**

     * Register a callback to be invoked when this list should be refreshed.

     *

     * @param onRefreshListener The callback to run.

     */

    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {

        mOnRefreshListener = onRefreshListener;

    }

 

    /**

     * Set a text to represent when the list was last updated.

     * @param lastUpdated Last updated at.

     */

    public void setLastUpdated(CharSequence lastUpdated) {

        if (lastUpdated != null) {

            mRefreshViewLastUpdated.setVisibility(View.VISIBLE);

            mRefreshViewLastUpdated.setText(lastUpdated);

        } else {

            mRefreshViewLastUpdated.setVisibility(View.GONE);

        }

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent event) {

     //當前手指的Y值

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

       

        //Log.i(TAG, "觸屏的Y值"+y);

        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:

                mLastMotionY = y;  //跟蹤手指的Y值

                break;

           

            case MotionEvent.ACTION_MOVE:

             //更行頭視圖的toppadding 屬性

                applyHeaderPadding(event);

                break;

        }

        return super.onTouchEvent(event);

    }

 

    /****

     * 不斷的頭部的top padding 屬性

     * @param ev

     */

    private void applyHeaderPadding(MotionEvent ev) {

        //獲取累積的動作數

        int pointerCount = ev.getHistorySize();

       // Log.i(TAG, "獲取累積的動作數"+pointerCount);

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

            if (mRefreshState == RELEASE_TO_REFRESH) {    //如果是釋放將要刷新狀態

                if (isVerticalFadingEdgeEnabled()) {  

                    setVerticalScrollBarEnabled(false);

                }

                //歷史累積的高度

                int historicalY = (int) ev.getHistoricalY(p);

                //Log.i(TAG, "單個動作getHistoricalY值:"+historicalY);

                // Calculate the padding to apply, we pide by 1.7 to

                // simulate a more resistant effect during pull.

                int topPadding = (int) (((historicalY – mLastMotionY)

                        – mRefreshViewHeight) / 1.7);

               

                mRefreshView.setPadding(

                        mRefreshView.getPaddingLeft(),

                        topPadding,

                        mRefreshView.getPaddingRight(),

                        mRefreshView.getPaddingBottom());

            }

        }

    }

 

    /**

     * Sets the header padding back to original size.

     * 使頭部視圖的toppadding 恢復到初始值

     */

    private void resetHeaderPadding() {

        mRefreshView.setPadding(

                mRefreshView.getPaddingLeft(),

                mRefreshOriginalTopPadding,

                mRefreshView.getPaddingRight(),

                mRefreshView.getPaddingBottom());

    }

 

    /**

     * Resets the header to the original state. 

     *  初始化頭部視圖 狀態

     */

    private void resetHeader() {

        if (mRefreshState != TAP_TO_REFRESH) {

            mRefreshState = TAP_TO_REFRESH; //初始刷新狀態

            //使頭部視圖的toppadding 恢復到初始值

            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);

        }

    }

 

   

    //測量視圖的高度

    private void measureView(View child) {

     //獲取頭部視圖屬性

        ViewGroup.LayoutParams p = child.getLayoutParams();

        if (p == null) {

            p = new ViewGroup.LayoutParams(

                    ViewGroup.LayoutParams.FILL_PARENT,

                    ViewGroup.LayoutParams.WRAP_CONTENT);

        }

       

        int childWidthSpec = ViewGroup.getChildMeasureSpec(0,

                0 + 0, p.width);

        int lpHeight = p.height;

        int childHeightSpec;

       

        //不懂MeasureSpec——————————————————————————————

        if (lpHeight > 0) {  //如果視圖的高度大於0

            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); 

        } else {

            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        }

        child.measure(childWidthSpec, childHeightSpec);

        //不懂MeasureSpec——————————————————————————————

    }

 

    /****

     * 滑動事件

     */

    @Override

    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.

        if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL   //如果是接觸滾動狀態,並且不是正在刷新的狀態

                && mRefreshState != REFRESHING) {

            if (firstVisibleItem == 0) {    //如果顯示出來瞭第一個列表項

             //顯示刷新圖片

                mRefreshViewImage.setVisibility(View.VISIBLE);

                if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20

                        || mRefreshView.getTop() >= 0)

                        && mRefreshState != RELEASE_TO_REFRESH) {  //如果下拉瞭listiview,則顯示上拉刷新動畫

                    mRefreshViewText.setText(R.string.pull_to_refresh_release_label);

                    mRefreshViewImage.clearAnimation();

                    mRefreshViewImage.startAnimation(mFlipAnimation);

                    mRefreshState = RELEASE_TO_REFRESH;

                   

                    Log.i(TAG, "現在處於下拉狀態");

                   

                } 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);

                       

                        Log.i(TAG, "現在處於回彈狀態");

                       

                    }

                    mRefreshState = PULL_TO_REFRESH;

                }

            } else {  

                mRefreshViewImage.setVisibility(View.GONE);  //隱藏刷新圖片

                resetHeader();   //初始化,頭部

            }

        } else if (mCurrentScrollState == SCROLL_STATE_FLING  //如果是自己滾動狀態+ 第一個視圖已經顯示+ 不是刷新狀態

                && firstVisibleItem == 0

                && mRefreshState != REFRESHING) {

            setSelection(1);

            mBounceHack = true;   //狀態為回彈

            Log.i(TAG, "現在處於自由滾動到頂部的狀態");

        } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {

            setSelection(1);

            Log.i(TAG, "現在處於自由滾動到頂部的狀態");

        }

 

        if (mOnScrollListener != null) {

            mOnScrollListener.onScroll(view, firstVisibleItem,

                    visibleItemCount, totalItemCount);

        }

    }

 

   

    //滾動狀態改變

    @Override

    public void onScrollStateChanged(AbsListView view, int scrollState) {

        mCurrentScrollState = scrollState;

 

        if (mCurrentScrollState == SCROLL_STATE_IDLE) {   //如果滾動停頓

            mBounceHack = false;

        }

 

        if (mOnScrollListener != null) {

            mOnScrollListener.onScrollStateChanged(view, scrollState);

        }

    }

 

   

   

    //準備刷新

    public void prepareForRefresh() {

        resetHeaderPadding();   //初始化,頭部文件

 

        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() {

        Log.d(TAG, "執行刷新");

 

        if (mOnRefreshListener != null) {

            mOnRefreshListener.onRefresh();

        }

    }

 

    /**

     * 刷新完成 的回調函數

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

     * @param lastUpdated Last updated at.

     */

    public void onRefreshComplete(CharSequence lastUpdated) {

        setLastUpdated(lastUpdated);

        onRefreshComplete();

    }

 

    /**

     *  刷新完成回調函數

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

     */

    public void onRefreshComplete() {       

        Log.d(TAG, "onRefreshComplete");

 

        resetHeader();

 

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

        // the next item.

        if (mRefreshView.getBottom() > 0) {

            invalidateViews();  //重繪視圖

            setSelection(1);

        }

    }

 

    /**

     * Invoked when the refresh view is clicked on. This is mainly used when

     * there's only a few items in the list and it's not possible to drag the

     * list.

     */

    private class OnClickRefreshListener implements OnClickListener {

 

        @Override

        public void onClick(View v) {

            if (mRefreshState != REFRESHING) {

                //準備刷新

                prepareForRefresh(); 

                //刷新  

                onRefresh();

            }

        }

 

    }

 

    /**

     * 刷新方法接口

     */

    public interface OnRefreshListener {

       

        public void onRefresh();

    }

  


   

   

 

* 如果你還是沒有弄明白的話,那就點擊下面的鏈接,來下載整個demo項目:

 

 

 http://download.csdn.net/detail/zjl5211314/3775209

 

原作者:johannilsson

 

選自:https://github.com/johannilsson/android-pulltorefresh

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *