自定义ListView_下拉刷新上拉加载更多
自定义ListView实现下拉刷新和上拉自动加载
效果图:
下拉效果:
上拉效果:
实现原理:通过ListView的addFooter与addHeader方法,将下拉布局与上拉布局添加到ListView中,再通过设置padding属性,隐藏头部和脚部
监听onTouchEvent事件,根据手势滑动距离,动态更改下拉布局的padding,并动态更改头布局内控件效果
监听onScrollStateChanged,动态显示隐藏脚布局
设置回调,提供下拉刷新与加载更多的方法
Demo:https://files.cnblogs.com/files/liujingg/PullRefreshDemo.rar
PullListView.java
import java.text.SimpleDateFormat; import java.util.Date; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; 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.ListView; import android.widget.ProgressBar; import android.widget.TextView; /** * 自定义ListView,下拉刷新、上拉自动加载更多 * @author liujing * @version 1.0 */ public class PullListView extends ListView implements OnScrollListener { private final int PULL_DOWN_REFRESH = 0;//下拉状态 private final int RELEASE_REFRESH = 1;//松开状态 private final int REFRESHING = 2;//刷新中状态 private int currentState = PULL_DOWN_REFRESH; private int mListViewOnScreenY = -1; private int downY = -1; private boolean isLoadingMore = false; private boolean isEnabledPullDownRefresh = false; private boolean isEnabledLoadMore = false; private OnPullDownRefresh mOnPullDownRefresh; //头布局、脚布局及高度 private View mFootView; private LinearLayout mHeaderView; private int mFooterViewHeight; private int mPullDownHeaderViewHeight; //mHeaderView中组件及动画 private View mCustomHeaderView;//用户自定义头布局 private View mPullDownHeader;//下拉刷新头布局 private RotateAnimation upAnimation,downAnimation; private ImageView ivArrow; private ProgressBar mProgressBar; private TextView tv_statue,tv_time; public PullListView(Context context) { this(context, null); } public PullListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PullListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initPullDownHeaderView(); initLoadMoreFooterView(); } private void initLoadMoreFooterView() { //加载更多的布局文件 mFootView = View.inflate(getContext(), R.layout.pull_listview_footer, null); mFootView.measure(0, 0);//测量 mFooterViewHeight = mFootView.getMeasuredHeight(); //隐藏脚布局 mFootView.setPadding(0,-mFooterViewHeight,0,0); addFooterView(mFootView); setOnScrollListener(this); } private void initPullDownHeaderView() { //下拉刷新的布局文件 mHeaderView = (LinearLayout) View.inflate(getContext(), R.layout.pull_listview_header, null); mPullDownHeader = mHeaderView .findViewById(R.id.ll_refresh_pull_down_header); ivArrow = (ImageView) mHeaderView .findViewById(R.id.iv_refresh_header_arrow); mProgressBar = (ProgressBar) mHeaderView .findViewById(R.id.pb_refresh_header); tv_statue = (TextView) mHeaderView .findViewById(R.id.tv_refresh_header_status); tv_time = (TextView) mHeaderView .findViewById(R.id.tv_refresh_header_time); mPullDownHeader.measure(0, 0);//测量 mPullDownHeaderViewHeight = mPullDownHeader.getMeasuredHeight(); //隐藏头布局 mPullDownHeader.setPadding(0, -mPullDownHeaderViewHeight, 0, 0); addHeaderView(mHeaderView); initAnimation(); } /** * 添加额外的头布局,比如轮播图 * @param v 自定义头布局 */ public void addListViewCustomHeaderView(View v) { mCustomHeaderView = v; mHeaderView.addView(mCustomHeaderView); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: if (downY == -1) downY = (int) ev.getY(); //是否启用下拉刷新 if (!isEnabledPullDownRefresh) break; if (currentState == REFRESHING) break; //解决用户添加header与下拉刷新header的冲突 if (mCustomHeaderView != null) { int[] location = new int[2]; if (mListViewOnScreenY == -1) { this.getLocationOnScreen(location); mListViewOnScreenY = location[1]; } mCustomHeaderView.getLocationOnScreen(location); if (location[1] < mListViewOnScreenY) { break; } } int moveY = (int) ev.getY(); int diffY = (moveY - downY)/2; if (diffY > 0 && getFirstVisiblePosition() == 0) { int paddingTop = -mPullDownHeaderViewHeight + diffY; if (paddingTop < 0 && currentState != PULL_DOWN_REFRESH) { //当前没有完全显示且当前状态为松开刷新,进入下拉刷新 currentState = PULL_DOWN_REFRESH; refreshPullDownState(); } else if (paddingTop > 0 && currentState != RELEASE_REFRESH) { //当前完全显示且当前状态为下拉刷新,进入松开刷新 currentState = RELEASE_REFRESH; refreshPullDownState(); } mPullDownHeader.setPadding(0, paddingTop, 0, 0); return true; }else if(diffY < 0 && getLastVisiblePosition() == getCount()-1){ //脚布局可向上滑动 mFootView.setPadding(0,0,0,0); } break; case MotionEvent.ACTION_UP: downY = -1; if (currentState == PULL_DOWN_REFRESH) { //隐藏header mPullDownHeader.setPadding(0, -mPullDownHeaderViewHeight, 0, 0); } else if (currentState == RELEASE_REFRESH) { currentState = REFRESHING; refreshPullDownState(); mPullDownHeader.setPadding(0, 0, 0, 0); //回调刷新方法 if (mOnPullDownRefresh != null) { mOnPullDownRefresh.onPullDownRefresh(); } } break; } return super.onTouchEvent(ev); } /** * 隐藏头布局或脚布局并重置控件 */ public void OnRefreshDataFinish() { if (isLoadingMore) { isLoadingMore = false; mFootView.setPadding(0,-mFooterViewHeight,0,0); } else { ivArrow.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); tv_statue.setText("下拉刷新"); tv_time.setText("最后刷新时间:" + getCurrentTime()); mPullDownHeader.setPadding(0, -mPullDownHeaderViewHeight, 0, 0); currentState = PULL_DOWN_REFRESH; } } private String getCurrentTime() { SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); return format.format(new Date()); } private void refreshPullDownState() { switch (currentState) { case PULL_DOWN_REFRESH: ivArrow.startAnimation(downAnimation); tv_statue.setText("下拉刷新"); break; case RELEASE_REFRESH: ivArrow.startAnimation(upAnimation); tv_statue.setText("松开刷新"); break; case REFRESHING: ivArrow.clearAnimation(); ivArrow.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); tv_statue.setText("正在刷新"); break; default: break; } } /** * 箭头旋转动画 */ private void initAnimation() { upAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); upAnimation.setDuration(200); upAnimation.setFillAfter(true); downAnimation = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); downAnimation.setDuration(200); downAnimation.setFillAfter(true); } /** * 回调方法,用于刷新数据及加载更多 * @param listener */ public void setOnPullDownRefresh(OnPullDownRefresh listener) { this.mOnPullDownRefresh = listener; } public interface OnPullDownRefresh { public void onPullDownRefresh(); public void onLoadingMore(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (!isEnabledLoadMore) { return; } //listView停止状态或惯性滑动状态 if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) { //listView已到达最底部 if ((getLastVisiblePosition() == getCount() - 1) && !isLoadingMore) { isLoadingMore = true; //展示脚布局 //mFootView.setPadding(0, 0, 0, 0); setSelection(getCount()); if (mOnPullDownRefresh != null) { mOnPullDownRefresh.onLoadingMore(); } } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } /** * 是否启用下拉刷新 * @param isEnable */ public void setEnabledPullDownRefresh(boolean isEnable) { isEnabledPullDownRefresh = isEnable; } /** * 是否启用加载更多 * @param isEnable */ public void setEnabledLoadMore(boolean isEnable) { isEnabledLoadMore = isEnable; } }
pull_listview_header.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <LinearLayout android:id="@+id/ll_refresh_pull_down_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <FrameLayout android:layout_width="40dp" android:layout_height="40dp" android:layout_marginBottom="3dp" android:layout_marginLeft="28dp" android:layout_marginTop="8dp" > <ImageView android:id="@+id/iv_refresh_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/common_listview_headview_red_arrow" /> <ProgressBar android:id="@+id/pb_refresh_header" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminateDrawable="@drawable/custom_progressbar" android:visibility="invisible" /> </FrameLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" android:gravity="center_horizontal" android:orientation="vertical" > <TextView android:id="@+id/tv_refresh_header_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="#ff0000" android:textSize="16sp" android:textStyle="bold" > </TextView> <TextView android:id="@+id/tv_refresh_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="最后刷新时间:09:23:23" android:textColor="@android:color/darker_gray" android:textSize="12sp" > </TextView> </LinearLayout> <TextView android:layout_width="40dp" android:layout_marginRight="28dp" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout>
pull_listview_footer.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <ProgressBar android:layout_width="40dp" android:layout_height="40dp" android:layout_margin="2dp" android:indeterminateDrawable="@drawable/custom_progressbar" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:padding="5dp" android:text="加载更多..." android:textColor="#ff0000" android:textSize="18sp" /> </LinearLayout>
custom_progressbar.xml
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:toDegrees="360" > <shape android:innerRadiusRatio="4" android:shape="ring" android:thicknessRatio="10" android:useLevel="false" > <gradient android:centerColor="#ff6666" android:endColor="#ff0000" android:startColor="#ffffff" android:type="sweep" /> </shape> </rotate>