欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-04 15:14:59
实现效果: 图片素材: --> 首先, 写先下拉刷新时的刷新布局 pull_to_refresh.xml:

实现效果:

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

图片素材:

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

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