Android View - 上拉刷新下拉加载ListView
2017-07-28 17:11
495 查看
虽然网上有很多上拉刷新库,效果也很好,只是有时候突然来的Bug不是很好处理,而且有时候还达不到效果,所以今天用ListView实现上拉刷新,下拉加载的效果。直接上码:
本着易扩展的理念,先写一个父类,实现上拉刷新,下拉加载的效果:
代码的注释已经很清楚说明了上拉刷新和下拉加载的原理,还有遇到的一些坑和解决办法,相信你看得懂。
下面是我实现的子类:
只要模仿RefreshListView,继承AbsRefreshListView,就很轻松实现自己的上拉刷新下拉加载的ListView。
代码已经上传到我的github:https://github.com/JohanMan/viewtoolkit
因为RefreshListView是已经实现了上拉刷新下拉加载了,可以直接使用。
使用实例:
xml
activity
本着易扩展的理念,先写一个父类,实现上拉刷新,下拉加载的效果:
package com.johan.library.viewtoolkit.refreshlistview; import android.animation.ValueAnimator; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.AbsListView; import android.widget.ListView; /** * Created by johan on 2017/7/27. */ public abstract class AbsRefreshListView extends ListView implements AbsListView.OnScrollListener { /** 动画每毫秒移动距离 */ private static final int ANIMATION_STEP = 1; /** 移动速率 */ private static final float MOVE_RATIO = 0.4f; /** 刷新View最大高度 */ private static final int REFRESH_VIEW_MAX_HEIGHT = 500; /** 不没有刷新 */ private static final int STATE_NO_REFRESH = 1; /** 正在上拉或者下拉 */ private static final int STATE_RELEASE_REFRESH = 2; /** 正在刷新 */ private static final int STATE_REFRESHING = 3; /** 状态 */ private int state = STATE_NO_REFRESH; /** 记录手指滑动的Y值 */ private float currentY; /** headerView和footerView的原始值 */ private int headerHeight, footerHeight; /** Header Footer View */ private View headerView, footerView; /** 记录是否最顶和最底 */ private boolean isTop = true, isBottom; /** 监听滑动的Listener,因为在AbsRefreshListView已经调用了setOnScrollListener,需要以另一种方式提供用户使用 */ private OnScrollListener onScrollListener; /** 是否可以上拉和下拉 */ private boolean canPullTop = true, canPullBottom = true; /** 判断是否在动画 */ private boolean isAnimation; /** 记录动画时,上一次的值 */ private int lastAnimationValue; public AbsRefreshListView(Context context) { super(context); init(); } public AbsRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { headerView = initHeaderView(); footerView = initFooterView(); if (headerView != null) { // 获取headerView的高度 headerView.measure(0, 0); headerHeight = headerView.getMeasuredHeight(); post(new Runnable() { @Override public void run() { // 设置headerView的高度 setViewHeight(headerView, 0); } }); addHeaderView(headerView); } else { canPullTop = false; } if (footerView != null) { // 获取footerView的高度 footerView.measure(0, 0); footerHeight = footerView.getMeasuredHeight(); post(new Runnable() { @Override public void run() { // 设置footerView的高度 setViewHeight(footerView, 0); } }); addFooterView(footerView); } else { canPullBottom = false; } setOnScrollListener(this); } /** * 设置View的高度 * Bug1 : 当height=0时,view会恢复原来的高度 * Bug1解决办法 : 如果height=0时,隐藏view,并设为1 * Bug2 : 当height=0时,分割线还会显示 * Bug2解决办法 : 如果height=0时,隐藏分割线,还好ListView有设置是否显示分割线的方法 * @param view * @param height */ private void setViewHeight(View view, int height) { int visibility = height == 0 ? View.GONE : View.VISIBLE; view.setVisibility(visibility); boolean isShowDivider = height != 0; if (view == headerView) { setHeaderDividersEnabled(isShowDivider); } else { setFooterDividersEnabled(isShowDivider); } LayoutParams params = (LayoutParams) view.getLayoutParams(); if (params == null) { params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } height = height == 0 ? 1 : height; params.height = height; view.setLayoutParams(params); } /** * 初始化Header,子类实现 * @return */ protected abstract View initHeaderView(); /** * 初始化Footer,子类实现 * @return */ protected abstract View initFooterView(); /** * 重写onTouchEvent实现上拉和下拉功能 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { if (isAnimation) { return super.onTouchEvent(event); } switch (event.getAction()) { case MotionEvent.ACTION_DOWN : currentY = event.getY(); break; case MotionEvent.ACTION_MOVE : // 单次移动的距离:手指移动的距离乘以移动速率,为了不让刷新的View显示太快 float moveY = (event.getY() - currentY) * MOVE_RATIO; currentY = event.getY(); // 上拉 if (isTop && canPullTop) { // 在最顶,如果上滑,不拦截 if (state == STATE_NO_REFRESH && moveY < 0) return super.onTouchEvent(event); // 设置headerView高度 updateHeader((int) moveY); // 如果不处理的话,下拉至刷新状态之后,不放手,然后上滑,发现设置不了headerView的高度 // 因为此时的ListView也会处理ACTION_MOVE事件下滑,所以我们要返回true,表示我们要处理事件 return true; } // 下拉 if (isBottom && canPullBottom) { // 在最底,如果下滑,不拦截 if (state == STATE_NO_REFRESH && moveY > 0) return super.onTouchEvent(event); // 设置footerView高度 updateFooter((int) -moveY); // 因为我们处理事件,所以ListView的滑动就停止了 // 所以,虽然我们设置了footerView的高度,但是还是显示不出来 // 因此,需要我们手动设置滑动 scrollBy(0, (int) -moveY); // 表示处理事件 return true; } break; case MotionEvent.ACTION_UP : currentY = 0; if (state == STATE_RELEASE_REFRESH) { if (headerView.getHeight() >= headerHeight) { // 上拉刷新 releaseRefresh(); state = STATE_REFRESHING; onRefreshing(true); } else if (footerView.getHeight() >= footerHeight) { // 下拉加载 releaseRefresh(); state = STATE_REFRESHING; onRefreshing(false); } else { // 没有刷新的话,回到原始状态 completeRefresh(); } } break; } return super.onTouchEvent(event); } /** * 更新headerView * @param moveY */ private void updateHeader(int moveY) { state = STATE_RELEASE_REFRESH; int height = headerView.getHeight() + moveY; height = Math.max(0, Math.min(REFRESH_VIEW_MAX_HEIGHT, height)); setViewHeight(headerView, height); onProgress(true, height, headerHeight); } /** * 更新footerView * @param moveY */ private void updateFooter(int moveY) { state = STATE_RELEASE_REFRESH; int height = footerView.getHeight() + moveY; height = Math.max(0, Math.min(REFRESH_VIEW_MAX_HEIGHT, height)); setViewHeight(footerView, height); onProgress(false, footerView.getHeight(), footerHeight); } /** * 松手刷新 */ private void releaseRefresh() { final View refreshView = headerView.getHeight() > 1 ? headerView : footerView.getHeight() > 1 ? footerView : null; int refreshViewHeight = headerView.getHeight() > 1 ? headerHeight : footerView.getHeight() > 1 ? footerHeight : 0; if (refreshView == null) { return; } if (refreshViewHeight == 0) { return; } if (refreshView.getHeight() < 2) { return; } if (refreshView.getHeight() > refreshViewHeight) { lastAnimationValue = refreshView.getHeight(); ValueAnimator animator = ValueAnimator.ofInt(refreshView.getHeight(), refreshViewHeight); animator.setDuration((refreshView.getHeight() - refreshViewHeight) / ANIMATION_STEP + 1); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); setViewHeight(refreshView, height); // 因为上拉加载时,我们手动滚动了ListView,现在要恢复滚动的位置 if (isBottom) { scrollBy(0, height - lastAnimationValue); lastAnimationValue = height; } } }); animator.start(); } } /** * 完成刷新 * Bug1 : 手指正在滑动,打算取消刷新,而此时,外部调用了completeRefresh完成刷新,把refreshView重置了, * 造成手指还在滑动,refreshView突然不见 * Bug1解决办法 : 根据 currentY != 0 判断手指是否在滑动,如果手指还在滑动,则不做任何操作,否则重置refreshView * 注意1 : 不能用 headerView.getHeight() != 0 这种方式判断是否显示有headerView,具体原因请看setViewHeight方法 * 要用 headerView.getHeight() > 1 方式判断 */ public void completeRefresh() { if (currentY != 0) return; isAnimation = true; final View refreshView = headerView.getHeight() > 1 ? headerView : footerView.getHeight() > 1 ? footerView : null; if (refreshView == null) { isAnimation = false; return; } if (refreshView.getHeight() < 2) { isAnimation = false; return; } lastAnimationValue = refreshView.getHeight(); ValueAnimator animator = ValueAnimator.ofInt(refreshView.getHeight(), 0); animator.setDuration(refreshView.getHeight() / ANIMATION_STEP + 1); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); setViewHeight(refreshView, height); // 因为上拉加载时,我们手动滚动了ListView,现在要恢复滚动的位置 if (isBottom) { scrollBy(0, height - lastAnimationValue); lastAnimationValue = height; } if (height == 0) { state = STATE_NO_REFRESH; isAnimation = false; onComplete(refreshView == headerView); } } }); animator.start(); } /** * 判断是否最顶或者最低或者都不是 * @param view * @param firstVisibleItem * @param visibleItemCount * @param totalItemCount */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (firstVisibleItem == 0) { isTop = true; isBottom = false; } else if ((firstVisibleItem + visibleItemCount) == totalItemCount) { isBottom = true; isTop = false; } else { isTop = false; isBottom = false; } if (onScrollListener != null) { onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (onScrollListener != null) { onScrollListener.onScrollStateChanged(view, scrollState); } } /** * 设置是否可以上拉 * @param canPullTop */ public void setCanPullTop(boolean canPullTop) { this.canPullTop = canPullTop; } /** * 设置是否可以下拉 * @param canPullBottom */ public void setCanPullBottom(boolean canPullBottom) { this.canPullBottom = canPullBottom; } /** * 是否在刷新 * @return */ public boolean isRefreshing() { return state == STATE_REFRESHING; } /** * 因为AbsRefreshListView已经调用了setOnScrollListener方法,不能在外面调用setOnScrollListener,否则会影响AbsRefreshListView * 请使用该方法,和setOnScrollListener的功能是一样的 * @param onScrollListener */ public void setScrollListener(OnScrollListener onScrollListener) { this.onScrollListener = onScrollListener; } /** * 下拉上拉时调用 * @param isTop * @param currentHeight * @param viewHeight */ protected void onProgress(boolean isTop, int currentHeight, int viewHeight) { } /** * 刷新时调用,子类实现 * @param isTop */ protected abstract void onRefreshing(boolean isTop); /** * 刷新完成时调用 * @param isTop */ protected void onComplete(boolean isTop) { } }
代码的注释已经很清楚说明了上拉刷新和下拉加载的原理,还有遇到的一些坑和解决办法,相信你看得懂。
下面是我实现的子类:
package com.johan.library.viewtoolkit.refreshlistview; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.ImageView; import android.widget.TextView; import com.johan.library.R; /** * Created by johan on 2017/7/27. */ public class RefreshListView extends AbsRefreshListView { private ImageView headerIconView, footerIconView; private TextView headerContentView, footerContentView; private RefreshMessage refreshMessage; private ObjectAnimator rotateAnimator; private OnRefreshListener onRefreshListener = new OnRefreshListener() { @Override public void onPullRefreshing() {} @Override public void onLoadRefreshing() {} }; public RefreshListView(Context context) { super(context); refreshMessage = new RefreshMessage(); initView(); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); // 支持attr自定义属性 refreshMessage = new RefreshMessage(); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RefreshListView); if (array.hasValue(R.styleable.RefreshListView_refresh_pull_icon)) { refreshMessage.pullIcon = array.getDrawable(R.styleable.RefreshListView_refresh_pull_icon); } if (array.hasValue(R.styleable.RefreshListView_refresh_pull_tip)) { refreshMessage.pullTip = array.getString(R.styleable.RefreshListView_refresh_pull_tip); } if (array.hasValue(R.styleable.RefreshListView_refresh_pull_release_refresh_tip)) { refreshMessage.pullReleaseRefreshTip = array.getString(R.styleable.RefreshListView_refresh_pull_release_refresh_tip); } if (array.hasValue(R.styleable.RefreshListView_refresh_pull_refreshing_tip)) { refreshMessage.pullRefreshingTip = array.getString(R.styleable.RefreshListView_refresh_pull_refreshing_tip); } if (array.hasValue(R.styleable.RefreshListView_refresh_load_icon)) { refreshMessage.loadIcon = array.getDrawable(R.styleable.RefreshListView_refresh_load_icon); } if (array.hasValue(R.styleable.RefreshListView_refresh_load_tip)) { refreshMessage.loadTip = array.getString(R.styleable.RefreshListView_refresh_load_tip); } if (array.hasValue(R.styleable.RefreshListView_refresh_load_release_refresh_tip)) { refreshMessage.loadReleaseRefreshTip = array.getString(R.styleable.RefreshListView_refresh_load_release_refresh_tip); } if (array.hasValue(R.styleable.RefreshListView_refresh_load_refreshing_tip)) { refreshMessage.loadRefreshingTip = array.getString(R.styleable.RefreshListView_refresh_load_refreshing_tip); } array.recycle(); initView(); } @Override protected View initHeaderView() { View headerView = LayoutInflater.from(getContext()).inflate(R.layout.header_default, null); headerIconView = (ImageView) headerView.findViewById(R.id.header_default_icon); headerContentView = (TextView) headerView.findViewById(R.id.header_default_content); return headerView; } @Override protected View initFooterView() { View footerView = LayoutInflater.from(getContext()).inflate(R.layout.footer_default, null); footerIconView = (ImageView) footerView.findViewById(R.id.footer_default_icon); footerContentView = (TextView) footerView.findViewById(R.id.footer_default_content); return footerView; } private void initView() { if (refreshMessage.pullIcon != null) { headerIconView.setImageDrawable(refreshMessage.pullIcon); } headerContentView.setText(refreshMessage.pullTip); if (refreshMessage.loadIcon != null) { footerIconView.setImageDrawable(refreshMessage.loadIcon); } footerContentView.setText(refreshMessage.loadTip); } @Override protected void onRefreshing(boolean isTop) { View refreshView = isTop ? headerIconView : footerIconView; startRotateAnimation(refreshView); if (isTop) { onRefreshListener.onPullRefreshing(); headerContentView.setText(refreshMessage.pullRefreshingTip); } else { onRefreshListener.onLoadRefreshing(); footerContentView.setText(refreshMessage.loadRefreshingTip); } } @Override protected void onProgress(boolean isTop, int currentHeight, int viewHeight) { if (isTop) { String headerContent = currentHeight >= viewHeight ? refreshMessage.pullReleaseRefreshTip : refreshMessage.pullTip; headerContentView.setText(headerContent); rotateIcon(headerIconView, 360 * (currentHeight % viewHeight) / viewHeight); } else { String footerContent = currentHeight >= viewHeight ? refreshMessage.loadReleaseRefreshTip : refreshMessage.loadTip; footerContentView.setText(footerContent); rotateIcon(footerIconView, 360 * (currentHeight % viewHeight) / viewHeight); } endRotateAnimation(); } @Override protected void onComplete(boolean isTop) { endRotateAnimation(); } private void startRotateAnimation(View view) { rotateAnimator = ObjectAnimator.ofFloat(view, "rotation", 0, 359.0f); rotateAnimator.setDuration(500); rotateAnimator.setRepeatCount(-1); rotateAnimator.setInterpolator(new LinearInterpolator()); rotateAnimator.start(); } private void endRotateAnimation() { if (rotateAnimator != null) { rotateAnimator.end(); rotateAnimator = null; } } public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.onRefreshListener = onRefreshListener; } public interface OnRefreshListener { void onPullRefreshing(); void onLoadRefreshing(); } private void rotateIcon(ImageView imageView, int angle) { imageView.setPivotX(imageView.getWidth() / 2); imageView.setPivotY(imageView.getHeight() / 2); imageView.setRotation(angle); } public void setPullTip(String pullTip) { refreshMessage.pullTip = pullTip; } public void setPullReleaseRefreshTip(String pullReleaseRefreshTip) { refreshMessage.pullReleaseRefreshTip = pullReleaseRefreshTip; } public void setPullRefreshingTip(String pullRefreshingTip) { refreshMessage.pullRefreshingTip = pullRefreshingTip; } public void setPullIcon(int pullIcon) { headerIconView.setImageResource(pullIcon); } public void setLoadTip(String loadTip) { refreshMessage.loadTip = loadTip; } public void setLoadReleaseRefreshTip(String loadReleaseRefreshTip) { refreshMessage.loadReleaseRefreshTip = loadReleaseRefreshTip; } public void setLoadRefreshingTip(String loadRefreshingTip) { refreshMessage.loadRefreshingTip = loadRefreshingTip; } public void setLoadIcon(int loadIcon) { footerIconView.setImageResource(loadIcon); } class RefreshMessage { public String pullTip = "下拉刷新"; public String pullReleaseRefreshTip = "松手刷新"; public String pullRefreshingTip = "正在刷新"; public Drawable pullIcon = null; public String loadTip = "上拉加载"; public String loadReleaseRefreshTip = "松手加载"; public String loadRefreshingTip = "正在加载"; public Drawable loadIcon = null; } }
只要模仿RefreshListView,继承AbsRefreshListView,就很轻松实现自己的上拉刷新下拉加载的ListView。
代码已经上传到我的github:https://github.com/JohanMan/viewtoolkit
因为RefreshListView是已经实现了上拉刷新下拉加载了,可以直接使用。
使用实例:
xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:refresh_view="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.johan.viewtoolkit.MainActivity"> <com.johan.library.viewtoolkit.refreshlistview.RefreshListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:dividerHeight="1px" android:divider="#cccccc" refresh_view:refresh_pull_icon="@drawable/my_refresh_icon" refresh_view:refresh_pull_tip="下拉刷新哦" refresh_view:refresh_pull_release_refresh_tip="松手刷新哦" refresh_view:refresh_pull_refreshing_tip="正在刷新哈" /> </LinearLayout>
activity
final RefreshListView listView = (RefreshListView) findViewById(R.id.list_view); listView.setAdapter(adapter); listView.setLoadIcon(R.drawable.my_refresh_icon); listView.setLoadTip("上拉加载哦"); listView.setOnRefreshListener(new RefreshListView.OnRefreshListener() { @Override public void onPullRefreshing() { // 模拟延时3秒 listView.postDelayed(new Runnable() { @Override public void run() { listView.completeRefresh(); } }, 3000); } @Override public void onLoadRefreshing() { // 模拟延时3秒 listView.postDelayed(new Runnable() { @Override public void run() { listView.completeRefresh(); } }, 3000); } });
相关文章推荐
- Android自定义View之快速实现下拉刷新, 点击加载更多ListView
- 【Android - 自定义View】之自定义可下拉刷新或上拉加载的ListView
- Android开发之细说ListView上拉加载,下拉刷新过程
- Android RecyclerView (四)总结(一)-(三)并且实现下拉刷新数据,上拉加载数据功能
- Android开发-UI控件:为ListView,GirdView,etc...添加系统自带的下拉刷新功能
- Android自定义listview布局实现上拉加载下拉刷新功能
- Android上拉刷新下拉加载XRefreshView集成以及自定义GIF动画
- Android listview、ScrollView等布局下拉加载和上拉刷新
- RecyclerView使用详解一代替ListView(点击事件,添加头布局,上拉刷新下拉加载)
- Android开发之RecyclerView的上拉刷新和下拉加载
- Android通过XListView实现上拉加载下拉刷新功能
- Android——终极版上拉刷新下拉加载(兼容ScrlooView、ListView、GridView以及各类布局)
- Android——Xlistview上拉刷新下拉加载
- Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多
- Android RecyclerView下拉刷新 & 上拉加载更多
- 横向滑动菜单+上拉刷新+图片轮播+listView(TextView,GridView)+下拉加载
- android 自定义listview——实现上拉刷新下拉加载的功能
- Android-----XlistView上拉刷新下拉加载更多
- Android ListView下拉与上拉刷新加载更多(一)
- Android MVP设计框架模板 之 漂亮ListView上拉刷新下拉加载更多