在Android中ListView下拉刷新、上拉载入更多示例一文中,Maxwin兄给出的控件比较强大,前面有详细介绍,但是有个不足就是,里面使用了一些资源文件,包括图片,String,layout,这样不利于封装打包,下面我将源码进行改进,所有布局全部用代码实现,这样直接将src和assets打包成jar就成了一个非常方便以后使用的扩展ListView控件,代码如下:
XListView:
/** * @file XListView.java * @package me.maxwin.view * @create Mar 18, 2012 6:28:41 PM * @author Maxwin * @description An ListView support (a) Pull down to refresh, (b) Pull up to load more. * Implement IXListViewListener, and see stopRefresh() / stopLoadMore(). */ package com.home.view; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.TextView; public class XListView extends ListView implements OnScrollListener { private float mLastY = -1; // save event y private Scroller mScroller; // used for scroll back private OnScrollListener mScrollListener; // user's scroll listener // the interface to trigger refresh and load more. private IXListViewListener mListViewListener; // -- header view private XListViewHeader mHeaderView; // header view content, use it to calculate the Header's height. And hide it // when disable pull refresh. private RelativeLayout mHeaderViewContent; private TextView mHeaderTimeView; private int mHeaderViewHeight; // header view's height private boolean mEnablePullRefresh = true; private boolean mPullRefreshing = false; // is refreashing. // -- footer view private 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; private final static int SCROLLBACK_FOOTER = 1; private final static int SCROLL_DURATION = 400; // scroll back duration private 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); // init header view mHeaderView = new XListViewHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView .findViewById(XListViewHeader.RELAYOUT_ID); mHeaderTimeView = (TextView) mHeaderView .findViewById(XListViewHeader.HEAD_TIME_VIEW_ID); addHeaderView(mHeaderView); // init footer view mFooterView = new XListViewFooter(context); // init header height mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } @Override public void setAdapter(ListAdapter adapter) { // make sure XListViewFooter is the last footer view, and only add once. if (mIsFooterReady == false) { mIsFooterReady = true; addFooterView(mFooterView); } super.setAdapter(adapter); } /** * enable or disable pull down refresh feature. * * @param enable */ public void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; if (!mEnablePullRefresh) { // disable, hide the content mHeaderViewContent.setVisibility(View.INVISIBLE); } else { mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * enable or disable pull up load more feature. * * @param enable */ public void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; if (!mEnablePullLoad) { mFooterView.hide(); mFooterView.setOnClickListener(null); } else { mPullLoading = false; mFooterView.show(); mFooterView.setState(XListViewFooter.STATE_NORMAL); // both "pull up" and "click" will invoke load more. mFooterView.setOnClickListener(new OnClickListener() { @Override public 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. */ 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); } } 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; // default: scroll back to dismiss header. // 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 computeScroll invalidate(); } 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 } 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(); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); System.out.println("数据监测:" + getFirstVisiblePosition() + "---->" + getLastVisiblePosition()); if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) { // 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)) { // last item, already pulled up or want to pull up. updateFooterHeight(-deltaY / OFFSET_RADIO); } break; default: mLastY = -1; // reset if (getFirstVisiblePosition() == 0) { // invoke refresh if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mPullRefreshing = true; mHeaderView.setState(XListViewHeader.STATE_REFRESHING); if (mListViewListener != null) { mListViewListener.onRefresh(); } } resetHeaderHeight(); } if (getLastVisiblePosition() == mTotalItemCount - 1) { // invoke load more. if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA) { startLoadMore(); } resetFooterHeight(); } break; } return super.onTouchEvent(ev); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); } else { mFooterView.setBottomMargin(mScroller.getCurrY()); } postInvalidate(); invokeOnScrolling(); } super.computeScroll(); } @Override public void setOnScrollListener(OnScrollListener l) { mScrollListener = l; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // send to user's listener mTotalItemCount = 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(); } }
XListViewFooter:
package com.home.view; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; 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 RelativeLayout mContentView; private ProgressBar mProgressBar; private TextView mHintView; public XListViewFooter(Context context) { super(context); initView(context); } public XListViewFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } 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("松开载入更多"); } else if (state == STATE_LOADING) { mProgressBar.setVisibility(View.VISIBLE); } else { mHintView.setVisibility(View.VISIBLE); mHintView.setText("查看更多"); } } public void setBottomMargin(int height) { if (height < 0) return; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); } 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); } private void initView(Context context) { mContext = context; // 根布局 LinearLayout moreView = new LinearLayout(mContext); moreView.setLayoutParams(new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); // 将根布局加到该LinearLayout中 addView(moreView); // 根布局里面的相对布局 mContentView = new RelativeLayout(mContext); LinearLayout.LayoutParams linearLp1 = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); mContentView.setPadding(10, 10, 10, 10); // 将相对布局relayout加到moreView根布局中 moreView.addView(mContentView, linearLp1); // 进度条mProgressBar mProgressBar = new ProgressBar(mContext); RelativeLayout.LayoutParams relativeLp1 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); relativeLp1.addRule(RelativeLayout.CENTER_IN_PARENT); mProgressBar.setVisibility(View.INVISIBLE); // 将mProgressBar加入相对布局relayout中 mContentView.addView(mProgressBar, relativeLp1); // 查看更多的TextView mHintView = new TextView(mContext); mHintView.setText("查看更多"); mHintView.setGravity(Gravity.CENTER); RelativeLayout.LayoutParams relativeLp2 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); relativeLp2.addRule(RelativeLayout.CENTER_IN_PARENT); // 将其加入相对布局relayout中 mContentView.addView(mHintView, relativeLp2); } }
XListViewHeader:
package com.home.view; import java.io.IOException; import java.io.InputStream; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; 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 static final int LINEAROUT_1_ID = 1; public static final int RELAYOUT_ID = 2; public static final int HEAD_TIME_VIEW_ID = 3; 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) { // 根布局 mContainer = new LinearLayout(context); mContainer.setGravity(Gravity.BOTTOM); // 初始情况,设置下拉刷新view高度为0 LinearLayout.LayoutParams linearLp1 = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, 0); // 将根布局加入该LinearLayout中 addView(mContainer, linearLp1); setGravity(Gravity.BOTTOM); // 里面的相对布局 RelativeLayout relayout = new RelativeLayout(context); relayout.setId(RELAYOUT_ID); LinearLayout.LayoutParams linearLp2 = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, 60); // 将里面的相对布局加入根布局中 mContainer.addView(relayout, linearLp2); // 相对布局里的子线性布局1 LinearLayout linear1 = new LinearLayout(context); linear1.setId(LINEAROUT_1_ID); RelativeLayout.LayoutParams relativeLp1 = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); relativeLp1.addRule(RelativeLayout.CENTER_IN_PARENT); linear1.setGravity(Gravity.CENTER); linear1.setOrientation(LinearLayout.VERTICAL); // 将子布局linear1加入relayout中 relayout.addView(linear1, relativeLp1); // 下拉刷新提示TextView mHintTextView = new TextView(context); mHintTextView.setText("下拉刷新"); LinearLayout.LayoutParams linearLp3 = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // 将TextView加入linear1中 linear1.addView(mHintTextView, linearLp3); // 子线性布局1(linear1)里的线性布局linear12 LinearLayout linear12 = new LinearLayout(context); LinearLayout.LayoutParams linearLp4 = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); linearLp4.setMargins(0, 3, 0, 0); // 将linear12加入linear1中 linear1.addView(linear12, linearLp4); // 提示时间TextView TextView tv = new TextView(context); tv.setText("上次更新时间:"); tv.setTextSize(12); // 将提示时间TextView加入linear12 linear12.addView(tv, linearLp3); // 时间值TextView TextView tv2 = new TextView(context); tv2.setId(HEAD_TIME_VIEW_ID); tv2.setTextSize(12); // 将时间值TextView加入linear12 linear12.addView(tv2, linearLp3); // ImageView mArrowImageView = new ImageView(context); RelativeLayout.LayoutParams relativeLp2 = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); relativeLp2.rightMargin = 20; relativeLp2.addRule(RelativeLayout.CENTER_VERTICAL); relativeLp2.addRule(RelativeLayout.LEFT_OF, LINEAROUT_1_ID); mArrowImageView.setImageBitmap(readAssetImage(context)); // 将ImageView加到相对布局relayout中 relayout.addView(mArrowImageView, relativeLp2); // ProgressBar mProgressBar = new ProgressBar(context); RelativeLayout.LayoutParams relativeLp3 = new RelativeLayout.LayoutParams( 45, 45); relativeLp3.addRule(RelativeLayout.LEFT_OF, LINEAROUT_1_ID); relativeLp3.addRule(RelativeLayout.CENTER_VERTICAL); relativeLp3.rightMargin = 20; mProgressBar.setVisibility(View.INVISIBLE); // 将mProgressBar加到相对布局relayout中 relayout.addView(mProgressBar, relativeLp3); 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("下拉刷新"); break; case STATE_READY: if (mState != STATE_READY) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText("松开刷新数据"); } break; case STATE_REFRESHING: mHintTextView.setText("正在加载..."); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getVisiableHeight() { return mContainer.getHeight(); } /** * 读取assets里面文件名为xlistview_arrow.png的图片 * * @param context * @return bitmap */ private Bitmap readAssetImage(Context context) { AssetManager asset = context.getAssets(); InputStream assetFile = null; Bitmap bitmap = null; try { assetFile = asset.open("xlistview_arrow.png"); bitmap = BitmapFactory.decodeStream(assetFile); } catch (IOException e) { e.printStackTrace(); } return bitmap; } }
然后将使用到的那张图片放在assets目录下和src一起打包即可。