Maxwin-z/XListView-Android(下拉重新整理上拉載入)源碼解析(二)
本文主要是貼出xlistview的原始碼和一個使用執行個體,沒有過多講解
使用執行個體,MainActivity
public class MainActivity extends Activity {private LinkedList listItems = null; private XListView listView = null; private ArrayAdapter adapter; private String[] mStrings = { Aaaaaa, Bbbbbb, Cccccc, Dddddd, Eeeeee, Ffffff, Gggggg, Hhhhhh, Iiiiii, Jjjjjj, Kkkkkk, Llllll, Mmmmmm, Nnnnnn, }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (XListView)findViewById(R.id.mlist); listView.setXListViewListener(new IXListViewListener() {@Overridepublic void onRefresh() {new GetDataTask(true).execute();}@Overridepublic void onLoadMore() {new GetDataTask(false).execute();}}); listItems = new LinkedList(); listItems.addAll(Arrays.asList(mStrings)); adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, listItems); listView.setAdapter(adapter); listView.setPullLoadEnable(true); } private class GetDataTask extends AsyncTask { private boolean isDropDown; public GetDataTask(boolean isDropDown){ this.isDropDown = isDropDown; } @Override protected String[] doInBackground(Void... params) { try { Thread.sleep(1000); } catch (InterruptedException e) { ; } return mStrings; } @Override protected void onPostExecute(String[] result) { if (isDropDown) { listItems.addFirst(Added after drop down); adapter.notifyDataSetChanged(); listView.stopRefresh(); } else { listItems.add(Added after on bottom); adapter.notifyDataSetChanged(); listView.stopLoadMore(); } super.onPostExecute(result); } }}
XListView
public class XListView extends ListView implements OnScrollListener {private float mLastY = -1; // save event y/** * 用於下拉後,滑動返回 */private Scroller mScroller; // used for scroll backprivate OnScrollListener mScrollListener; // user's scroll listener// the interface to trigger refresh and load more.private IXListViewListener mListViewListener;/** * 下拉頭部 */private XListViewHeader mHeaderView;/** * 下拉頭主體,用於計算頭部的高度 * 當不能重新整理時,被隱藏 */private RelativeLayout mHeaderViewContent;/** * 下拉式箭頭 */private TextView mHeaderTimeView;/** * 下拉頭部的高度 */private int mHeaderViewHeight;/** * 能否下拉重新整理 */private boolean mEnablePullRefresh = true;/** * 是否正在重新整理,false表示正在重新整理 */private boolean mPullRefreshing = false; // is refreashing.// -- footer viewprivate XListViewFooter mFooterView;private boolean mEnablePullLoad;private boolean mPullLoading;private boolean mIsFooterReady = false;// total list items, used to detect is at the bottom of listview.private int mTotalItemCount;// for mScroller, scroll back from header or footer.private int mScrollBack;/** * 頭部滑動返回 */private final static int SCROLLBACK_HEADER = 0;/** * footer滑動返回 */private final static int SCROLLBACK_FOOTER = 1;private final static int SCROLL_DURATION = 400; // scroll back durationprivate final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px// at bottom, trigger// load more.private final static float OFFSET_RADIO = 1.8f; // support iOS like pull// feature./** * @param context */public XListView(Context context) {super(context);initWithContext(context);}public XListView(Context context, AttributeSet attrs) {super(context, attrs);initWithContext(context);}public XListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initWithContext(context);}private void initWithContext(Context context) {mScroller = new Scroller(context, new DecelerateInterpolator());// XListView need the scroll event, and it will dispatch the event to// user's listener (as a proxy).super.setOnScrollListener(this);//初始化下拉頭mHeaderView = new XListViewHeader(context);mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.xlistview_header_content);mHeaderTimeView = (TextView) mHeaderView.findViewById(R.id.xlistview_header_time);addHeaderView(mHeaderView);//初始化底部mFooterView = new XListViewFooter(context);//初始化下拉頭高度mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {mHeaderViewHeight = mHeaderViewContent.getHeight();//獲得下拉頭的高度getViewTreeObserver().removeGlobalOnLayoutListener(this);}});}@Overridepublic void setAdapter(ListAdapter adapter) {/* * 將上拉載入更多footer加入listview的底部 * 並且保證只加入一次 */if (mIsFooterReady == false) {mIsFooterReady = true;addFooterView(mFooterView);//listview原生方法}super.setAdapter(adapter);}/** * 設定下拉重新整理功能是否開啟 * @param enable */public void setPullRefreshEnable(boolean enable) {mEnablePullRefresh = enable;if (!mEnablePullRefresh) {//不開啟,隱藏頭部mHeaderViewContent.setVisibility(View.INVISIBLE);} else {mHeaderViewContent.setVisibility(View.VISIBLE);}}/** * 設定上拉載入更多功能是否開啟 * 如果要開啟,必須主動調用這個函數 * @param enable */public void setPullLoadEnable(boolean enable) {mEnablePullLoad = enable;if (!mEnablePullLoad) {//如果不開啟mFooterView.hide();//隱藏footermFooterView.setOnClickListener(null);//取消監聽//make sure pull up don't show a line in bottom when listview with one page //保證listview的item之間的分割線消失(最後一條)setFooterDividersEnabled(false);//listview原生方法} else {mPullLoading = false;mFooterView.show();mFooterView.setState(XListViewFooter.STATE_NORMAL);//make sure pull up don't show a line in bottom when listview with one page setFooterDividersEnabled(true);// both pull up and click will invoke load more.mFooterView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {startLoadMore();}});}}/** * stop refresh, reset header view. * 停止重新整理,重設頭部 */public void stopRefresh() {if (mPullRefreshing == true) {mPullRefreshing = false;resetHeaderHeight();}}/** * stop load more, reset footer view. * 停止載入更多,重設footer */public void stopLoadMore() {if (mPullLoading == true) {mPullLoading = false;mFooterView.setState(XListViewFooter.STATE_NORMAL);}}/** * set last refresh time * 設定最後一次下來重新整理時間 * @param time */public void setRefreshTime(String time) {mHeaderTimeView.setText(time);}private void invokeOnScrolling() {if (mScrollListener instanceof OnXScrollListener) {OnXScrollListener l = (OnXScrollListener) mScrollListener;l.onXScrolling(this);}}/** * 更新頭部高度 * 這個函數用於下拉時,記錄下拉了多少 * @param delta */private void updateHeaderHeight(float delta) {mHeaderView.setVisiableHeight((int) delta+ mHeaderView.getVisiableHeight());if (mEnablePullRefresh && !mPullRefreshing) {//未處於重新整理狀態,更新箭頭if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) {mHeaderView.setState(XListViewHeader.STATE_READY);} else {mHeaderView.setState(XListViewHeader.STATE_NORMAL);}}//滑動到頭部setSelection(0); // scroll to top each time}/** * reset header view's height. * 重設頭部高度 */private void resetHeaderHeight() {int height = mHeaderView.getVisiableHeight();if (height == 0) // not visible.return;// refreshing and header isn't shown fully. do nothing.//正在重新整理,或者頭部沒有完全顯示,返回if (mPullRefreshing && height <= mHeaderViewHeight) {return;}int finalHeight = 0; // 預設最終高度,也就是說要讓頭部消失// is refreshing, just scroll back to show all the header.//正在重新整理,並且下拉頭部完全顯示if (mPullRefreshing && height > mHeaderViewHeight) {finalHeight = mHeaderViewHeight;}mScrollBack = SCROLLBACK_HEADER;//從當前位置,返回到頭部被隱藏mScroller.startScroll(0, height, 0, finalHeight - height,SCROLL_DURATION);// trigger computeScrollinvalidate();}/** * 更新footer離底部的距離 * @param delta */private void updateFooterHeight(float delta) {int height = mFooterView.getBottomMargin() + (int) delta;if (mEnablePullLoad && !mPullLoading) {if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load上拉足夠高才去載入// more.mFooterView.setState(XListViewFooter.STATE_READY);} else {mFooterView.setState(XListViewFooter.STATE_NORMAL);}}mFooterView.setBottomMargin(height);//setSelection(mTotalItemCount - 1); // scroll to bottom}/** * 重設footer位置 */private void resetFooterHeight() {int bottomMargin = mFooterView.getBottomMargin();if (bottomMargin > 0) {mScrollBack = SCROLLBACK_FOOTER;mScroller.startScroll(0, bottomMargin, 0, -bottomMargin,SCROLL_DURATION);invalidate();}}private void startLoadMore() {mPullLoading = true;mFooterView.setState(XListViewFooter.STATE_LOADING);if (mListViewListener != null) {mListViewListener.onLoadMore();}}@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (mLastY == -1) {//獲得觸摸時的y座標mLastY = ev.getRawY();}switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:mLastY = ev.getRawY();break;case MotionEvent.ACTION_MOVE:final float deltaY = ev.getRawY() - mLastY;//下拉或者上拉了多少offsetmLastY = ev.getRawY();if (getFirstVisiblePosition() == 0&& (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {//第一個item可見並且頭部部分顯示或者下拉操作,則表示處於下拉重新整理狀態// the first item is showing, header has shown or pull down.updateHeaderHeight(deltaY / OFFSET_RADIO);invokeOnScrolling();} else if (getLastVisiblePosition() == mTotalItemCount - 1&& (mFooterView.getBottomMargin() > 0 || deltaY < 0)) {//最後一個item可見並且footer被上拉顯示或者上拉操作,則表示處於上拉重新整理狀態// last item, already pulled up or want to pull up.updateFooterHeight(-deltaY / OFFSET_RADIO);}break;default://action_upmLastY = -1; // resetif (getFirstVisiblePosition() == 0) {// invoke refreshif (mEnablePullRefresh&& mHeaderView.getVisiableHeight() > mHeaderViewHeight) {mPullRefreshing = true;mHeaderView.setState(XListViewHeader.STATE_REFRESHING);if (mListViewListener != null) {mListViewListener.onRefresh();}}resetHeaderHeight();} else if (getLastVisiblePosition() == mTotalItemCount - 1) {// invoke load more.if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA && !mPullLoading) {startLoadMore();}resetFooterHeight();}break;}return super.onTouchEvent(ev);}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {if (mScrollBack == SCROLLBACK_HEADER) {mHeaderView.setVisiableHeight(mScroller.getCurrY());} else {mFooterView.setBottomMargin(mScroller.getCurrY());}postInvalidate();invokeOnScrolling();}super.computeScroll();}@Overridepublic void setOnScrollListener(OnScrollListener l) {mScrollListener = l;}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (mScrollListener != null) {mScrollListener.onScrollStateChanged(view, scrollState);}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {// send to user's listenermTotalItemCount = totalItemCount;if (mScrollListener != null) {mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount,totalItemCount);}}public void setXListViewListener(IXListViewListener l) {mListViewListener = l;}/** * you can listen ListView.OnScrollListener or this one. it will invoke * onXScrolling when header/footer scroll back. */public interface OnXScrollListener extends OnScrollListener {public void onXScrolling(View view);}/** * implements this interface to get refresh/load more event. * 下拉和上拉監聽器 */public interface IXListViewListener {public void onRefresh();public void onLoadMore();}}
XListViewHeader
public class XListViewHeader extends LinearLayout {/** * 下拉布局主體 */private LinearLayout mContainer;/** * 下拉式箭頭 */private ImageView mArrowImageView;/** * 環形進度條 */private ProgressBar mProgressBar;/** * 提示文本 */private TextView mHintTextView;private int mState = STATE_NORMAL;private Animation mRotateUpAnim;private Animation mRotateDownAnim;/** * 動畫時間 */private final int ROTATE_ANIM_DURATION = 180;public final static int STATE_NORMAL = 0;//普通狀態public final static int STATE_READY = 1;//下拉準備重新整理public final static int STATE_REFRESHING = 2;//正在載入public XListViewHeader(Context context) {super(context);initView(context);}/** * @param context * @param attrs */public XListViewHeader(Context context, AttributeSet attrs) {super(context, attrs);initView(context);}private void initView(Context context) {// 初始情況,設定下拉重新整理view高度為0LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, 0);mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_header, null);addView(mContainer, lp);setGravity(Gravity.BOTTOM);mArrowImageView = (ImageView)findViewById(R.id.xlistview_header_arrow);mHintTextView = (TextView)findViewById(R.id.xlistview_header_hint_textview);mProgressBar = (ProgressBar)findViewById(R.id.xlistview_header_progressbar);//旋轉動畫mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);//設定動畫時間mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);//動畫終止時停留在最後一幀,也就是保留動畫以後的狀態mRotateUpAnim.setFillAfter(true);//旋轉動畫mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);mRotateDownAnim.setFillAfter(true);}public void setState(int state) {if (state == mState) return ;if (state == STATE_REFRESHING) {// 顯示進度mArrowImageView.clearAnimation();mArrowImageView.setVisibility(View.INVISIBLE);mProgressBar.setVisibility(View.VISIBLE);} else {// 顯示箭頭圖片mArrowImageView.setVisibility(View.VISIBLE);mProgressBar.setVisibility(View.INVISIBLE);}switch(state){case STATE_NORMAL:if (mState == STATE_READY) {mArrowImageView.startAnimation(mRotateDownAnim);}if (mState == STATE_REFRESHING) {mArrowImageView.clearAnimation();}mHintTextView.setText(R.string.xlistview_header_hint_normal);break;case STATE_READY:if (mState != STATE_READY) {mArrowImageView.clearAnimation();mArrowImageView.startAnimation(mRotateUpAnim);mHintTextView.setText(R.string.xlistview_header_hint_ready);}break;case STATE_REFRESHING:mHintTextView.setText(R.string.xlistview_header_hint_loading);break;default:}mState = state;}/** * 設定下拉頭有效高度 * @param height */public void setVisiableHeight(int height) {if (height < 0)height = 0;LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer.getLayoutParams();lp.height = height;mContainer.setLayoutParams(lp);}/** * 獲得下拉頭有效高度 * @return */public int getVisiableHeight() {return mContainer.getLayoutParams().height;}}
XListViewFooter
public class XListViewFooter extends LinearLayout {public final static int STATE_NORMAL = 0;//普通狀態public final static int STATE_READY = 1;//上拉準備重新整理public final static int STATE_LOADING = 2;//正在載入private Context mContext;private View mContentView;private View mProgressBar;private TextView mHintView;public XListViewFooter(Context context) {super(context);initView(context);}public XListViewFooter(Context context, AttributeSet attrs) {super(context, attrs);initView(context);}/** * 設定狀態 * @param state */public void setState(int state) {mHintView.setVisibility(View.INVISIBLE);mProgressBar.setVisibility(View.INVISIBLE);mHintView.setVisibility(View.INVISIBLE);if (state == STATE_READY) {mHintView.setVisibility(View.VISIBLE);mHintView.setText(R.string.xlistview_footer_hint_ready);} else if (state == STATE_LOADING) {mProgressBar.setVisibility(View.VISIBLE);} else {mHintView.setVisibility(View.VISIBLE);mHintView.setText(R.string.xlistview_footer_hint_normal);}}/** * 設定footer裡底部的高度 * @param height */public void setBottomMargin(int height) {if (height < 0) return ;LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();lp.bottomMargin = height;mContentView.setLayoutParams(lp);}/** * 獲得footer裡底部的高度 * @return */public int getBottomMargin() {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();return lp.bottomMargin;}/** * normal status */public void normal() {mHintView.setVisibility(View.VISIBLE);mProgressBar.setVisibility(View.GONE);}/** * loading status */public void loading() {mHintView.setVisibility(View.GONE);mProgressBar.setVisibility(View.VISIBLE);}/** * hide footer when disable pull load more */public void hide() {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();lp.height = 0;mContentView.setLayoutParams(lp);}/** * show footer */public void show() {LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams();lp.height = LayoutParams.WRAP_CONTENT;mContentView.setLayoutParams(lp);}/** * 初始化布局 * @param context */private void initView(Context context) {mContext = context;LinearLayout moreView = (LinearLayout)LayoutInflater.from(mContext).inflate(R.layout.xlistview_footer, null);addView(moreView);moreView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));mContentView = moreView.findViewById(R.id.xlistview_footer_content);mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar);mHintView = (TextView)moreView.findViewById(R.id.xlistview_footer_hint_textview);}}