这阵子发现以前做的项目中,所有的列表都没有弄动态加载数据
导致数据量多的时候,加载很慢。
网上找了些的例子,不是很卡就是功能不全
所以参考了一些例子,自己写了一个
先上截图
小儿,上源码->> 点我点我点我~~~
先说说思路吧,这里是自己写一个类,去继承ListView和实现OnScrollListener接口。
在OnScrollListener中的onScroll方法中去处理“下拉刷新”的控件和状态
然后在屏幕的触摸事件中去处理下拉的高度,也就是去重写onTouchEvent方法
PullRefreshListView.java
package com.example.pullrefreshlistview; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; 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.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; public class PullRefreshListView extends ListView implements OnScrollListener { private final int REFRESHING = 1; // 刷新中 private final int RELEASE_TO_REFRESH = 2; // 释放刷新 private final int PULL_TO_REFRESH = 3; // 下拉刷新 private final int REFRESH_NORMAL = 4; // 正常状态 private final int LOADMORE_MORMAL = 5; // 未加载更多 private final int LOADMORE_LOADING = 6; // 加载中 private int mScrollState; // 当前滚动位置 private int mRefreshState; // 当前刷新状态 private int mLoadSate; // 当前加载状态 private OnRefreshListener mRefreshListener; private OnScrollListener mOnScrollListener; private LayoutInflater mInflater; //布局加载器 // "下拉刷新"布局的控件 private RelativeLayout headerView; private ProgressBar pgsHeaderRefresh; private ImageView imgHeaderArrow; private TextView txtHeaderPullRefresh; private LinearLayout layoutHeaderLoading; private TextView txtHeaderTime; // "加载更多"布局的控件 private RelativeLayout footerView; private ProgressBar pgsFooterRefresh; private TextView txtFooterLoadMore; private RotateAnimation mFlipAnimation; // 下拉动画 private RotateAnimation mReverseFlipAnimation; // 恢复动画 private int lastTouchY; // 最后点击位置的Y坐标 private int headerHeight; // 顶部高度 private int headerPaddingTop; // 顶部布局的内边距 private int headerPaddingBottom; // 顶部布局的内边距 private int headerPaddingLeft; // 顶部布局的内边距 private int headerPaddingRight; // 顶部布局的内边距 private boolean isExitsListener; //是否设置了刷新事件 private boolean refreshMode; //上拉刷新模式 private boolean loadMoreMode; //下拉刷新模式 private final String TAG = "PullRefreshListView"; private final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm", Locale.CHINA); public PullRefreshListView(Context context) { super(context); // TODO Auto-generated constructor stub init(context); } public PullRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public PullRefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { /** * 定义旋转动画 参数: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); mInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); headerView = (RelativeLayout) mInflater.inflate( R.layout.pull_refresh_header, this, false); pgsHeaderRefresh = (ProgressBar) headerView .findViewById(R.id.pull_refresh_header_pgs_refresh); imgHeaderArrow = (ImageView) headerView .findViewById(R.id.pull_refresh_header_img_arrow); txtHeaderPullRefresh = (TextView) headerView .findViewById(R.id.pull_refresh_header_txt_pull_refresh); layoutHeaderLoading = (LinearLayout) headerView .findViewById(R.id.pull_refresh_header_lot_loading); txtHeaderTime = (TextView) headerView .findViewById(R.id.pull_refresh_header_txt_time); footerView = (RelativeLayout) mInflater.inflate( R.layout.pull_loadmore_footer, this, false); pgsFooterRefresh = (ProgressBar) footerView .findViewById(R.id.pull_refresh_footer_pgs_loadmore); txtFooterLoadMore = (TextView) footerView .findViewById(R.id.pull_refresh_footer_txt_loadmore); // 为列表添加"下拉刷新"和"加载更多"布局 addHeaderView(headerView); addFooterView(footerView); refreshMode = true; loadMoreMode = true; measureView(headerView); // 测量"下拉刷新"视图 headerHeight = headerView.getMeasuredHeight(); // 获取"下拉刷新"布局的高度 // 初始化"下拉刷新"内边距 headerPaddingTop = headerView.getPaddingTop(); headerPaddingBottom = headerView.getPaddingBottom(); headerPaddingLeft = headerView.getPaddingLeft(); headerPaddingRight = headerView.getPaddingRight(); // 初始化刷新状态和加载状态 mRefreshState = REFRESH_NORMAL; mLoadSate = LOADMORE_MORMAL; // 设置点击"加载更多"时,加载更多 footerView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if (mLoadSate != LOADMORE_LOADING) { prepareForLoad(); loadMore(); mLoadSate = LOADMORE_LOADING; } } }); super.setOnScrollListener(this); } /** * 设置无"下拉刷新"模式 */ public void setNoRefreshMode() { refreshMode = false; this.removeHeaderView(headerView); } /** * 设置无"加载更多"模式 */ public void setNoLoadMoreMode() { loadMoreMode = false; this.removeFooterView(footerView); } /** * 测量View的高度 * * @param child */ private void measureView(View child) { // TODO Auto-generated method stub 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 setOnScrollListener(OnScrollListener listener) { // TODO Auto-generated method stub mOnScrollListener = listener; } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); // 判断是否是下拉刷新模式 if (refreshMode) { setSelection(1); } else { setSelection(0); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub if (refreshMode) { if (mScrollState == SCROLL_STATE_TOUCH_SCROLL && mRefreshState != REFRESHING) { if (firstVisibleItem == 0) { // 如果下拉了listiview,则显示上拉刷新动画 if (mRefreshState != RELEASE_TO_REFRESH && (headerView.getTop() >= 0 || headerView .getBottom() >= headerHeight)) { txtHeaderPullRefresh.setText("释放刷新"); imgHeaderArrow.clearAnimation(); imgHeaderArrow.startAnimation(mFlipAnimation); mRefreshState = RELEASE_TO_REFRESH; // 如果下拉距离不够,则回归原来的状态 } else if (mRefreshState != PULL_TO_REFRESH && headerView.getBottom() < headerHeight) { txtHeaderPullRefresh.setText("下拉刷新"); if (mRefreshState != REFRESH_NORMAL) { imgHeaderArrow.clearAnimation(); imgHeaderArrow .startAnimation(mReverseFlipAnimation); } mRefreshState = PULL_TO_REFRESH; } } } else if (mScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0 && mRefreshState != REFRESHING) { // 当列表处于飞滑状态时,不能让他滑到表头 setSelection(1); } } /** * 判断是否滑到底部 */ if (loadMoreMode && mLoadSate != LOADMORE_LOADING && isExitsListener && getBottom() == footerView.getBottom()) { prepareForLoad(); loadMore(); mLoadSate = LOADMORE_LOADING; } if (mOnScrollListener != null) { mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } @Override public boolean onTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub final int touchY = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_UP: // 判断是否有表头 if (refreshMode) { // 如果滚动条不可用,设置为可用状态 if (!isVerticalScrollBarEnabled()) { setVerticalScrollBarEnabled(true); } if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) { // 判断下拉距离是否符合刷新条件 if (headerView.getBottom() >= headerHeight && mRefreshState == RELEASE_TO_REFRESH) { // 准备刷新 prepareForRefresh(); // 刷新 refresh(); } else if (headerView.getBottom() < headerHeight && mRefreshState == PULL_TO_REFRESH) { // 重置列表头控件 resetHeader(); // 弹回第一项 setSelection(1); } } } break; case MotionEvent.ACTION_MOVE: calHeaderPadding(touchY); break; case MotionEvent.ACTION_DOWN: lastTouchY = touchY; break; } return super.onTouchEvent(ev); } /** * 计算并设置表头的顶部内边距 * * @param ev */ public void calHeaderPadding(int y) { // 如果列表处于"释放刷新"状态的话,扩大表头的顶部内边距 if (mRefreshState == RELEASE_TO_REFRESH && mRefreshState != REFRESHING) { //感觉2.5倍的差值滑起来挺不错的,自己可以设置 int topPadding = (int) ((y - lastTouchY) / 2.5) - headerPaddingTop; // 设置上、下、左、右四个内边距 headerView.setPadding(headerPaddingLeft, topPadding, headerPaddingRight, headerPaddingBottom); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub mScrollState = scrollState; if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(view, scrollState); } } /** * 为刷新前做准备,调整控件是否显示 */ private void prepareForRefresh() { // 让列表滚动到表头,距离headerHeight的位置,在500ms内 smoothScrollToPosition(0); setSelectionFromTop(0, headerHeight); resetHeaderPadding(); // 设置表头控件的显示 pgsHeaderRefresh.setVisibility(View.VISIBLE); txtHeaderPullRefresh.setVisibility(View.GONE); imgHeaderArrow.clearAnimation(); imgHeaderArrow.setVisibility(View.GONE); layoutHeaderLoading.setVisibility(View.VISIBLE); // 设置刷新状态为"刷新中" mRefreshState = REFRESHING; } private void prepareForLoad() { txtFooterLoadMore.setText("加载中"); pgsFooterRefresh.setVisibility(View.VISIBLE); } /** * 重置顶部控件 */ private void resetHeader() { // TODO Auto-generated method stub if (mRefreshState != REFRESH_NORMAL) { resetHeaderPadding(); mRefreshState = REFRESH_NORMAL; txtHeaderPullRefresh.setText("下拉刷新"); imgHeaderArrow.clearAnimation(); pgsHeaderRefresh.setVisibility(View.GONE); layoutHeaderLoading.setVisibility(View.GONE); imgHeaderArrow.setVisibility(View.VISIBLE); txtHeaderPullRefresh.setVisibility(View.VISIBLE); } } /** * 恢复顶部的内边距 */ private void resetHeaderPadding() { headerView.setPadding(headerPaddingLeft, headerPaddingTop, headerPaddingRight, headerPaddingBottom); } /** * 下拉刷新 */ private void refresh() { if (mRefreshListener != null) { mRefreshListener.onRefresh(); } } /** * 下拉刷新完成时 */ public void refreshComplete() { resetHeader(); txtHeaderTime.setText("更新于 " + sdf.format(new Date())); if (headerView.getBottom() > 0) { setSelection(1); } } /** * 加载更多 */ private void loadMore() { if (mRefreshListener != null) { mRefreshListener.onLoadMore(); } } /** * 加载更多完成时 */ public void loadMoreComplete() { Log.i(TAG, "loadMoreComplete"); mLoadSate = LOADMORE_MORMAL; txtFooterLoadMore.setText("加载更多"); pgsFooterRefresh.setVisibility(View.GONE); } /** * 设置刷新事件 * * @param listener */ public void setOnRefreshListener(OnRefreshListener listener) { isExitsListener = true; mRefreshListener = listener; } /** * 接口定义一个回调方法当列表被刷新 */ public interface OnRefreshListener { /** * 当列表下拉刷新时,调用这个方法 */ public void onRefresh(); /** * 当列表上拉加载更多是,调用这个方法 */ public void onLoadMore(); } }
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub if (refreshMode) { if (mScrollState == SCROLL_STATE_TOUCH_SCROLL && mRefreshState != REFRESHING) { if (firstVisibleItem == 0) { // 如果下拉了listiview,则显示上拉刷新动画 if (mRefreshState != RELEASE_TO_REFRESH && (headerView.getTop() >= 0 || headerView .getBottom() >= headerHeight)) { txtHeaderPullRefresh.setText("释放刷新"); imgHeaderArrow.clearAnimation(); imgHeaderArrow.startAnimation(mFlipAnimation); mRefreshState = RELEASE_TO_REFRESH; // 如果下拉距离不够,则回归原来的状态 } else if (mRefreshState != PULL_TO_REFRESH && headerView.getBottom() < headerHeight) { txtHeaderPullRefresh.setText("下拉刷新"); if (mRefreshState != REFRESH_NORMAL) { imgHeaderArrow.clearAnimation(); imgHeaderArrow .startAnimation(mReverseFlipAnimation); } mRefreshState = PULL_TO_REFRESH; } } } else if (mScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0 && mRefreshState != REFRESHING) { // 当列表处于飞滑状态时,不能让他滑到表头 setSelection(1); } } /** * 判断是否滑到底部 */ if (loadMoreMode && mLoadSate != LOADMORE_LOADING && isExitsListener && getBottom() == footerView.getBottom()) { prepareForLoad(); loadMore(); mLoadSate = LOADMORE_LOADING; } if (mOnScrollListener != null) { mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }
SCROLL_STATE_FLING //滚动条处于飞‘飞滑’状态,也就是手指不在屏幕但列表还在滑动
SCROLL_STATE_TOUCH_SCROLL //滚动条处于手指触摸滑动状态
撸主先去判断是否设置的"下拉刷新"模式,然后再去判断滚动条的状态和当前刷新的状态,
然后通过header的底部边缘为界限去做一些处理,代码注释显得很清楚,这里不多说。
最后别忘了下面这个方法,去记录滚动条的状态
@Override public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub mScrollState = scrollState; if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(view, scrollState); } }
然后在onTouchEvent中,对listView做处理
@Override public boolean onTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub final int touchY = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_UP: // 判断是否设置了加载更多模式 if (refreshMode) { // 如果滚动条不可用,设置为可用状态 if (!isVerticalScrollBarEnabled()) { setVerticalScrollBarEnabled(true); } if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) { // 判断下拉距离是否符合刷新条件 if (headerView.getBottom() >= headerHeight && mRefreshState == RELEASE_TO_REFRESH) { // 准备刷新 prepareForRefresh(); // 刷新 refresh(); } else if (headerView.getBottom() < headerHeight && mRefreshState == PULL_TO_REFRESH) { // 重置列表头控件 resetHeader(); // 弹回第一项 setSelection(1); } } } break; case MotionEvent.ACTION_MOVE: calHeaderPadding(touchY); break; case MotionEvent.ACTION_DOWN: lastTouchY = touchY; break; } return super.onTouchEvent(ev); } /** * 计算并设置表头的顶部内边距 * * @param ev */ public void calHeaderPadding(int y) { // 如果列表处于"释放刷新"状态的话,扩大表头的顶部内边距 if (mRefreshState == RELEASE_TO_REFRESH && mRefreshState != REFRESHING) { //感觉2.5倍的差值滑起来挺不错的,自己可以设置 int topPadding = (int) ((y - lastTouchY) / 2.5) - headerPaddingTop; // 设置上、下、左、右四个内边距 headerView.setPadding(headerPaddingLeft, topPadding, headerPaddingRight, headerPaddingBottom); } }
下面看看我们如何调用它
package com.example.pullrefreshlistview; import java.util.Arrays; import java.util.LinkedList; import com.example.pullrefreshlistview.PullRefreshListView.OnRefreshListener; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.os.SystemClock; import android.widget.ArrayAdapter; public class MainActivity extends Activity { private int newData = 21; private LinkedList<String> mListItems; private PullRefreshListView listView; public String[] mStrings = { "1","2","3","4","5" ,"6","7","8","9","10" ,"11","12","13","14","15" ,"16","17","18","19","20"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListItems = new LinkedList<String>(); mListItems.addAll(Arrays.asList(mStrings)); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems); listView = (PullRefreshListView) findViewById(R.id.list); listView.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { // TODO Auto-generated method stub new RefreshNetWork().execute(); } @Override public void onLoadMore() { new LoadMoreNetWork().execute(); } }); listView.setAdapter(adapter); } /** * 下拉刷新时加载数据 * @author Administrator * */ private class RefreshNetWork extends AsyncTask<Void, Void, Void>{ @Override protected Void doInBackground(Void... params) { //模拟网络加载时间 SystemClock.sleep(3000); return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); //别忘了调用Compelete方法; listView.refreshComplete(); } } /** * 加载更多时加载数据 * @author Administrator * */ private class LoadMoreNetWork extends AsyncTask<Void, Void, Void>{ @Override protected Void doInBackground(Void... params) { //模拟网络加载时间 SystemClock.sleep(3000); return null; } @Override protected void onPostExecute(Void result) { // TODO Auto-generated method stub mListItems.addLast( (newData++) + ""); listView.loadMoreComplete(); super.onPostExecute(result); } } }
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" > <com.example.pullrefreshlistview.PullRefreshListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/list"/> </RelativeLayout>
如果不想用"下拉刷新"或者"加载更多"功能怎么办呢,可以调用这两个方法分别取禁用
/** * 设置无"下拉刷新"模式 */ public void setNoRefreshMode() { refreshMode = false; this.removeHeaderView(headerView); } /** * 设置无"加载更多"模式 */ public void setNoLoadMoreMode() { loadMoreMode = false; this.removeFooterView(footerView); }
最后,如果你们发现项目有什么问题,麻烦告诉撸主~~~~
最近爱上开源~~~以后有什么demo撸主会分享出来~