Android自定义控件下拉刷新实例代码
程序员文章站
2024-03-04 15:14:59
实现效果:
图片素材:
--> 首先, 写先下拉刷新时的刷新布局 pull_to_refresh.xml:
实现效果:
图片素材:
--> 首先, 写先下拉刷新时的刷新布局 pull_to_refresh.xml:
<resources> <string name="app_name">pulltorefreshtest</string> <string name="pull_to_refresh">下拉可以刷新</string> <string name="release_to_refresh">释放立即刷新</string> <string name="refreshing">正在刷新...</string> <string name="not_updated_yet">暂未更新过</string> <string name="updated_at">上次更新于%1$s前</string> <string name="updated_just_now">刚刚更新</string> <string name="time_error">时间有问题</string> </resources> <?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pull_to_refresh_head" android:layout_width="match_parent" android:layout_height="60dp"> <linearlayout android:layout_width="200dp" android:layout_height="60dp" android:layout_centerinparent="true" android:orientation="horizontal"> <relativelayout android:layout_width="0dp" android:layout_height="60dp" android:layout_weight="3"> <imageview android:id="@+id/arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerinparent="true" android:src="@mipmap/indicator_arrow" /> <progressbar android:id="@+id/progress_bar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerinparent="true" android:visibility="gone" /> </relativelayout> <linearlayout android:layout_width="0dp" android:layout_height="60dp" android:layout_weight="12" android:orientation="vertical"> <textview android:id="@+id/description" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:gravity="center_horizontal|bottom" android:text="@string/pull_to_refresh" /> <textview android:id="@+id/updated_at" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:gravity="center_horizontal|top" android:text="@string/updated_at" /> </linearlayout> </linearlayout> </relativelayout>
strings pull_to_refresh --> 然后, 也是主要的, 自定义下拉刷新的 view (包含下拉刷新所有操作) refreshview.java: package com.dragon.android.tofreshlayout; import android.content.context; import android.content.sharedpreferences; import android.os.asynctask; import android.os.systemclock; import android.preference.preferencemanager; import android.util.attributeset; import android.view.layoutinflater; import android.view.motionevent; import android.view.view; import android.view.viewconfiguration; import android.view.animation.rotateanimation; import android.widget.imageview; import android.widget.linearlayout; import android.widget.listview; import android.widget.progressbar; import android.widget.textview; public class refreshview extends linearlayout implements view.ontouchlistener { private static final string tag = refreshview.class.getsimplename(); public enum pull_status { status_pull_to_refresh(0), // 下拉状态 status_release_to_refresh(1), // 释放立即刷新状态 status_refreshing(2), // 正在刷新状态 status_refresh_finished(3); // 刷新完成或未刷新状态 private int status; // 状态 pull_status(int value) { this.status = value; } public int getvalue() { return this.status; } } // 下拉头部回滚的速度 public static final int scroll_speed = -20; // 一分钟的毫秒值,用于判断上次的更新时间 public static final long one_minute = 60 * 1000; // 一小时的毫秒值,用于判断上次的更新时间 public static final long one_hour = 60 * one_minute; // 一天的毫秒值,用于判断上次的更新时间 public static final long one_day = 24 * one_hour; // 一月的毫秒值,用于判断上次的更新时间 public static final long one_month = 30 * one_day; // 一年的毫秒值,用于判断上次的更新时间 public static final long one_year = 12 * one_month; // 上次更新时间的字符串常量,用于作为 sharedpreferences 的键值 private static final string updated_at = "updated_at"; // 下拉刷新的回调接口 private pulltorefreshlistener mlistener; private sharedpreferences preferences; // 用于存储上次更新时间 private view header; // 下拉头的view private listview listview; // 需要去下拉刷新的listview private progressbar progressbar; // 刷新时显示的进度条 private imageview arrow; // 指示下拉和释放的箭头 private textview description; // 指示下拉和释放的文字描述 private textview updateat; // 上次更新时间的文字描述 private marginlayoutparams headerlayoutparams; // 下拉头的布局参数 private long lastupdatetime; // 上次更新时间的毫秒值 // 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,使用id来做区分 private int mid = -1; private int hideheaderheight; // 下拉头的高度 /** * 当前处理什么状态,可选值有 status_pull_to_refresh, status_release_to_refresh, status_refreshing 和 status_refresh_finished */ private pull_status currentstatus = pull_status.status_refresh_finished; // 记录上一次的状态是什么,避免进行重复操作 private pull_status laststatus = currentstatus; private float ydown; // 手指按下时的屏幕纵坐标 private int touchslop; // 在被判定为滚动之前用户手指可以移动的最大值。 private boolean loadonce; // 是否已加载过一次layout,这里onlayout中的初始化只需加载一次 private boolean abletopull; // 当前是否可以下拉,只有listview滚动到头的时候才允许下拉 /** * 下拉刷新控件的构造函数,会在运行时动态添加一个下拉头的布局 */ public refreshview(context context, attributeset attrs) { super(context, attrs); preferences = preferencemanager.getdefaultsharedpreferences(context); header = layoutinflater.from(context).inflate(r.layout.pull_to_refresh, null, true); progressbar = (progressbar) header.findviewbyid(r.id.progress_bar); arrow = (imageview) header.findviewbyid(r.id.arrow); description = (textview) header.findviewbyid(r.id.description); updateat = (textview) header.findviewbyid(r.id.updated_at); touchslop = viewconfiguration.get(context).getscaledtouchslop(); refreshupdatedatvalue(); setorientation(vertical); addview(header, 0); //log.d(tag, "refreshview constructor() getchildat(0): " + getchildat(0)); //log.d(tag, "refreshview constructor() getchildat(0): " + getchildat(1)); // listview = (listview) getchildat(1); // listview.setontouchlistener(this); } /** * 进行一些关键性的初始化操作,比如:将下拉头向上偏移进行隐藏,给 listview 注册 touch 事件 */ @override protected void onlayout(boolean changed, int l, int t, int r, int b) { super.onlayout(changed, l, t, r, b); if (changed && !loadonce) { hideheaderheight = -header.getheight(); headerlayoutparams = (marginlayoutparams) header.getlayoutparams(); headerlayoutparams.topmargin = hideheaderheight; listview = (listview) getchildat(1); //log.d(tag, "onlayout() getchildat(0): " + getchildat(0)); //log.d(tag, "onlayout() listview: " + listview); listview.setontouchlistener(this); loadonce = true; } } /** * 当 listview 被触摸时调用,其中处理了各种下拉刷新的具体逻辑 */ @override public boolean ontouch(view v, motionevent event) { setcanabletopull(event); // 判断是否可以下拉 if (abletopull) { switch (event.getaction()) { case motionevent.action_down: ydown = event.getrawy(); break; case motionevent.action_move: // 获取移动中的 y 轴的位置 float ymove = event.getrawy(); // 获取从按下到移动过程中移动的距离 int distance = (int) (ymove - ydown); // 如果手指是上滑状态,并且下拉头是完全隐藏的,就屏蔽下拉事件 if (distance <= 0 && headerlayoutparams.topmargin <= hideheaderheight) { return false; } if (distance < touchslop) { return false; } // 判断是否已经在刷新状态 if (currentstatus != pull_status.status_refreshing) { // 判断设置的 topmargin 是否 > 0, 默认初始设置为 -header.getheight() if (headerlayoutparams.topmargin > 0) { currentstatus = pull_status.status_release_to_refresh; } else { // 否则状态为下拉中的状态 currentstatus = pull_status.status_pull_to_refresh; } // 通过偏移下拉头的 topmargin 值,来实现下拉效果 headerlayoutparams.topmargin = (distance / 2) + hideheaderheight; header.setlayoutparams(headerlayoutparams); } break; case motionevent.action_up: default: if (currentstatus == pull_status.status_release_to_refresh) { // 松手时如果是释放立即刷新状态,就去调用正在刷新的任务 new refreshingtask().execute(); } else if (currentstatus == pull_status.status_pull_to_refresh) { // 松手时如果是下拉状态,就去调用隐藏下拉头的任务 new hideheadertask().execute(); } break; } // 时刻记得更新下拉头中的信息 if (currentstatus == pull_status.status_pull_to_refresh || currentstatus == pull_status.status_release_to_refresh) { updateheaderview(); // 当前正处于下拉或释放状态,要让 listview 失去焦点,否则被点击的那一项会一直处于选中状态 listview.setpressed(false); listview.setfocusable(false); listview.setfocusableintouchmode(false); laststatus = currentstatus; // 当前正处于下拉或释放状态,通过返回 true 屏蔽掉 listview 的滚动事件 return true; } } return false; } /** * 给下拉刷新控件注册一个监听器 * * @param listener 监听器的实现 * @param id 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,不同界面在注册下拉刷新监听器时一定要传入不同的 id */ public void setonrefreshlistener(pulltorefreshlistener listener, int id) { mlistener = listener; mid = id; } /** * 当所有的刷新逻辑完成后,记录调用一下,否则你的 listview 将一直处于正在刷新状态 */ public void finishrefreshing() { currentstatus = pull_status.status_refresh_finished; preferences.edit().putlong(updated_at + mid, system.currenttimemillis()).commit(); new hideheadertask().execute(); } /** * 根据当前 listview 的滚动状态来设定 {@link #abletopull} * 的值,每次都需要在 ontouch 中第一个执行,这样可以判断出当前应该是滚动 listview,还是应该进行下拉 */ private void setcanabletopull(motionevent event) { view firstchild = listview.getchildat(0); if (firstchild != null) { // 获取 listview 中第一个item的位置 int firstvisiblepos = listview.getfirstvisibleposition(); // 判断第一个子控件的 top 是否和第一个 item 位置相等 if (firstvisiblepos == 0 && firstchild.gettop() == 0) { if (!abletopull) { // getrawy() 获得的是相对屏幕 y 方向的位置 ydown = event.getrawy(); } // 如果首个元素的上边缘,距离父布局值为 0,就说明 listview 滚动到了最顶部,此时应该允许下拉刷新 abletopull = true; } else { if (headerlayoutparams.topmargin != hideheaderheight) { headerlayoutparams.topmargin = hideheaderheight; header.setlayoutparams(headerlayoutparams); } abletopull = false; } } else { // 如果 listview 中没有元素,也应该允许下拉刷新 abletopull = true; } } /** * 更新下拉头中的信息 */ private void updateheaderview() { if (laststatus != currentstatus) { if (currentstatus == pull_status.status_pull_to_refresh) { description.settext(getresources().getstring(r.string.pull_to_refresh)); arrow.setvisibility(view.visible); progressbar.setvisibility(view.gone); rotatearrow(); } else if (currentstatus == pull_status.status_release_to_refresh) { description.settext(getresources().getstring(r.string.release_to_refresh)); arrow.setvisibility(view.visible); progressbar.setvisibility(view.gone); rotatearrow(); } else if (currentstatus == pull_status.status_refreshing) { description.settext(getresources().getstring(r.string.refreshing)); progressbar.setvisibility(view.visible); arrow.clearanimation(); arrow.setvisibility(view.gone); } refreshupdatedatvalue(); } } /** * 根据当前的状态来旋转箭头 */ private void rotatearrow() { float pivotx = arrow.getwidth() / 2f; float pivoty = arrow.getheight() / 2f; float fromdegrees = 0f; float todegrees = 0f; if (currentstatus == pull_status.status_pull_to_refresh) { fromdegrees = 180f; todegrees = 360f; } else if (currentstatus == pull_status.status_release_to_refresh) { fromdegrees = 0f; todegrees = 180f; } rotateanimation animation = new rotateanimation(fromdegrees, todegrees, pivotx, pivoty); animation.setduration(100); animation.setfillafter(true); arrow.startanimation(animation); } /** * 刷新下拉头中上次更新时间的文字描述 */ private void refreshupdatedatvalue() { lastupdatetime = preferences.getlong(updated_at + mid, -1); long currenttime = system.currenttimemillis(); long timepassed = currenttime - lastupdatetime; long timeintoformat; string updateatvalue; if (lastupdatetime == -1) { updateatvalue = getresources().getstring(r.string.not_updated_yet); } else if (timepassed < 0) { updateatvalue = getresources().getstring(r.string.time_error); } else if (timepassed < one_minute) { updateatvalue = getresources().getstring(r.string.updated_just_now); } else if (timepassed < one_hour) { timeintoformat = timepassed / one_minute; string value = timeintoformat + "分钟"; updateatvalue = string.format(getresources().getstring(r.string.updated_at), value); } else if (timepassed < one_day) { timeintoformat = timepassed / one_hour; string value = timeintoformat + "小时"; updateatvalue = string.format(getresources().getstring(r.string.updated_at), value); } else if (timepassed < one_month) { timeintoformat = timepassed / one_day; string value = timeintoformat + "天"; updateatvalue = string.format(getresources().getstring(r.string.updated_at), value); } else if (timepassed < one_year) { timeintoformat = timepassed / one_month; string value = timeintoformat + "个月"; updateatvalue = string.format(getresources().getstring(r.string.updated_at), value); } else { timeintoformat = timepassed / one_year; string value = timeintoformat + "年"; updateatvalue = string.format(getresources().getstring(r.string.updated_at), value); } updateat.settext(updateatvalue); } /** * 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器 */ class refreshingtask extends asynctask<void, integer, void> { @override protected void doinbackground(void... params) { int topmargin = headerlayoutparams.topmargin; while (true) { topmargin = topmargin + scroll_speed; if (topmargin <= 0) { topmargin = 0; break; } publishprogress(topmargin); systemclock.sleep(10); } currentstatus = pull_status.status_refreshing; publishprogress(0); if (mlistener != null) { mlistener.onrefresh(); } return null; } @override protected void onprogressupdate(integer... topmargin) { updateheaderview(); headerlayoutparams.topmargin = topmargin[0]; header.setlayoutparams(headerlayoutparams); } } /** * 隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏 */ class hideheadertask extends asynctask<void, integer, integer> { @override protected integer doinbackground(void... params) { int topmargin = headerlayoutparams.topmargin; while (true) { topmargin = topmargin + scroll_speed; if (topmargin <= hideheaderheight) { topmargin = hideheaderheight; break; } publishprogress(topmargin); systemclock.sleep(10); } return topmargin; } @override protected void onprogressupdate(integer ... topmargin) { headerlayoutparams.topmargin = topmargin[0]; header.setlayoutparams(headerlayoutparams); } @override protected void onpostexecute(integer topmargin) { headerlayoutparams.topmargin = topmargin; header.setlayoutparams(headerlayoutparams); currentstatus = pull_status.status_refresh_finished; } } /** * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调 */ public interface pulltorefreshlistener { // 刷新时会去回调此方法,在方法内编写具体的刷新逻辑。注意此方法是在子线程中调用的, 可以不必另开线程来进行耗时操作 void onrefresh(); } }
--> 第三步, 写主布局:
<?xml version="1.0" encoding="utf-8"?> <relativelayout 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" tools:context=".mainactivity" > <com.dragon.android.tofreshlayout.refreshview android:id="@+id/refreshable_view" android:layout_width="match_parent" android:layout_height="match_parent" > <listview android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" > </listview> </com.dragon.android.tofreshlayout.refreshview> </relativelayout>
--> 最后, java 代码添加 listview 的数据:
package com.dragon.android.tofreshlayout; import android.os.bundle; import android.os.systemclock; import android.support.v7.app.appcompatactivity; import android.webkit.webview; import android.widget.arrayadapter; import android.widget.listview; public class mainactivity extends appcompatactivity { refreshview refreshableview; listview listview; arrayadapter<string> adapter; private webview webview; private static int num = 30; string[] items = new string[num]; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); getsupportactionbar().hide(); for (int i = 0; i < items.length; i++) { items[i] = "列表项" + i; } refreshableview = (refreshview) findviewbyid(r.id.refreshable_view); listview = (listview) findviewbyid(r.id.list_view); adapter = new arrayadapter<>(this, android.r.layout.simple_list_item_1, items); listview.setadapter(adapter); refreshableview.setonrefreshlistener(new refreshview.pulltorefreshlistener() { @override public void onrefresh() { systemclock.sleep(3000); refreshableview.finishrefreshing(); } }, 0); } }
程序 demo: 链接:http://pan.baidu.com/s/1ge6llw3 密码:skna
***************其实还应该再封装的...*****************
以上所述是小编给大家介绍的android自定义控件下拉刷新实例代码,希望对大家有所帮助
推荐阅读