Android自定义下拉刷新控件RefreshableView
这是在了解下拉刷新功能原理下的产物,下拉刷新可以说是国产app里面必有的功能,连google都为此出了swiperefreshlayout,一种md风格的下拉刷新。
不过,md风格在国内似乎很是艰难,不单单是国内系统主流仍是4.4的原因,也有用户习惯的问题,扯的有点多了,在看了许多博客之后,我突然想写一个能仿照 swiperefreshlayout 的兼容所有控件的下拉刷新,不单单只是 listview,希望它也可以包容普通的view和scrollview,经过两天的奋斗,终于搞定了,因为我的目的只是想要下拉刷新,所以功能很少,不过,如果能把下拉刷新搞定了,其它的功能,就可以慢慢堆砌起来了。
新系统的原因,无法给出合适的gif,只能截图了:
第一张图片展示的是textview:
第二章图片展示的是listview:
第三章图片展示的是scrollview:
基本上这就是我测试的成功的控件,兼容普通的view是最简单的,复杂一点的就是listview和scrollview。
思路:我的思路和大部分博客都是一样的,自定义一个viewgroup,然后将包含的要拖拽的控件的touch事件交给 refreshableview 处理,动态改变 headview 的 margintop 的值,以上。当然,这其中会有一些细节要注意,比如 ontouch 方法的返回值的处理,还有子 view 的 margintop 值的处理。
源码
先是headview的布局文件:
<?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"> <imageview android:id="@+id/imageview_down" android:layout_width="14dp" android:layout_height="24dp" android:padding="2dp" android:src="@drawable/svg_down" /> <progressbar android:visibility="gone" android:id="@+id/progressbar" style="?android:attr/progressbarstyle" android:layout_width="20dp" android:layout_height="20dp" android:progressdrawable="@drawable/progressbar" /> <textview android:padding="15dp" android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pull_to_refresh" android:textsize="16sp" /> </linearlayout>
接下来就是至关重要的类 refreshableview:
package com.pull2refresh; import android.animation.objectanimator; import android.animation.valueanimator; import android.annotation.targetapi; import android.content.context; import android.os.build; import android.util.attributeset; import android.util.log; import android.view.layoutinflater; import android.view.motionevent; import android.view.view; import android.view.viewconfiguration; import android.view.viewgroup; import android.view.animation.rotateanimation; import android.widget.imageview; import android.widget.linearlayout; import android.widget.progressbar; import android.widget.textview; import shike.xianrou.com.pull2refresh.r; /** * created by cjh on 16-9-6. */ public class refreshableview extends linearlayout implements view.ontouchlistener { private static final string tag = "refreshableview"; private static final int refreshing = 0;//正在刷新 private static final int original = refreshing + 1;//初始状态 private static final int release_to_refreshing = original + 1;//释放即将刷新的状态 private int current_status = original;//当前最新状态 private linearlayout headview;//刷新layout private textview textview;//刷新layout中的文字提示 private imageview imageview;//刷新layout中的箭头 private progressbar progressbar;//刷新layout中的进度条 private view view;//手指控制的下拉的view private int hideheight;//刷新layout要隐藏的高度 private boolean isablepull;//是否可以下拉,例如当 current_status = refreshiing 时是不可以下拉拖拽的 private float ydown;//手指按下的坐标 private int touchslop = viewconfiguration.get(getcontext()).getscaledtouchslop();//界限值,防止手指误触,过于灵敏 private boolean firstlayout = true;//第一次调用onlayout的时候置为false private int maxmargintop;//刷新layout能拉下的最大距离 private marginlayoutparams marginlayoutparams;//刷新layout的marginlayoutparams private string pull_to_refresh = "下拉可以刷新"; private string release_to_refresh = "释放立即刷新"; private string refreshing = "正在刷新…"; private int original_margin = 0;//针对下拉的view存在margintop这中特殊值的处理 public interface pulltorefreshlistener { void onrefresh(); } private pulltorefreshlistener pulltorefreshlistener; public void addpulltorefreshlistener(pulltorefreshlistener pulltorefreshlistener) { this.pulltorefreshlistener = pulltorefreshlistener; } public refreshableview(context context) { super(context); init(); } public refreshableview(context context, attributeset attrs) { super(context, attrs); init(); } public refreshableview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); init(); } @targetapi(build.version_codes.lollipop) public refreshableview(context context, attributeset attrs, int defstyleattr, int defstyleres) { super(context, attrs, defstyleattr, defstyleres); init(); } private void init() { headview = (linearlayout) layoutinflater.from(getcontext()).inflate(r.layout.refresh_layout, null, true); imageview = (imageview) headview.findviewbyid(r.id.imageview_down); progressbar = (progressbar) headview.findviewbyid(r.id.progressbar); textview = (textview) headview.findviewbyid(r.id.textview); progressbar.setvisibility(view.gone); setorientation(vertical); addview(headview, 0); } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { super.onlayout(changed, l, t, r, b); log.d(tag, "onlayout"); if (changed && firstlayout) { //将view的touch时间的处理交给refreshableview去处理 view = getchildat(1); view.setontouchlistener(this); //刷新layout的 margintop 的最大值设为刷新头的高度 maxmargintop = headview.getheight(); //要将控件完全隐藏起来,那么隐藏的高度就设置为控件的高度 hideheight = -headview.getheight(); marginlayoutparams = (marginlayoutparams) headview.getlayoutparams(); marginlayoutparams.topmargin = hideheight; headview.setlayoutparams(marginlayoutparams); //这里必须将firstlayout设置为false,否则在处理touch是件的过程中,headview在怎么调用setlayoutparams都会被置为初始的隐藏状态 firstlayout = false; //如果子view是一个viewgroup 那么就需要计算出子view的margintop的值,因为如果margintop不为0,那么子view的y轴坐标和父view的坐标是不一样的 if (view instanceof viewgroup) { int[] childlocations = new int[2]; int[] viewlocations = new int[2]; view.getlocationonscreen(viewlocations); ((viewgroup) view).getchildat(0).getlocationonscreen(childlocations); original_margin = childlocations[1] - viewlocations[1]; log.d(tag, "onlayout viewlocations[1] " + viewlocations[1]); log.d(tag, "onlayout locations[1] " + childlocations[1]); log.d(tag, "onlayout original_margin " + original_margin); } } } @override public boolean ontouch(view view, motionevent motionevent) { if (pulltorefreshlistener != null) { isablepull(); if (isablepull) { switch (motionevent.getaction()) { case motionevent.action_down: ydown = motionevent.getrawy(); break; case motionevent.action_move: float ymove = motionevent.getrawy(); float distance = ymove - ydown; //如果手势是向上的,并且手势在y轴的移动距离小于界限值,那么就不处理 if (distance < 0 || math.abs(distance) < touchslop) return false; //margintop的距离是手势距离的1/2,形成费力延迟的效果 marginlayoutparams.topmargin = (int) (distance / 2 + hideheight); log.d(tag, "topmargin " + marginlayoutparams.topmargin); //如果大于最大的margintop的值的时候,就将值置为 maxmargintop if (marginlayoutparams.topmargin >= maxmargintop) marginlayoutparams.topmargin = maxmargintop; if (marginlayoutparams.topmargin >= 0) { //当刷新头完全显示的时候,改变状态,置为 释放刷新的状态 if (current_status != release_to_refreshing) { rotate(0, 180); textview.settext(release_to_refresh); } current_status = release_to_refreshing; } else { //否则就置为初始状态 if (current_status != original) { rotate(180, 360); textview.settext(pull_to_refresh); } current_status = original; } headview.setlayoutparams(marginlayoutparams); break; case motionevent.action_cancel: case motionevent.action_up: float yup = motionevent.getrawy(); float dis = yup - ydown; if (dis > 0 && math.abs(dis) > touchslop) switch (current_status) { //释放刷新 case release_to_refreshing: animatemargintop(marginlayoutparams.topmargin, 0); imageview.clearanimation(); imageview.setvisibility(view.gone); progressbar.setvisibility(view.visible); textview.settext(refreshing); pulltorefreshlistener.onrefresh(); break; //初始化 case original: reset(); //从当前的margintop的值,转变到隐藏所需的 margintop animatemargintop(marginlayoutparams.topmargin, hideheight); break; } else return false; break; } return true; } } return false; } /** * 判断是否可以下拉 * * @return */ public boolean isablepull() { //统一判断,其实主要是对于view是普通view的判断 if (current_status != refreshing) isablepull = true; else isablepull = false; if (view instanceof viewgroup) { isablepull = false; view childview = ((viewgroup) view).getchildat(0); int[] viewlocations = new int[2]; int[] childviewlocations = new int[2]; view.getlocationonscreen(viewlocations); childview.getlocationonscreen(childviewlocations); //这一步中的 original_margin 至关重要,就是用来兼容 子view 有margintop属性的值,当childview 的y轴坐标 和 计算出了 margin 值后的父view坐标相等时,说明此时处于可下拉的状态 if (viewlocations[1] + original_margin == childviewlocations[1]) isablepull = true; else isablepull = false; } return isablepull; } private void rotate(int from, int to) { rotateanimation rotateanimation = new rotateanimation(from, to, imageview.getwidth() / 2, imageview.getheight() / 2); rotateanimation.setduration(100); rotateanimation.setfillafter(true); imageview.startanimation(rotateanimation); } private void animatemargintop(int from, int to) { objectanimator objectanimator = objectanimator.ofint(headview, "cjh", from, to); objectanimator.setduration(300); objectanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator valueanimator) { int margin = (int) valueanimator.getanimatedvalue(); marginlayoutparams.topmargin = margin; headview.setlayoutparams(marginlayoutparams); } }); objectanimator.start(); } public void complete() { animatemargintop(0, hideheight); reset(); } private void reset() { rotate(180, 360); textview.settext(pull_to_refresh); imageview.setvisibility(view.visible); progressbar.setvisibility(view.gone); } }
使用:
<com.pull2refresh.refreshableview android:id="@+id/refreshableview" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <com.pull2refresh.mtextview android:id="@+id/mtextview" android:layout_width="match_parent" android:layout_height="100dp" android:background="@color/colorprimary" android:gravity="center" android:text="hello world!" android:textsize="22sp" /> </com.pull2refresh.refreshableview>
... refreshableview.addpulltorefreshlistener(new refreshableview.pulltorefreshlistener() { @override public void onrefresh() { setdata(); } }); ... private void setdata() { objectanimator objectanimator = objectanimator.offloat(textview, "text", 1f, 100f); objectanimator.setduration(3000); objectanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator valueanimator) { textview.settext((float) valueanimator.getanimatedvalue()); } }); objectanimator.addlistener(new animator.animatorlistener() { @override public void onanimationstart(animator animator) { } @override public void onanimationend(animator animator) { refreshableview.complete(); } @override public void onanimationcancel(animator animator) { } @override public void onanimationrepeat(animator animator) { } }); objectanimator.start(); }
在我自定义的 refreshableview 中,如果不设置下拉的监听,就没有下拉的效果,也就是不支持下拉
源码下载:android下拉刷新控件
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
android自定义开关控件-SlideSwitch的实例
-
Android自定义下拉刷新控件RefreshableView
-
Android仿XListView支持下拉刷新和上划加载更多的自定义RecyclerView
-
浅析Android手机卫士自定义控件的属性
-
Android自定义listview布局实现上拉加载下拉刷新功能
-
【android自定义控件】button样式自定义<二>
-
Android自定义电量控件中过度绘制与思考
-
Android 笔记 自定义View,让用户觉得熟悉的控件,才是一个好的控件 (六)
-
Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器
-
Android高级控件系列二之第三方控件PullToRefreshListView下拉刷新的使用