開源項目之Android 向下刷新列表

Android PullToRefresh為 Android 應用提供一個向下滑動即刷新列表的功能。

  項目如圖:

 

效果如圖:

 

包含測試文件就2個目標文件!

主要類如下:

[java]
 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 OnScrollListener mOnScrollListener; 
    private LayoutInflater mInflater; 
 
    private RelativeLayout mRefreshView; 
    private TextView mRefreshViewText; 
    private ImageView mRefreshViewImage; 
    private ProgressBar mRefreshViewProgress; 
    private TextView mRefreshViewLastUpdated; 
 
    private int mCurrentScrollState; 
    private int mRefreshState; 
 
    private RotateAnimation mFlipAnimation; 
    private RotateAnimation mReverseFlipAnimation; 
 
    private int mRefreshViewHeight; 
    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) 
    { 
        //我們需要在代碼中加載所有的動畫,而不是通過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; 
 
        addHeaderView(mRefreshView); 
 
        super.setOnScrollListener(this); 
 
        measureView(mRefreshView); 
        mRefreshViewHeight = mRefreshView.getMeasuredHeight(); 
    } 
 
    @Override 
    protected void onAttachedToWindow() 
    { 
        super.onAttachedToWindow(); 
        setSelection(1); 
    } 
 
    @Override 
    public void setAdapter(ListAdapter adapter) 
    { 
        super.setAdapter(adapter); 
 
        setSelection(1); 
    } 
 
    /**
     * 設置將接收通知的偵聽器,每次滾動列表。
     * @param l
     *            The scroll listener.
     */ 
    @Override 
    public void setOnScrollListener(AbsListView.OnScrollListener l) 
    { 
        mOnScrollListener = l; 
    } 
 
    /**
     * 註冊一個回調函數被調用時,此列表應被刷新。
     * @param onRefreshListener
     *            The callback to run.
     */ 
    public void setOnRefreshListener(OnRefreshListener onRefreshListener) 
    { 
        mOnRefreshListener = onRefreshListener; 
    } 
 
    /**
     * 設置文本表示當列表的最後更新時間。
     * @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) 
    { 
        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: 
            mLastMotionY = y; 
            break; 
        case MotionEvent.ACTION_MOVE: 
            applyHeaderPadding(event); 
            break; 
        } 
        return super.onTouchEvent(event); 
    } 
 
    private void applyHeaderPadding(MotionEvent ev) 
    { 
        // getHistorySize has been available since API 1 
        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 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()); 
            } 
        } 
    } 
 
    /**
     *設置的頭部填充返回到原來的大小。
     */ 
    private void resetHeaderPadding() 
    { 
        mRefreshView.setPadding(mRefreshView.getPaddingLeft(), 
                mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(), 
                mRefreshView.getPaddingBottom()); 
    } 
 
    /**
     * 復位到原來的狀態的標頭。
     */ 
    private void resetHeader() 
    { 
        if (mRefreshState != TAP_TO_REFRESH) 
        { 
            mRefreshState = TAP_TO_REFRESH; 
 
            resetHeaderPadding(); 
 
            // 將刷新視圖文本拉標簽 
            mRefreshViewText.setText(R.string.pull_to_refresh_tap_label); 
            // Replace refresh drawable with arrow drawable 
            mRefreshViewImage 
                    .setImageResource(R.drawable.ic_pulltorefresh_arrow); 
            //清除旋轉動畫 
            mRefreshViewImage.clearAnimation(); 
            // 隱藏進度條和箭頭。 
            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; 
        if (lpHeight > 0) 
        { 
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, 
                    MeasureSpec.EXACTLY); 
        } else 
        { 
            childHeightSpec = MeasureSpec.makeMeasureSpec(0, 
                    MeasureSpec.UNSPECIFIED); 
        } 
        child.measure(childWidthSpec, childHeightSpec); 
    } 
 
    @Override 
    public void onScroll(AbsListView view, int firstVisibleItem, 
            int visibleItemCount, int totalItemCount) 
    { 
        // 當刷新的觀點是完全可見的,改變的文字說:“放開刷新…”翻轉的箭頭繪制的。 
        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) 
                { 
                    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); 
        } 
    } 
 
    @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); 
        // 我們需要這個技巧,否則它會保持以前的drawable。 
        mRefreshViewImage.setImageDrawable(null); 
        mRefreshViewProgress.setVisibility(View.VISIBLE); 
 
        // 將刷新視圖文本清爽的標簽 
        mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label); 
 
        mRefreshState = REFRESHING; 
    } 
 
    public void onRefresh() 
    { 
        Log.d(TAG, "onRefresh"); 
 
        if (mOnRefreshListener != null) 
        { 
            mOnRefreshListener.onRefresh(); 
        } 
    } 
 
    /**
     * 重置列表刷新後到正常狀態。
     * 
     * @param lastUpdated
     *            Last updated at.
     */ 
    public void onRefreshComplete(CharSequence lastUpdated) 
    { 
        setLastUpdated(lastUpdated); 
        onRefreshComplete(); 
    } 
 
    /**
     * 重置列表刷新後到正常狀態。
     */ 
    public void onRefreshComplete() 
    { 
        Log.d(TAG, "onRefreshComplete"); 
 
        resetHeader(); 
 
        // 如果刷新視圖是可見的,當加載完成,向下滾動到下一個項目。 
        if (mRefreshView.getBottom() > 0) 
        { 
            invalidateViews(); 
            setSelection(1); 
        } 
    } 
 
    /**
     * 點擊刷新視圖時調用。這主要是用於當有幾個項目在列表中,這是不可能的拖動列表。
     */ 
    private class OnClickRefreshListener implements OnClickListener 
    { 
 
        @Override 
        public void onClick(View v) 
        { 
            if (mRefreshState != REFRESHING) 
            { 
                prepareForRefresh(); 
                onRefresh(); 
            } 
        } 
 
    } 
 
    /**
     *當列表被刷新的回調被調用的接口定義。
     */ 
    public interface OnRefreshListener 
    { 
        /**
         * 列表被刷新時調用的
         * <p>
         * A call to {@link PullToRefreshListView #onRefreshComplete()} is
         * expected to indicate that the refresh has completed.
         */ 
        public void onRefresh(); 
    } 

