当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义控件下拉刷新实例代码

Android自定义控件下拉刷新实例代码

2019年07月24日  | 移动技术网移动技术  | 我要评论

实现效果:

图片素材:

--> 首先, 写先下拉刷新时的刷新布局 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自定义控件下拉刷新实例代码,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网