listview下拉刷新
Posted 翻滚的咸鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了listview下拉刷新相关的知识,希望对你有一定的参考价值。
x写控件挺麻烦的,因为有很多细节要处理好,列表控件使用太频繁了,网上也各种自定义的方法,一般的listview自定义肯定会联想到加个头部,然后监听事件加动画,其实方式很多种,今天记录的方式是另外一种方式,个人觉得复用性更强,写好了可以通用,思路就是在不动原列表控件的情况下给它上面套个壳,然后让壳来操作刷新显示,这样的话是不是以后要用的时候加个壳就行了,而且我可以基本上不管里面的控件是什么。
下载地址: http://download.csdn.net/detail/u010864175/9805339
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="cn.com.listwebview.demo.MainActivity"> <cn.com.listwebview.demo.ListRefreshLayout android:id="@+id/refreshLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f4a148" android:orientation="vertical" > <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#dff19a" android:divider="#595958"/> <!--<android.support.v7.widget.RecyclerView--> <!--android:id="@+id/recyclerview"--> <!--android:divider="#ffff0000"--> <!--android:dividerHeight="10dp"--> <!--android:background="#dff19a"--> <!--android:layout_width="match_parent"--> <!--android:layout_height="match_parent" />--> </cn.com.listwebview.demo.ListRefreshLayout> </LinearLayout>
MainActivity很简单,啥都不用做,按着 RecyclerView或者listview加载数据的方式加载就好了
MainActivity
重点都在这个壳里了,注释非常详细,因为壳是父容器,所以不用传递视图进去,这样也会产生依赖关系,界面开始显示listview或者recyclerview列表,监听时间滑动,然后判断滑动的程度,距离,
是否在刷新中等等,接着通过更新MarginTop来展示头部的刷新,而头部都是在加载事件里面动态添加进去的,因为效果都是在壳里实现的,所以不影响列表控件添加头部脚部,以后也可以更换配置头部样
式,这样xml里也不用特地的为这个控件去写特定的布局,不然这种做法就失去了意义了,和直接添加头部也没什么大区别了,显示出了头部根据你滑动的状态来控制动画,然后已经运行了动画就不能让它
在更新top了,不然重复刷新,接着抬起操作,此处我直接设置的两秒完成,实际是访问回调接口停止的。
ListRefreshLayout
package cn.com.listwebview.demo; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.support.v4.view.ViewCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import java.lang.reflect.Field; import cn.com.listwebview.demo.utils.DensityUtil; /** * Created by LiuZhen on 2017/3/24. */ public class ListRefreshLayout extends FrameLayout{ private String TAG = "TwinklingRefreshLayout"; private int downY;// 按下时y轴的偏移量 private final static float RATIO = 3f; //头部的高度 protected int mHeadHeight; //头部layout protected FrameLayout mHeadLayout;//头部父容器 private HeaderView mHeadView;//头部 protected FrameLayout mFootLayout;//头部父容器 private ImageView ivArrow_left,ivArrow_right; //头布局的剪头 private ProgressBar mProgressBar; // 底布局的进度条 private Animation upAnimation;// 向上旋转的动画 private Animation downAnimation;// 向下旋转的动画 private final int DOWN_PULL_REFRESH = 0;// 下拉刷新状态 private final int RELEASE_REFRESH = 1;// 松开刷新 private final int REFRESHING = 2;// 正在刷新中 private final int END = 3;// 正在刷新中 private int currentState = DOWN_PULL_REFRESH;// 头布局的状态: 默认为下拉刷新状态 private View list;//子节点中的listview视图 private LayoutParams listParam,footParam;//用于控制下拉动画展示 private boolean isLoadingMore = false;// 是否进入加载状态,防止多次重复的启动 private boolean isStart = false;//表示正在加载刷新中,还没停止 private boolean isTop = false,isBottom = false; private int mTouchSlop; public ListRefreshLayout(Context context) { this(context, null, 0); } public ListRefreshLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ListRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwinklingRefreshLayout, defStyleAttr, 0); try { mHeadHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_head_height, DensityUtil.dp2px(context, 40)); } finally { a.recycle(); } mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); addHeader(); init(); } private void addHeader() { FrameLayout headViewLayout = new FrameLayout(getContext()); LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight); this.addView(headViewLayout,layoutParams); mHeadLayout = headViewLayout; } private void init(){ initAnimation(); } @Override protected void onFinishInflate() {//布局加载成xml时触发 super.onFinishInflate(); if (mHeadView == null) setHeaderView(new HeaderView(getContext())); setFootView(); if (list == null) { list = getChildAt(1); listParam = (LayoutParams) list.getLayoutParams(); list.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return onFingerTouch(event); } }); //下面是原先的方法,比较简单,也可以达到同样的效果,思路也是需要判断类型,毕竟每个列表控件的判断top bottom方式不同,只是此处需要用具体的list对象来监听,所以采用另外的方式来判断 // list.setOnScrollListener(new AbsListView.OnScrollListener() { // @Override // public void onScrollStateChanged(AbsListView view, int scrollState) { // // } // // @Override // public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // isTop = false; // isBottom = false; // //判断顶部底部 // if (firstVisibleItem == 0) { // Log.d(TAG, "滚动到顶部"); // isTop = true; // isBottom = false; // } else if ((firstVisibleItem + visibleItemCount) == totalItemCount) { // Log.d(TAG, "滚动到底部"); // isTop = false; // isBottom = true; // } // } // }); } } /** * 设置头部View */ public void setHeaderView(final HeaderView headerView) { if (headerView != null) { post(new Runnable() { @Override public void run() { mHeadLayout.removeAllViewsInLayout(); mHeadLayout.addView(headerView.getView()); View view = LayoutInflater.from(getContext()).inflate(R.layout.item_head_progress,null); // mProgressBar = (ProgressBar) view.findViewById(R.id.pb_listview_header); ivArrow_left = (ImageView) view.findViewById(R.id.iv_listview_header_arrow_left) ; ivArrow_right = (ImageView) view.findViewById(R.id.iv_listview_header_arrow_right) ; mHeadLayout.addView(view); } }); mHeadView = headerView; } } /** * 设置尾部View */ public void setFootView() { footParam = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight); FrameLayout footViewLayout = new FrameLayout(getContext());//底部布局 this.addView(footViewLayout,footParam); this.mFootLayout = footViewLayout; mFootLayout.setBackgroundColor(Color.BLACK); footParam.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; footParam.setMargins(0,0,0,-mHeadHeight); mProgressBar = new ProgressBar(getContext(),null,android.R.attr.progressBarStyleSmallInverse); mFootLayout.addView(mProgressBar); } public boolean onFingerTouch(MotionEvent ev) { isTop = isViewToTop(list,mTouchSlop); isBottom = isViewToBottom(list,mTouchSlop); // Log.e(TAG,"isTop "+isTop+" isBottom "+isBottom); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN : currentState = REFRESHING; downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE : if (!isTop && !isBottom)//没有到顶,无需计算操作 break; int moveY = (int) ev.getY(); int diff = (int) (((float)moveY - (float)downY) / RATIO); // int paddingTop = -mHeadLayout.getHeight() + diff; int paddingTop = diff; if (diff>0 && isTop) { //向下滑动多少后开始启动刷新 if (paddingTop >= 200 && currentState == DOWN_PULL_REFRESH) { // 完全显示了. // Log.i(TAG, "松开刷新 RELEASE_REFRESH"); currentState = RELEASE_REFRESH; refreshHeaderView(); start(); } else if (currentState == REFRESHING) { // 没有显示完全 // Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH"); currentState = DOWN_PULL_REFRESH; refreshHeaderView(); } if (paddingTop <= 400 && !isStart) {//已经处于运行刷新状态的时候禁止设置 listParam.setMargins(0, paddingTop, 0, 0); list.setLayoutParams(listParam); } }else if (isBottom){ //限制上滑时不能超过底部的宽度,不然会超出边界 if (paddingTop <= -50 && paddingTop >= -mHeadHeight && !isStart) {//已经处于运行刷新状态的时候禁止设置 listParam.setMargins(0, 0, 0, -paddingTop); footParam.setMargins(0,0,0,-paddingTop-mHeadHeight); list.setLayoutParams(listParam); } if (paddingTop <= -mHeadHeight) isLoadingMore = true; } // Log.i(TAG,"paddingTop "+paddingTop); break; case MotionEvent.ACTION_UP : if (isLoadingMore){ isLoadingMore = false; postDelayed(new Runnable() { @Override public void run() { // Log.i(TAG, "停止 END"); // currentState = END; refreshHeaderView(); listParam.setMargins(0, 0, 0, 0); footParam.setMargins(0,0,0,-mHeadHeight); list.setLayoutParams(listParam); stop(); } },2000); }else{ if (!isStart){ // 隐藏头布局 listParam.setMargins(0, 0,0,0); footParam.setMargins(0,0,0,-mHeadHeight); list.setLayoutParams(listParam); } } // Log.i(TAG, "松开 REFRESHING"); currentState = REFRESHING; break; default : break; } return super.onTouchEvent(ev); } /** * 初始化动画 */ private void initAnimation() { /* * Animation.RELATIVE_TO_SELF 相对于自身的动画 * Animation.RELATIVE_TO_PARENT 相对于父控件的动画 * 0.5f,表示在控件自身的 x,y的中点坐标处,为动画的中心。 * * 设置动画的变化速率 * setInterpolator(newAccelerateDecelerateInterpolator()):先加速,后减速 * setInterpolator(newAccelerateInterpolator()):加速 * setInterpolator(newDecelerateInterpolator()):减速 * setInterpolator(new CycleInterpolator()):动画循环播放特定次数,速率改变沿着正弦曲线 * setInterpolator(new LinearInterpolator()):匀速 */ upAnimation = new RotateAnimation(0f, -180f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); upAnimation.setInterpolator(new LinearInterpolator()); upAnimation.setDuration(700); upAnimation.setFillAfter(true); // 动画结束后, 停留在结束的位置上 downAnimation = new RotateAnimation(-180f, -360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); downAnimation.setInterpolator(new LinearInterpolator());//这句话可以不写,默认匀速 downAnimation.setDuration(700); downAnimation.setFillAfter(true); // 动画结束后, 停留在结束的位置上 } /** * 根据currentState刷新头布局的状态 */ private void refreshHeaderView() { switch (currentState) { case DOWN_PULL_REFRESH : // 下拉刷新状态 // tvState.setText("下拉刷新"); ivArrow_left.startAnimation(downAnimation); // 执行向下旋转 ivArrow_right.startAnimation(downAnimation); // 执行向下旋转 break; case RELEASE_REFRESH : // 松开刷新状态 // tvState.setText("松开刷新"); ivArrow_left.startAnimation(upAnimation);// 执行向上旋转 ivArrow_right.startAnimation(upAnimation);// 执行向上旋转 break; case REFRESHING : // 正在刷新中状态 ivArrow_left.clearAnimation(); ivArrow_right.clearAnimation(); // tvState.setText("正在刷新中..."); break; default : break; } } public static boolean isViewToTop(View view,int mTouchSlop){ if (view instanceof AbsListView) return isAbsListViewToTop((AbsListView) view); if (view instanceof RecyclerView) return isRecyclerViewToTop((RecyclerView) view); return (view != null && Math.abs(view.getScrollY()) <= 2 * mTouchSlop); } public static boolean isViewToBottom(View view,int mTouchSlop){ if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view); if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view); // if (view instanceof WebView) return isWebViewToBottom((WebView) view,mTouchSlop); // if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view); return false; } public static boolean isAbsListViewToTop(AbsListView absListView) { if (absListView != null) { int firstChildTop = 0; if (absListView.getChildCount() > 0) { // 如果AdapterView的子控件数量不为0,获取第一个子控件的top firstChildTop = absListView.getChildAt(0).getTop() - absListView.getPaddingTop(); } if (absListView.getFirstVisiblePosition() == 0 && firstChildTop == 0) { return true; } } return false; } public static boolean isRecyclerViewToTop(RecyclerView recyclerView) { if (recyclerView != null) { RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager == null) { return true; } if (manager.getItemCount() == 0) { return true; } if (manager instanceof LinearLayoutManager) { LinearLayoutManager layoutManager = (LinearLayoutManager) manager; int firstChildTop = 0; if (recyclerView.getChildCount() > 0) { // 处理item高度超过一屏幕时的情况 View firstVisibleChild = recyclerView.getChildAt(0); if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) { if (android.os.Build.VERSION.SDK_INT < 14) { return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0); } else { return !ViewCompat.canScrollVertically(recyclerView, -1); } } // 如果RecyclerView的子控件数量不为0,获取第一个子控件的top // 解决item的topMargin不为0时不能触发下拉刷新 View firstChild = recyclerView.getChildAt(0); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams(); firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop(); } if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) { return true; } } else if (manager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager; int[] out = layoutManager.findFirstCompletelyVisibleItemPositions(null); if (out[0] < 1) { return true; } } } return false; } /** * 通过反射获取RecyclerView的item的topInset * * @param layoutParams * @return */ private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) { try { Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets"); field.setAccessible(true); // 开发者自定义的滚动监听器 Rect decorInsets = (Rect) field.get(layoutParams); return decorInsets.top; } catch (Exception e) { e.printStackTrace(); } return 0; } public static boolean isAbsListViewToBottom(AbsListView absListView) { if (absListView != null && absListView.getAdapter() != null && absListView.getChildCount() > 0 && absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1) { View lastChild = absListView.getChildAt(absListView.getChildCount() - 1); return lastChild.getBottom() <= absListView.getMeasuredHeight(); } return false; } public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) { if (recyclerView != null) { RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager == null || manager.getItemCount() == 0) { return false; } if (manager instanceof LinearLayoutManager) { // 处理item高度超过一屏幕时的情况 View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1); if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) { if (android.os.Build.VERSION.SDK_INT < 14) { return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0); } else { return !ViewCompat.canScrollVertically(recyclerView, 1); } } LinearLayoutManager layoutManager = (LinearLayoutManager) manager; if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) { return true; } } else if (manager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager; int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null); int lastPosition = layoutManager.getItemCount() - 1; for (int position : out) { if (position == lastPosition) { return true; } } } } return false; } public void start(){ isLoadingMore = true; isStart = true; mHeadView.onPullingDown(0); mHeadView.startAnim(); } public void stop(){ isLoadingMore = false; isStart = false; mHeadView.reset(); } }
有了头部还得要漂亮的动画啊,增强体验,动画就随自己配置了,本身控件的动画就是两个箭头我这里是加了个自定义的视图来做动画,绘制了几个圆圈,可以根据自己的需要调制,毕竟头部是一个小的容器布局,展示的头部也都是在这个布局里面,所以可以任意搭配。
底部就简单的以同样的思路来做的了,同样是通过滑动上拉来展示刷新的底部和启动动画(个人觉得滑动后加载体验好点,不太喜欢直接滑到顶就显示加载了),底部实现简单了点,就添加了一个底部的布局(这里添加是加载好xml布局后在添加的,因为是动态布局会添加在布局上面),然后里面加了个简单的progress。
HeaderView
package cn.com.listwebview.demo; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import android.view.animation.DecelerateInterpolator; import cn.com.listwebview.demo.utils.DensityUtil; /** * Created by LiuZhen on 2017/3/28. */ public class HeaderView extends View { private Paint mPath; ValueAnimator animator1, animator2; private float r; float fraction1; floatAndroid中ListView下拉刷新的实现