測試代碼如下:

[java]
public class PullToRefreshActivity extends ListActivity 

    private LinkedList<String> mListItems; 
 
    /** Called when the activity is first created. */ 
    @Override 
    public void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.pull_to_refresh); 
 
        // 設置一個監聽器時要調用的列表被刷新。 
        ((PullToRefreshListView) getListView()) 
                .setOnRefreshListener(new OnRefreshListener() 
                { 
                    @Override 
                    public void onRefresh() 
                    { 
                        // 請刷新列表。 
                        new GetDataTask().execute(); 
                    } 
                }); 
 
        mListItems = new LinkedList<String>(); 
        mListItems.addAll(Arrays.asList(mStrings)); 
 
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
                android.R.layout.simple_list_item_1, mListItems); 
 
        setListAdapter(adapter); 
    } 
 
    private class GetDataTask extends AsyncTask<Void, Void, String[]> 
    { 
 
        @Override 
        protected String[] doInBackground(Void… params) 
        { 
            //後臺作業。 
            try 
            { 
                Thread.sleep(2000); 
            } catch (InterruptedException e) 
            { 
                ; 
            } 
            return mStrings; 
        } 
 
        @Override 
        protected void onPostExecute(String[] result) 
        { 
            mListItems.addFirst("Added after refresh…"); 
 
            // 刷新完成 
            ((PullToRefreshListView) getListView()).onRefreshComplete(); 
 
            super.onPostExecute(result); 
        } 
    } 
 
    private String[] mStrings = 
    { "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", 
            "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis", 
            "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", 
            "Allgauer Emmentaler" }; 

發佈留言

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