Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android UI--自定義ListView(實現下拉刷新+加載更多)

Android UI--自定義ListView(實現下拉刷新+加載更多)

編輯:關於Android編程

Android UI--自定義ListView(實現下拉刷新+加載更多)  

關於實現ListView下拉刷新和加載更多的實現,我想網上一搜就一堆。不過我就沒發現比較實用的,要不就是實現起來太復雜,要不就是不健全的。因為小巫近期要開發新浪微博客戶端,需要實現ListView的下拉刷新,所以就想把這個UI整合到項目當中去,這裡只是一個demo,可以根據項目的需要進行修改。

自定義ListView:

package com.markupartist.android.widget;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.markupartist.android.example.pulltorefresh.R;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;



/**
 * 2013/8/13
 * 自定義ListView,實現OnScrollListener接口
 * 此ListView是為實現"下拉刷新"和"上拉加載更多"而定制的,具體效果可參考新浪微博、騰訊微博
 * @author wwj
 *
 */
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 int TAP_TO_LOADMORE = 5;			// 未加載更多
    private static final int LOADING = 6;					// 正在加載
    

    private static final String TAG = "PullToRefreshListView";

    private OnRefreshListener mOnRefreshListener;			// 刷新監聽器

    /**
     * Listener that will receive notifications every time the list scrolls.
     */
    private OnScrollListener mOnScrollListener;				// 列表滾動監聽器
    private LayoutInflater mInflater;		    			// 用於加載布局文件

    private RelativeLayout mRefreshHeaderView;				// 刷新視圖(也就是頭部那部分)	
    private TextView mRefreshViewText;						// 刷新提示文本		
    private ImageView mRefreshViewImage;					// 刷新向上向下的那個圖片
    private ProgressBar mRefreshViewProgress;				// 這裡是圓形進度條
    private TextView mRefreshViewLastUpdated;				// 最近更新的文本
    
    private RelativeLayout mLoadMoreFooterView;				// 加載更多
    private TextView mLoadMoreText;							// 提示文本
    private ProgressBar mLoadMoreProgress;					// 加載更多進度條
    

    private int mCurrentScrollState;						// 當前滾動位置			
    private int mRefreshState;								// 刷新狀態	
    private int mLoadState;									// 加載狀態

    private RotateAnimation mFlipAnimation;					// 下拉動畫
    private RotateAnimation mReverseFlipAnimation;			// 恢復動畫

    private int mRefreshViewHeight;							// 刷新視圖高度					
    private int mRefreshOriginalTopPadding;					// 原始上部間隙
    private int mLastMotionY;								// 記錄點擊位置
    
  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
    	/** 定義旋轉動畫**/
    	// 參數:1.旋轉開始的角度 2.旋轉結束的角度 3. X軸伸縮模式 4.X坐標的伸縮值 5.Y軸的伸縮模式 6.Y坐標的伸縮值
        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);

        // 獲取LayoutInflater實例對象
        mInflater = (LayoutInflater) context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);

        // 加載下拉刷新的頭部視圖
    mRefreshHeaderView = (RelativeLayout) mInflater.inflate(
        R.layout.pull_to_refresh_header, this, false);
        mRefreshViewText =
            (TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_text);
        mRefreshViewImage =
            (ImageView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_image);
        mRefreshViewProgress =
            (ProgressBar) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_progress);
        mRefreshViewLastUpdated =
            (TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_updated_at);
    mLoadMoreFooterView = (RelativeLayout) mInflater.inflate(
        R.layout.loadmore_footer, this, false);
    mLoadMoreText = (TextView) mLoadMoreFooterView.findViewById(R.id.loadmore_text);
    mLoadMoreProgress = (ProgressBar) mLoadMoreFooterView.findViewById(R.id.loadmore_progress);
    

        mRefreshViewImage.setMinimumHeight(50);		// 設置圖片最小高度
        mRefreshHeaderView.setOnClickListener(new OnClickRefreshListener());
        mRefreshOriginalTopPadding = mRefreshHeaderView.getPaddingTop();
        mLoadMoreFooterView.setOnClickListener(new OnClickLoadMoreListener());

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

        addHeaderView(mRefreshHeaderView);			// 增加頭部視圖
        addFooterView(mLoadMoreFooterView);			// 增加尾部視圖

        super.setOnScrollListener(this);		

        measureView(mRefreshHeaderView);				// 測量視圖
        mRefreshViewHeight = mRefreshHeaderView.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) {
        final int y = (int) event.getY();	 // 獲取點擊位置的Y坐標

        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:		// 手指抬起
                if (!isVerticalScrollBarEnabled()) {
                    setVerticalScrollBarEnabled(true);
                }
                if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
                    if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight
                            || mRefreshHeaderView.getTop() >= 0)
                            && mRefreshState == RELEASE_TO_REFRESH) {
                        // Initiate the refresh
                        mRefreshState = REFRESHING;		// 刷新狀態
                        prepareForRefresh();
                        onRefresh();
                    } else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight
                            || mRefreshHeaderView.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) {
        final int historySize = ev.getHistorySize();

        // Workaround for getPointerCount() which is unavailable in 1.5
        // (it's always 1 in 1.5)
        int pointerCount = 1;
        try {
            Method method = MotionEvent.class.getMethod("getPointerCount");
            pointerCount = (Integer)method.invoke(ev);
        } catch (NoSuchMethodException e) {
            pointerCount = 1;
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (IllegalAccessException e) {
            System.err.println("unexpected " + e);
        } catch (InvocationTargetException e) {
            System.err.println("unexpected " + e);
        }

        for (int h = 0; h < historySize; h++) {
            for (int p = 0; p < pointerCount; p++) {
                if (mRefreshState == RELEASE_TO_REFRESH) {
                    if (isVerticalFadingEdgeEnabled()) {
                        setVerticalScrollBarEnabled(false);
                    }

                    int historicalY = 0;
                    try {
                        // For Android > 2.0
                        Method method = MotionEvent.class.getMethod(
                                "getHistoricalY", Integer.TYPE, Integer.TYPE);
                        historicalY = ((Float) method.invoke(ev, p, h)).intValue();
                    } catch (NoSuchMethodException e) {
                        // For Android < 2.0
                        historicalY = (int) (ev.getHistoricalY(h));
                    } catch (IllegalArgumentException e) {
                        throw e;
                    } catch (IllegalAccessException e) {
                        System.err.println("unexpected " + e);
                    } catch (InvocationTargetException e) {
                        System.err.println("unexpected " + e);
                    }

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

                    // 設置上、下、左、右四個位置的間隙間隙
                    mRefreshHeaderView.setPadding(
                            mRefreshHeaderView.getPaddingLeft(),
                            topPadding,
                            mRefreshHeaderView.getPaddingRight(),
                            mRefreshHeaderView.getPaddingBottom());
                }
            }
        }
    }

    /**
     * Sets the header padding back to original size.
     * 設置頭部填充會原始大小
     */
    private void resetHeaderPadding() {
        mRefreshHeaderView.setPadding(
                mRefreshHeaderView.getPaddingLeft(),
                mRefreshOriginalTopPadding,
                mRefreshHeaderView.getPaddingRight(),
                mRefreshHeaderView.getPaddingBottom());
    }

    /**
     * Resets the header to the original state.
     * 重新設置頭部為原始狀態
     */
    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(R.drawable.ic_pulltorefresh_arrow);
            // Clear the full rotation animation
            mRefreshViewImage.clearAnimation();
            // Hide progress bar and arrow.
            mRefreshViewImage.setVisibility(View.GONE);
            mRefreshViewProgress.setVisibility(View.GONE);
        }
    }
    
    /**
     * 重設ListView尾部視圖為初始狀態
     */
    private void resetFooter() {
    	if(mLoadState != TAP_TO_LOADMORE) {
    		mLoadState = TAP_TO_LOADMORE;
    		
    		// 進度條設置為不可見
    		mLoadMoreProgress.setVisibility(View.GONE);
    		// 按鈕的文本替換為“加載更多”
    		mLoadMoreText.setText(R.string.loadmore_label);
    	}
    	
    }
    

    /**
     * 測量視圖的大小
     * @param child
     */
    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_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) {
        // 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) {		// 如果第一個可見條目為0
                mRefreshViewImage.setVisibility(View.VISIBLE);	// 讓指示箭頭變得可見
                /**如果頭部視圖相對與父容器的位置大於其自身高度+20或者頭部視圖的頂部位置>0,並且要在刷新狀態不等於"釋放以刷新"**/
                if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight + 20
                        || mRefreshHeaderView.getTop() >= 0)
                        && mRefreshState != RELEASE_TO_REFRESH) {
                    mRefreshViewText.setText(R.string.pull_to_refresh_release_label);// 設置刷新文本為"Release to refresh..."
                    mRefreshViewImage.clearAnimation();					// 清除動畫	
                    mRefreshViewImage.startAnimation(mFlipAnimation);	// 啟動動畫
                    mRefreshState = RELEASE_TO_REFRESH;					// 更改刷新狀態為“釋放以刷新"
                } else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight + 20
                        && mRefreshState != PULL_TO_REFRESH) {
                    mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);// 設置刷新文本為"Pull to refresh..."
                    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);
        }

        if (mOnScrollListener != null) {
            mOnScrollListener.onScroll(view, firstVisibleItem,
                    visibleItemCount, totalItemCount);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;

        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 prepareForLoadMore() {
    	mLoadMoreProgress.setVisibility(View.VISIBLE);	 
    	mLoadMoreText.setText(R.string.loading_label);
    	mLoadState = LOADING;
    }

    public void onRefresh() {
        Log.d(TAG, "onRefresh");

        if (mOnRefreshListener != null) {
            mOnRefreshListener.onRefresh();
        }
    }
    
    public void OnLoadMore() {
    	Log.d(TAG, "onLoadMore");
    	if(mOnRefreshListener != null) {
    		mOnRefreshListener.onLoadMore();
    	}
    }

    /**
     * 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 (mRefreshHeaderView.getBottom() > 0) {
            invalidateViews();
            setSelection(1);
        }
    }
    
    public void onLoadMoreComplete() {
    	Log.d(TAG, "onLoadMoreComplete");
    	resetFooter();
    }

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

    }
    
    /**
     * 
     * @author wwj
     * 加載更多
     */
    private class OnClickLoadMoreListener implements OnClickListener {

    @Override
    public void onClick(View v) {
      if(mLoadState != LOADING) {
        prepareForLoadMore();
        OnLoadMore();
      }
    }
    }

    /**
     * Interface definition for a callback to be invoked when list should be
     * refreshed.
     * 接口定義一個回調方法當列表應當被刷新
     */
    public interface OnRefreshListener {
        /**
         * Called when the list should be refreshed.
         * 當列表應當被刷新是調用這個方法
         * 

* A call to {@link PullToRefreshListView #onRefreshComplete()} is * expected to indicate that the refresh has completed. */ public void onRefresh(); public void onLoadMore(); } }


