下拉刷新菜单-实现
主要技术点:1、使用SystemClock.sleep(time)代替Thread.sleep(time)睡眠 2、需要睡眠时,需要是要子线程,但界面UI更新需要在主线程中 runOnUiThread,Handler+Message,AsyncTask 3、测量一个组件的高度,使用ListView中private的measureItem,可以黏贴过来,或者反射 4、枚举的使用,限定在指定的几个范围值中 5、 SimpleDateFormat格式化系统时间 |
步骤: 实现下拉时候,显示头部并刷新 RefreshListView 中 1、监听ListView的滚动setOnScrollListener 事件,用来记住第一个item的位置 2、监听ListView的触摸事件,实现onTouchEvent()方法 ACTION_DOWN://触摸按下 记住按下dowmY位置,初始化为-1,防止触摸不到,在移动的时候出错 ACTION_MOVE://触摸移动 a) 判断按下Y轴坐标downY是否为-1,如果是,说明触摸按下事件没有响应,将当前y轴坐标赋值给downY b)记住移动的Y轴坐标位置moveY 通过paddingTop = (-height)+(moveY-downY)/2 //除以2,是为了防止拉的过长,head会拉到最下面来 如果paddingTop(此时为负数)>-height,说明刷新中 如果paddingTop大于0时,那么该头就全部出来了,iv_arrow执行向上动画, paddingTop一小于0,就执行箭头向下动画 通过一个枚举来记录headView的状态 ACTION_UP://触摸松手 通过headView的状态来判断松手后的动作 a)下拉刷新中,那么完全隐藏 b)松手刷新,跳到最顶层,刷新,并修改相应的组件文本和动画, c)正在刷新,此时,箭头改为progressbar,且不能listview不能滚动, 3、对外提供一个 OnRefreshListener 接口,并提供 setOnRefreshListener方法,用来设置当刷新过程中,需要执行的操作,比如获取数据 4、并提供一个刷新完成的方法,用来更新界面的UI 清理动画 组件的文本 更新最后刷新时间等 4、在MainActivity利用runOnUithread()、Handler+Message或者AsyncTask后台获取数据,更新到界面 |
1、布局: a)activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<cn.zengfansheng.refreshListview.RefreshListView
android:id="@+id/refresh_listview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</cn.zengfansheng.refreshListview.RefreshListView>
</RelativeLayout> b)listview_head.xml <?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:orientation="horizontal" >
<!--1、箭头和进度条 -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp" >
<!--箭头 -->
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/common_listview_headview_red_arrow" />
<!-- 进度条 -->
<ProgressBar
android:id="@+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/custom_progressbar"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:orientation="vertical" >
<!-- 刷新状态 -->
<TextView
android:id="@+id/tv_refresh_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="下拉刷新"
android:textColor="#ff0000"
android:textSize="20sp" />
<!-- 最后刷新时间 -->
<TextView
android:id="@+id/tv_last_update_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="最后刷新时间:2013-12-02 23:28:02"
android:textColor="@android:color/darker_gray"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
|
2、核心代码: RefreshListView.java package cn.zengfansheng.refreshListview;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class RefreshListView extends ListView { private ImageView iv_arrow;// 箭头
private ProgressBar pb;// 进度条
private TextView tv_refresh;// 刷新状态文本
private TextView tv_last_update;// 最后刷新时间
private int viewHeaderMeasuredHeight;// 测量后的高度
private int myFirstVisibleItem;// listview中第一个可见的条目
private float downY = -1;// 触摸按下Y轴的坐标,防止其他事件,导致触摸事件没有响应
private float moveY;// 触摸移动时Y轴的坐标
private View viewHeader;// 头布局
private HeaderDisplayMode headerDisplayMode = HeaderDisplayMode.PULL_DOWN;//headerview的状态,默认为下拉刷新
private float paddingTop;// headerview的上边距
private Animation refresh_down_animation;// 箭头向下动画
private Animation refresh_up_animation;// 箭头向上动画
private OnRefreshListener mOnRefreshListener;// 刷新的监听器 public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
initHeader();// 初始化头
}
public RefreshListView(Context context) {
super(context);
initHeader();// 初始化头
}
/**
* 1、初始化头布局
*/
public void initHeader() {
viewHeader = View.inflate(getContext(), R.layout.listview_head, null);
iv_arrow = (ImageView) viewHeader.findViewById(R.id.iv_arrow);
pb = (ProgressBar) viewHeader.findViewById(R.id.pb);
tv_refresh = (TextView) viewHeader.findViewById(R.id.tv_refresh_status);
tv_last_update = (TextView) viewHeader.findViewById(R.id.tv_last_update_time);
// 设置箭头的最小宽度,防止播放动画时,宽度不够
// iv_arrow.setMinimumWidth(40);
/*System.out.println("测量前,通过getHeight()获取viewHeader的高度:"+viewHeader.getHeight());//0
// 由于ListView每一个item,都会进行测量高度,但是现在还没有测量,所以需要手动测量
// 测量viewHeader的高度和宽度
measureHeader(viewHeader);
System.out.println("测量后,通过getHeight()获取viewHeader的高度:"+viewHeader.getHeight());//0
viewHeaderMeasuredHeight = viewHeader.getMeasuredHeight();
System.out.println("测量后,通过getMeasuredHeight()获取viewHeader的高度:"+viewHeaderMeasuredHeight);//59
// 如何实现head开始就隐藏呢,利用paddingTop
//viewHeader.setPadding(0, -viewHeaderMeasuredHeight / 2, 0, 0);//此时,只显示viewHeader高度的一半
viewHeader.setPadding(0, -viewHeaderMeasuredHeight, 0, 0);// 这样,就可以隐藏了*/
tv_last_update.setText("最后更新时间:" + getCurrentTime());// 第一次进来,更新一次
measureHeader(viewHeader);// 测量高度,否则获取不到头布局高度
viewHeaderMeasuredHeight = viewHeader.getMeasuredHeight();
viewHeader.setPadding(0, -viewHeaderMeasuredHeight, 0, 0);//隐藏头布局
addHeaderView(viewHeader);
// 注册滚动监听事件
this.setOnScrollListener(new MyOnScrollListener());
// 初始化动画
intiAnimation();
}
/**
* 1-1、初始化动画
*/
private void intiAnimation() {
// xml定义动画
refresh_down_animation = AnimationUtils.loadAnimation(getContext(), R.anim.refresh_down);
refresh_up_animation = AnimationUtils.loadAnimation(getContext(),R.anim.refresh_up);
// 代码定义动画
/*refresh_down_animation = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
refresh_down_animation.setDuration(500);
refresh_down_animation.setFillAfter(true);*/
/*refresh_up_animation = new RotateAnimation(0, -180,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
refresh_up_animation.setDuration(500);
refresh_up_animation.setFillAfter(true);*/
}
/**
* 2、测量并计算head的宽度和高度 测量item,这是在ListView中私有的方法,所以可以将其黏贴过来
* Measure a particular list child. unify with setUpChild.
* @param viewHeaderThe child.
*/
private void measureHeader(View viewHeader) {
ViewGroup.LayoutParams lp = viewHeader.getLayoutParams();
if (lp == null) {
lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);// 测量标准
int lpHeight = lp.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
viewHeader.measure(childWidthSpec, childHeightSpec);
}
/**
* 3、滚动事件-获取第一个item在ListView中的位置
* @author hacket
*/
private class MyOnScrollListener implements OnScrollListener {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
myFirstVisibleItem = firstVisibleItem;
}
}
// 4、触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:// 触摸按下
downY = event.getY();// downY初始值为-1,防止其他事件,导致触摸事件没有响应
System.out.println("downY:" + downY);
break;
case MotionEvent.ACTION_MOVE:// 触摸移动
// 如果当前的状态是正在刷新中, 跳出
if (headerDisplayMode == HeaderDisplayMode.ISREFRESHING) {// 正在刷新时,不可触摸移动
break;
}
if (downY == -1) {
downY = event.getY();
}
// 获得移动中y轴的偏移量
moveY = event.getY();
// 头布局的负数 + (移动中y轴的偏移量 - 按下时y轴的偏移量) / 2
paddingTop = -viewHeaderMeasuredHeight + (moveY - downY) / 2;
//判断paddingTop是否大于头布局的高度的负数,并且滚动时第一个显示的item为0 进入if语句
if (myFirstVisibleItem==0 && paddingTop > -viewHeaderMeasuredHeight) {// 开始下拉刷新,如果不判断myFirstVisibleItem,后面的item下拉时也不正常
// 开始移动下拉布局
if (paddingTop > 0// 完全显示头布局
&& headerDisplayMode == HeaderDisplayMode.PULL_DOWN) {// 如果大于0,说明已经完全出来了,变成松手刷新状态
// 改变当前的状态为 松开刷新
headerDisplayMode = HeaderDisplayMode.REALEASE_REFRESH;
refreshHeaderView();
} else if (paddingTop <= 0 // 还没有完全显示头布局
&& headerDisplayMode == HeaderDisplayMode.REALEASE_REFRESH) {// 小于0,那么变成下拉刷新
// 改变当前的状态为下拉状态
headerDisplayMode = HeaderDisplayMode.PULL_DOWN;
refreshHeaderView();
}
// refreshHeaderView();//不能放在这里,如果在松手状态,会多次执行松手动画,导致,箭头上下在移动,影响用户体验
viewHeader.setPadding(0, (int) paddingTop, 0, 0);
return true;// 消费掉该事件
}
break;
case MotionEvent.ACTION_UP:// 触摸松手
// 如果当前状态是正在刷新中 跳出
if (headerDisplayMode == HeaderDisplayMode.ISREFRESHING) {// 正在刷新时,不可触摸移动
break;
}
// 如果当前状态是松开刷新, 进入if 做刷新的操作
if (headerDisplayMode == HeaderDisplayMode.REALEASE_REFRESH) {
headerDisplayMode = HeaderDisplayMode.ISREFRESHING;
paddingTop = 0;
viewHeader.setPadding(0, (int) paddingTop, 0, 0);
refreshHeaderView();
if (mOnRefreshListener != null) {// 如果调用者实现了OnRefreshListener接口
// 调用刷新回调方法
mOnRefreshListener.onRefresh();// 调用接口中暴露出去的方法
}
// 当下拉状态时
} else if (headerDisplayMode == HeaderDisplayMode.PULL_DOWN) {
// 把头布局复位为隐藏状态
paddingTop = -viewHeaderMeasuredHeight;
viewHeader.setPadding(0, (int) paddingTop, 0, 0);
refreshHeaderView();
}
downY = -1;
moveY = -1;
break;
}
return super.onTouchEvent(event);// 消费掉该事件,如果true,那么就消费了该事件,那么就无法滑动了,所以不能为true
}
/**
* 5、刷新头布局
*/
private void refreshHeaderView() {
if (headerDisplayMode == HeaderDisplayMode.PULL_DOWN) {// 下拉状态
iv_arrow.startAnimation(refresh_down_animation);
tv_refresh.setText("下拉刷新");
System.out.println("下拉刷新动画~~~");
} else if (headerDisplayMode == HeaderDisplayMode.REALEASE_REFRESH) {// 松手刷新
iv_arrow.startAnimation(refresh_up_animation);
tv_refresh.setText("松手刷新");
System.out.println("松手刷新动画~~~");
} else if (headerDisplayMode == HeaderDisplayMode.ISREFRESHING) {// 正在刷新
iv_arrow.clearAnimation();// 清理动画
iv_arrow.setVisibility(View.GONE);
pb.setVisibility(View.VISIBLE);
tv_refresh.setText("正在刷新中");
}
}
/**
* 6、headerview状态枚举
* @author hacket
*/
public enum HeaderDisplayMode {
PULL_DOWN, // 下拉刷新
REALEASE_REFRESH, // 松手刷新
ISREFRESHING // 正在刷新中
}
/**
* 7-1、当刷新时的监听事件实现的接口
*/
public interface OnRefreshListener {
public abstract void onRefresh();
}
/**
* 7-2、设置刷新时监听器
* @param mOnRefreshListener
*/
public void setOnRefreshListener(OnRefreshListener mOnRefreshListener) {
this.mOnRefreshListener = mOnRefreshListener;
}
/**
* 8、刷新完成后,将状态初始化为下拉状态
*/
public void refreshFinish() {
// 隐藏头布局
viewHeader.setPadding(0, -viewHeaderMeasuredHeight, 0, 0);
// 更改头布局状态为下拉
headerDisplayMode = HeaderDisplayMode.PULL_DOWN;
// 进度条不可见
pb.setVisibility(View.GONE);
// 箭头可见
iv_arrow.setVisibility(View.VISIBLE);
// 头布局状态文本
tv_refresh.setText("下拉刷新");
// 头布局最后刷新时间文本
tv_last_update.setText("最后一次刷新时间:" + getCurrentTime());
}
/**
* 9、获取当前系统时间
* @return
*/
private String getCurrentTime(){
SimpleDateFormat sdp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String time = sdp.format(new Date());
return time;
}
} |
3、使用该下拉刷新菜单: MainActivity.java package cn.zengfansheng.refreshListview;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;
import cn.zengfansheng.refreshListview.RefreshListView.OnRefreshListener;
public class MainActivity extends Activity {
private RefreshListView refreshListView;
private List<Integer> list;
private RefreshAdapter freshAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = new ArrayList<Integer>();
for (int i = 0; i < 50; i++) {
list.add(i + 1);
}
refreshListView = (RefreshListView) this.findViewById(R.id.refresh_listview);
if (freshAdapter == null) {
freshAdapter = new RefreshAdapter();
}
refreshListView.setAdapter(freshAdapter);
// 设置刷新监听器
refreshListView.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
// TODO:
//SystemClock.sleep(2000);// 和Thread.sleep();类似,只是不会抛出异常,两秒钟后模拟刷新完毕操作,要在子线程执行
// 3、AsyncTask异步任务
// new AsyncTask<Params, Progress, Result>
new AsyncTask<String, Object, Integer>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(getApplicationContext(), "开始任务刷新", 0).show();
}
@Override
protected Integer doInBackground(String... params) {// 参数1,String后台任务中需要的参数,传递到这里,方法Integer的result
String p1 = params[0];
String p2 = params[1];
System.out.println("params:" + p1 + ":" + p2);
list.add(0, (int) (Math.random() * 10));
SystemClock.sleep(2000);
return 202;
}
@Override
protected void onPostExecute(Integer result) {// 参数3,Integer执行完毕后,然后的参数,被这里给接收
super.onPostExecute(result);
System.out.println("result:" + result);
refreshListView.refreshFinish();
freshAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "刷新完毕", 0).show();
}
@Override
protected void onProgressUpdate(Object... values) {// 参数2:Object执行过程中,数据的传递
super.onProgressUpdate(values);
}
}.execute("hehe", "haha");// 这里传入参数1:String,为后台任务所需要的参数
// 2、 Handler+Message
// 由于比较耗时,所以用子线程
/*
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
refreshListView.refreshFinish();
freshAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "刷新完毕", 0).show();
}
};
new Thread() {
public void run() {
SystemClock.sleep(2000);
list.add(0, (int) (Math.random() * 10));
handler.sendEmptyMessage(0);// 由于是在子线程中,所以需要发送消息
}
}.start();*/
// 1、runOnUiThread
/*new Thread() {
public void run() {
SystemClock.sleep(2000);//子线程中睡眠
list.add(0, new Random().nextInt(100));
runOnUiThread(new Runnable() {
public void run() {
refreshListView.refreshFinish();// 调用刷新完毕方法,更新头布局ui
freshAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "刷新完毕", 0).show();
}
});
}
}.start();*/
}
});
}
/**
* 1、下拉刷新数据适配器类
* @author hacket
*/
private class RefreshAdapter extends BaseAdapter {
@Override
public int getCount() {
if (list != null) {
return list.size();
}
return 0;
}
@Override
public Object getItem(int position) {
return getItem(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tView;
if (convertView != null && convertView instanceof TextView) {
tView = (TextView) convertView;
} else {
tView = new TextView(getApplicationContext());
}
Integer i = list.get(position);
tView.setTextColor(Color.BLACK);
tView.setText("这是下拉菜单刷新数据:"+i);
return tView;
}
}
} |
4、结果: a) 默认头布局隐藏,但下拉时,箭头向下,文本显示下拉刷新 b) 当下拉到超出该头文件的高度,那么箭头向上,文本显示松手刷新 c) 当松手后,箭头不可见,progressbaar可见,文本更新为正在刷新 d)当刷新完毕后,还原界面ui,并更新最后刷新时间,然后将头布局给隐藏掉 |