效果图:
自定义下拉刷新,主要就是对头部的控制,大体可以分为3步:
1. 控件初加载时,把头部隐藏起来;
2. 给listview设置touch事件,在手指移动过程中计算差值,并赋值给头部,设置头部隐藏或者显示;
3. 头部完全显示之后,就是对头部里view的操作,例如旋转箭头,更改文字,还有显示更新时间。
代码里面个人觉得需要记录点的知识点:
1.
关于LayoutInflater类inflate(intresource, ViewGroup root, boolean attachToRoot)方法三个参数的含义 resource:需要加载布局文件的id,意思是需要将这个布局文件中加载到Activity中来操作。 root:需要附加到resource资源文件的根控件,什么意思呢, 就是inflate()会返回一个View对象,如果第三个参数attachToRoot为true,就将这个root作为根对象返回,否则仅仅将这个root对象的LayoutParams属性附加到resource对象的根布局对象上,也就是将布局文件resource的布局参数转换为外层root可以接受的类型,比如root是一个LinearLayout自己要转换的resource里面有layout_width=”fill_parent”,和layout_height=”fill_parent”参数,但是这些参数没有外部环境,它们对应的对象都是ViewGroup.LayoutParams对象,root参数让系统将ViewGroup.LayoutParams对象转换为LinearLayout.LayoutParams对象。 attachToRoot:是否将root附加到布局文件的根视图上
关于inflate参数通俗点说,最关键的就是root,当root设置不为空且attachToRoot=true,root会作为父布局存在,resource会变为子布局,当设置不为空且attachToRoot=false,resource布局会引用root布局的参数LayoutParam,当root=null ,则直接返回布局
2. MarginLayoutParams:
ViewGroup.MarginLayoutParams是ViewGroup.LayoutParams的一个继承者,主要用于定义和边缘的空白,它具有android:layout_marginTop,android:layout_marginLeft,android:layout_marginBottom,android:layout_marginRight几个XML属性,表示四个方向的边缘空白
3. ViewConfiguration:
ViewConfiguration.get(getContext()).getScaledTouchSlop():最小滑动距离;
ViewConfiguration这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取,具体方法如下:
1、获取ViewConfiguration对象,由于ViewConfiguration的构造方法为私有的,只能通过这个静态方法来获取到该对象。ViewConfiguration configure = ViewConfiguration.get(context);
4.
listView.getFirstVisiblePosition():获取可视区域第一个可见的item的id
listView.getLastVisiblePosition();获取可视区域最后一个可见的item的id
自定义下拉刷新代码如下:
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
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;
import android.view.View.OnTouchListener;
/**
* 下拉刷新
*/
public class MPullRefreshView extends LinearLayout implements OnTouchListener{
/** 下拉状态*/
public static final int PULL_STATUS=0;
/** 释放立即刷新状态*/
public static final int RELEASE_TO_REFRESH_STATUS=1;
/** 正在刷新状态*/
public static final int REFRESHING_STATUS=2;
/** 刷新完成或者未完成状态*/
public static final int REFRESH_FINISH_STATUS=3;
/** 刷新状态*/
private int refreshStatus=REFRESH_FINISH_STATUS;
/** 记录上一次的状态是什么,避免进行重复操作*/
private int lastStatus = refreshStatus;
/** 头部回滚速度*/
private int HEADER_SCROLL_BACK=-20;
/** 第一次加载*/
private boolean loadone;
/** 是否允许下拉*/
private boolean allowpull;
/** 内容*/
private ListView listView;
/** 最大下拉距离*/
private int mTouchSlop;
/** 手指按下时的屏幕纵坐标*/
private float Ydown;
/** 下拉刷新头部*/
private View header;
/** 下拉刷新头部箭头*/
private ImageView arrow;
/** 下拉刷新头部加载*/
private ProgressBar progressBar;
/** 下拉刷新头部文字提示*/
private TextView description;
/** 下拉刷新头部隐藏高度*/
private int hideheaderheight;
/** 下拉刷新头部距离边缘的相关参数*/
private MarginLayoutParams headermarginLayoutParams;
/** 下拉刷新回调接口*/
private PullToRefreshListener listener;
public MPullRefreshView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public MPullRefreshView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MPullRefreshView(Context context) {
super(context);
init();
}
/**
* 添加刷新接口
* @param listener
*/
public void setonRefreshListener(PullToRefreshListener listener){
this.listener=listener;
}
/**
* 结束刷新状态
*/
public void finishRefreshing(){
new HideHeader().execute();
}
private void init() {
header=LayoutInflater.from(getContext()).inflate(R.layout.mpull_refresh_view_header,this,false);
arrow=(ImageView) header.findViewById(R.id.arrow);
progressBar=(ProgressBar) header.findViewById(R.id.progress_bar);
description=(TextView) header.findViewById(R.id.description);
mTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop();
setOrientation(VERTICAL);
addView(header,0);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//当布局改变 或者是第一次加载 隐藏头部
if(changed || !loadone){
hideheaderheight=-header.getHeight();
headermarginLayoutParams=(MarginLayoutParams) header.getLayoutParams();
//将距离上部边缘距离设置为高度的负值 ,即头部隐藏
headermarginLayoutParams.topMargin=hideheaderheight;
listView=(ListView) getChildAt(1);
listView.setOnTouchListener(this);
loadone=true;
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
//允许listview可下拉前先判断listview是否到达顶部
JudgeallowPull();
if(allowpull){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Ydown= event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float Ymove=event.getRawY();
int distance=(int) (Ymove-Ydown);
//上滑 并且头部是隐藏状态 则屏蔽该事件
if(distance<0 && headermarginLayoutParams.topMargin<=hideheaderheight){
return false;
}
//滑动距离小于感应距离,屏蔽事件
if(distance<mTouchSlop){
return false;
}
//不是正在刷新状态,表示头部没有完全显示完
if(refreshStatus!=REFRESHING_STATUS){
if(headermarginLayoutParams.topMargin>0){
refreshStatus=RELEASE_TO_REFRESH_STATUS;//请求释放
}else{
refreshStatus=PULL_STATUS;//下拉状态
}
headermarginLayoutParams.topMargin=(distance/2)+hideheaderheight;
header.setLayoutParams(headermarginLayoutParams);
}
break;
case MotionEvent.ACTION_UP:
default:
//判断当前头部状态
if(refreshStatus==PULL_STATUS){
//当前头部没有完全显示出来,隐藏头部
new HideHeader().execute();
}else if(refreshStatus==RELEASE_TO_REFRESH_STATUS){
//开始刷新,放在异步任务中
new ExectueRefreshTask().execute();
}
break;
}
if(refreshStatus==RELEASE_TO_REFRESH_STATUS || refreshStatus==PULL_STATUS){
//更新头部信息
UpdateHeaderContent();
// 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态
listView.setPressed(false);
listView.setFocusable(false);
listView.setFocusableInTouchMode(false);
lastStatus=refreshStatus;
return true;
}
}
return false;
}
/**
* 更新头部内容
*/
private void UpdateHeaderContent() {
if(lastStatus!=refreshStatus){
switch (refreshStatus) {
case PULL_STATUS:
description.setText("下拉刷新");
rotateArrow();
break;
case RELEASE_TO_REFRESH_STATUS:
description.setText("释放立即刷新");
rotateArrow();
break;
case REFRESHING_STATUS:
description.setText("正在刷新...");
arrow.clearAnimation();
arrow.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
break;
}
}
}
/**
* 旋转箭头
*/
private void rotateArrow() {
arrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
float pivotX = arrow.getWidth() / 2f;
float pivotY = arrow.getHeight() / 2f;
float fromDegrees = 0f;
float toDegrees = 0f;
if (refreshStatus == PULL_STATUS) {
fromDegrees = 180f;
toDegrees = 360f;
} else if (refreshStatus == RELEASE_TO_REFRESH_STATUS) {
fromDegrees = 0f;
toDegrees = 180f;
}
RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
animation.setDuration(100);
animation.setFillAfter(true);
arrow.startAnimation(animation);
}
/**
* 判断是否允许下拉(条件:第一行item可见并处于最顶部 或者内容为空)
*/
private void JudgeallowPull() {
int position;
View fistview=listView.getChildAt(0);
if(fistview!=null){
position=listView.getFirstVisiblePosition();
if(position==0 && fistview.getTop()==0){
allowpull=true;
}else{
//当前listview显示的item不是第一个,头部应该隐藏
if(headermarginLayoutParams.topMargin!=hideheaderheight){
headermarginLayoutParams.topMargin=hideheaderheight;
header.setLayoutParams(headermarginLayoutParams);
}
allowpull=false;
}
}else{
//listview 为空时 也允许下拉
allowpull=true;
}
}
// AsyncTask定义了三种泛型类型 Params,Progress和Result。
//
// Params 启动任务执行的输入参数,比如HTTP请求的URL。
// Progress 后台任务执行的百分比。
// Result 后台执行任务最终返回的结果,比如String。
/**
* 执行下拉刷新任务
*/
class ExectueRefreshTask extends AsyncTask<Void, Integer,Void>{
@Override
protected Void doInBackground(Void... params) {
int topmargin=headermarginLayoutParams.topMargin;
while(true){
topmargin+=HEADER_SCROLL_BACK;
if(topmargin<=0){
topmargin=0;
break;
}
publishProgress(topmargin);
sleep(10);
}
refreshStatus=REFRESHING_STATUS;
publishProgress(0);
if(listener!=null){
listener.Refresh();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
UpdateHeaderContent();
headermarginLayoutParams.topMargin=values[0];
header.setLayoutParams(headermarginLayoutParams);
}
}
/**
* 隐藏头部
*/
class HideHeader extends AsyncTask<Void, Integer,Integer>{
@Override
protected Integer doInBackground(Void... params) {
int topmargin=headermarginLayoutParams.topMargin;
while(true){
topmargin+=HEADER_SCROLL_BACK;
if(topmargin<=hideheaderheight){
topmargin=hideheaderheight;
break;
}
publishProgress(topmargin);
sleep(10);
}
return topmargin;
}
@Override
protected void onProgressUpdate(Integer... values) {
headermarginLayoutParams.topMargin=values[0];
header.setLayoutParams(headermarginLayoutParams);
}
@Override
protected void onPostExecute(Integer result) {
refreshStatus=REFRESH_FINISH_STATUS;
headermarginLayoutParams.topMargin=result;
header.setLayoutParams(headermarginLayoutParams);
}
}
/**
* 使当前线程睡眠指定的毫秒数
* @param i
*/
private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
interface PullToRefreshListener{
/**
* 刷新任务(注意刷新完之后结束刷新)
*/
void Refresh();
}
}
Activity:
import java.util.ArrayList;
import java.util.List;
import com.example.mytoolutils.MPullRefreshView.PullToRefreshListener;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MPullRefreshActivity extends Activity implements OnItemClickListener, PullToRefreshListener {
private MPullRefreshView mpullrefreshview;
private ListView listview;
private List<String> list;
private MyAdapter adapter;
private Myhandler myhandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mpull_refresh);
mpullrefreshview=(MPullRefreshView) findViewById(R.id.mpullrefreshview);
mpullrefreshview.setonRefreshListener(this);
listview=(ListView) findViewById(R.id.listview);
myhandler=new Myhandler();
setdata();
}
private void setdata() {
list=new ArrayList<String>();
for(int a=0;a<30;a++){
list.add(a+"");
}
}
@Override
protected void onResume() {
super.onResume();
adapter=new MyAdapter();
listview.setAdapter(adapter);
listview.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(this,position+"",Toast.LENGTH_SHORT).show();
}
@Override
public void Refresh() {
try {
//模拟联网查询数据
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message=new Message();
message.arg1=1;
myhandler.sendMessage(message);
mpullrefreshview.finishRefreshing();
}
@SuppressLint("HandlerLeak")
class Myhandler extends Handler{
@Override
public void handleMessage(Message msg) {
int a= msg.arg1;
if(a==1){
list.add("新添加的"+list.size());
adapter.notifyDataSetChanged();
}
}
}
@SuppressLint("InflateParams")
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Myholder myholder;
if(convertView==null){
myholder=new Myholder();
convertView=LayoutInflater.from(MPullRefreshActivity.this).inflate(android.R.layout.simple_list_item_1,null);
convertView.setTag(myholder);
}else{
myholder=(Myholder) convertView.getTag();
}
myholder.textView=(TextView) convertView.findViewById(android.R.id.text1);
myholder.textView.setText(list.get(position));
return convertView;
}
}
class Myholder{
TextView textView;
}
}
activity_mpull_refresh:
<merge 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="com.example.mytoolutils.MPullRefreshActivity" >
<com.example.mytoolutils.MPullRefreshView
android:id="@+id/mpullrefreshview"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@android:color/transparent"
android:scrollbars="none" >
</ListView>
</com.example.mytoolutils.MPullRefreshView>
</merge>
mpull_refresh_view_header:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/pull_to_refresh_head"
android:layout_width="fill_parent"
android:layout_height="60dip" >
<LinearLayout
android:layout_width="200dip"
android:layout_height="60dip"
android:layout_centerInParent="true"
android:orientation="horizontal" >
<RelativeLayout
android:layout_width="0dip"
android:layout_height="60dip"
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="@drawable/arrow"
/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_centerInParent="true"
android:visibility="gone"
/>
</RelativeLayout>
<LinearLayout
android:layout_width="0dip"
android:layout_height="60dip"
android:layout_weight="12"
android:orientation="vertical" >
<TextView
android:id="@+id/description"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/pull_to_refresh" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>