使用方法:
package com.markupartist.android.example.pulltorefresh;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;

import com.markupartist.android.widget.PullToRefreshListView;
import com.markupartist.android.widget.PullToRefreshListView.OnRefreshListener;

public class PullToRefreshActivity extends Activity {
  private LinkedList mListItems;
  public static PullToRefreshListView weiboListView;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.pull_to_refresh);
    weiboListView = (PullToRefreshListView) findViewById(R.id.weibolist);

    // Set a listener to be invoked when the list should be refreshed.
    weiboListView.setOnRefreshListener(new OnRefreshListener() {
      @Override
      public void onRefresh() {
        // Do work to refresh the list here.
        new GetDataTask(PullToRefreshActivity.this, 0).execute();
      }

      @Override
      public void onLoadMore() {
        new GetDataTask(PullToRefreshActivity.this, 1).execute();
      }
    });

    mListItems = new LinkedList();
    mListItems.addAll(Arrays.asList(mStrings));

    ArrayAdapter adapter = new ArrayAdapter(this,
        android.R.layout.simple_list_item_1, mListItems);

    weiboListView.setAdapter(adapter);
  }

  private class GetDataTask extends AsyncTask {
    private Context context;
    private int index;

    public GetDataTask(Context context, int index) {
      this.context = context;
      this.index = index;
    }

    @Override
    protected String[] doInBackground(Void... params) {
      // Simulates a background job.
      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        ;
      }
      return mStrings;
    }

    @Override
    protected void onPostExecute(String[] result) {
      if (index == 0) {
        // 將字符串“Added after refresh”添加到頂部
        mListItems.addFirst("Added after refresh...");

        SimpleDateFormat format = new SimpleDateFormat(
            "yyyy年MM月dd日  HH:mm");
        String date = format.format(new Date());
        // Call onRefreshComplete when the list has been refreshed.
        weiboListView.onRefreshComplete(date);
      } else if (index == 1) {
        mListItems.addLast("Added after loadmore...");
        weiboListView.onLoadMoreComplete();
      }

      super.onPostExecute(result);
    }
  }

  public static String[] mStrings = { "一條微博", "兩條微博", "三條微博", "四條微博", "五條微博",
      "六條微博", "七條微博", "八條微博", "九條微博", "十條微博", "十一條微博", "十二條微博" };

}

下拉刷新的那個頭部布局

/2013.08.22_PullToRefresh_ListView_Demo/res/layout/pull_to_refresh_header.xml






    

    
    

    
    

    

    


加載更多的底部布局

/2013.08.22_PullToRefresh_ListView_Demo/res/layout/loadmore_footer.xml





    

   


/2013.08.22_PullToRefresh_ListView_Demo/res/layout/pull_to_refresh.xml



    

    

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved