Android 向下刷新列表

ppucd 12年前發布 | 3K 次閱讀 1.8版本發布

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

  項目如圖:

效果如圖:


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

主要類如下:

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

}</pre>
測試代碼如下:

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

}</pre>

 本文由用戶 ppucd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!