Android自定义下拉刷新控件for RecyclerView详情
程序员文章站
2022-04-08 11:36:48
1.下拉刷新肯定得用自己的 不用说 或者可以灵活运用别人的能满足各种需求 也凑合
2.而且 设计思路肯定得是包裹式的 即自定义一个view group包裹recycler view 这样解耦
这篇文...
1.下拉刷新肯定得用自己的 不用说 或者可以灵活运用别人的能满足各种需求 也凑合
2.而且 设计思路肯定得是包裹式的 即自定义一个view group包裹recycler view 这样解耦
这篇文章只是提供制作方法,在这里可以轻易的学会;但是想直接用恐怕不行,可以找别人开源的,这里的下拉刷新控件由于设计时间尚短 还很丑
效果还很初级,不过该有的都有了,所以直接用可能读者还需要改动下(比如状态恢复的时候,还有一个判断,如果拉出来一点点,直接恢复;拉出来很多,就执行刷新,还要有一个延时动画,不过这些都不是什么难事)
需要解决几个问题
第一个问题
怎么实现下拉刷新,在于如何把header弄到屏幕之外
绿色的是手机屏幕,如何把header弄到外面就要:
linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams(); params.topmargin = -headerheight; pullview.setlayoutparams(params);headerheight是在onwindowsfocuschanged中获取的控件高度,topmargin就是距离父控件的距离,负数自然就到了屏幕之外
当然这个难题可以通过scrollview来实现,不过我猜scrollview本质也是这样实现的
第二个问题
逻辑问题,什么时候事件给recyclerview,什么时候给下拉头(即recycler view的父view group ,我们要自定义的控件)
if (state == 下拉) { 拦截 执行下拉:修改lp up时,进入松开复原状态 } if (state == 松开复原中) { 全体阻塞 执行复原:属性动画复原 属性动画执行完毕,进入无状态 } if (无状态) { if (rv位移为0 && 下拉手势) { 进入下拉状态 } else { 不拦截 } }
第三个问题
逻辑转化为代码,由于你是父容器,所以你需要再onintercept中拦截事件,ontouchevent中处理事件,这两部分的事件各有耦合,所以算是个难点。不过代码注释的很清楚,可以轻松看懂
第四个问题
手松开后还有一个恢复过程,只需要弄一个value animator即可,监听topmargin的变化。
上代码,注释的一清二楚
public class pulltorefreshlayout extends linearlayout { private static final string tag = "xbh"; private view pullview; private recyclerview rv; private int headerheight; private int headerbarheight;//有内容的部分 private static final float pull_rate = 0.4f; private int recyclerviewoffset; public pulltorefreshlayout(context context, attributeset attrs) { super(context, attrs); //获取header view,并把它添加到recycler view之前 pullview = layoutinflater.from(context).inflate(r.layout.pull_refresh_view, null, false); addview(pullview, 0); //设置linearlayout为垂直 setorientation(vertical); } @override public void onwindowfocuschanged(boolean haswindowfocus) { super.onwindowfocuschanged(haswindowfocus); //通过this.getchildat来获取recycler view rv = (recyclerview) getchildat(1); //获取下拉view的高度(也包括上面的空白部分) headerheight = pullview.getheight(); //获取下拉view不含空白部分的高度 headerbarheight = ((viewgroup)pullview).getchildat(1).getheight(); //初始,设置header view反向偏移一段距离,为了让他一开始处于隐藏状态 linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams(); params.topmargin = -headerheight; pullview.setlayoutparams(params); //通过recycler view的这个监听,实时获取recycler view的下滑位移距离 rv.setonscrolllistener(new recyclerview.onscrolllistener() { @override public void onscrolled(recyclerview rv, int dx, int dy) { super.onscrolled(rv, dx, dy); recyclerviewoffset = rv.computeverticalscrolloffset(); } }); } int state = none; private static final int pull_down = 1; private static final int recover = 2; private static final int none = 3; int downy = 0; @override public boolean onintercepttouchevent(motionevent ev) { boolean intercepted = false; int y = (int) ev.gety(); switch (ev.getaction()) { case motionevent.action_down: intercepted = false; downy = y;//获取按下时候的纵坐标 break; case motionevent.action_move: if (state == pull_down) { intercepted = true;//如果是正在下拉状态、恢复状态,拦截! } if (state == recover) { intercepted = true; } if (state == none) {//当recycler view位移为0,且正是下拉手势,进入正在下拉状态 if (recyclerviewoffset == 0 && y - downy > 0) { state = pull_down; intercepted = true; } else { intercepted = false; } } break; } return intercepted; } @override public boolean ontouchevent(motionevent ev) { int y = (int) ev.gety(); switch (ev.getaction()) { case motionevent.action_down: downy = y; break; case motionevent.action_move: //这里获取的是鼠标下滑的距离 int verticaloffset = y - downy; //鼠标下滑的距离 乘以 pull_rate,就是header view应该下滑的距离 //因为我发现如果鼠标下滑多少,header view就下来多少,有点不太好看 linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams(); params.topmargin = -headerheight + (int) (verticaloffset * pull_rate); pullview.setlayoutparams(params); break; case motionevent.action_up: case motionevent.action_cancel: //cancel情况是手机移出屏幕,导致事件意外取消的情况,和up事件一致 if (state == pull_down) {//事件结束,从正在下拉状态 进入 恢复状态 state = recover; int verticaloffset2 = y - downy; if (verticaloffset2 > headerbarheight) { //这里就是改进的地方,不足距离,正常恢复;超过距离,延时操作 //还有一个改进的地方:我在恢复的时候,*了事件;其实应该是在恢复之前可以继续下拉的 } else { } //topmargin //start:从你当前偏移的位置开始 //end:恢复到初始位置 final int start = -headerheight + (int) (verticaloffset2 * pull_rate); final int end = -headerheight; //执行一个value动画 valueanimator animator = valueanimator.ofint(start, end); animator.setduration(500).start(); animator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { //i:这里取得start到end动态在500ms中动态变化的值 int i = (int) animation.getanimatedvalue(); linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams(); params.topmargin = i; pullview.setlayoutparams(params); //结束情况,修改标记位成:无状态 if (i == end) { state = none; } } }); } break; } return true; } }
pull fresh view
xml version="1.0" encoding="utf-8"?> <android.support.constraint.constraintlayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="275dp" android:background="@color/colorprimary" tools:layout_editor_absolutey="81dp"> <imageview android:id="@+id/imageview" android:layout_width="38dp" android:layout_height="36dp" android:layout_marginstart="32dp" android:layout_margintop="220dp" android:background="@mipmap/pull_icon" app:layout_constraintstart_tostartof="parent" app:layout_constrainttop_totopof="parent" /> <textview android:id="@+id/textview" android:layout_width="387dp" android:layout_height="69dp" android:layout_margintop="208dp" android:gravity="center" android:textcolor="#ffffff" app:layout_constraintstart_tostartof="parent" app:layout_constrainttop_totopof="parent" android:text="下拉刷新" /> <progressbar android:id="@+id/progressbar" style="?android:attr/progressbarstyle" android:layout_width="43dp" android:layout_height="41dp" android:layout_marginend="28dp" android:layout_margintop="216dp" app:layout_constraintend_toendof="parent" app:layout_constrainttop_totopof="parent" /> android.support.constraint.constraintlayout>
xml version="1.0" encoding="utf-8"?> <framelayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.test.mainactivity"> <com.example.test.pulltorefreshlayout android:id="@+id/pt" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.recyclerview android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="match_parent" /> com.example.test.pulltorefreshlayout> framelayout>
推荐阅读
-
Android RecyclerView上拉加载和下拉刷新
-
Android RecyclerView上拉加载和下拉刷新(基础版)
-
Android自定义渐变式炫酷ListView下拉刷新动画
-
Android之RecyclerView轻松实现下拉刷新和加载更多示例
-
Android RecyclerView实现下拉刷新和上拉加载更多
-
android使用Ultra-PullToRefresh实现下拉刷新自定义代码
-
Android RecyclerView上拉加载和下拉刷新
-
Android RecyclerView上拉加载和下拉刷新(基础版)
-
Android自定义渐变式炫酷ListView下拉刷新动画
-
Android RecyclerView设置下拉刷新的实现方法