您的位置:首页 > 其它

安卓自定义ListView实现上拉加载、下拉刷新

2016-02-18 11:24 351 查看
自定义ListView实现上拉加载、下来刷新,暂时未加入侧滑功能,可以设置是否需要加载、刷新以及每次加载的item个数,可以根据设置的加载item个数判断第一次获取数据后是否加载(如果少于设置的个数则不许加载)

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:gravity="center">

<RelativeLayout
android:id="@+id/my_list_view_header_content"
android:layout_width="match_parent"
android:layout_height="60dp">

<LinearLayout
android:id="@+id/my_list_view_header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">

<TextView
android:id="@+id/my_list_view_header_hint_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="上次更新时间:"
android:textSize="12sp" />

<TextView
android:id="@+id/my_list_view_header_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

<ImageView
android:id="@+id/my_list_view_header_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/my_list_view_header_text"
android:layout_centerVertical="true"
android:layout_marginLeft="-35dp"
android:src="@drawable/my_listview_arrow" />

<ProgressBar
android:id="@+id/my_list_view_header_progressbar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignLeft="@id/my_list_view_header_text"
android:layout_centerVertical="true"
android:layout_marginLeft="-40dp"
android:visibility="gone" />
</RelativeLayout>

</LinearLayout>


[code]

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="75dp"
android:clickable="false"
android:orientation="vertical">

<LinearLayout
android:id="@+id/my_listview_footer_load_layout"
android:layout_width="match_parent"
android:layout_height="75dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingBottom="10dp"
android:paddingTop="10dp">

<ProgressBar
android:id="@+id/my_listview_footer_load_progress"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="10dp" />

<TextView
android:id="@+id/my_listview_footer_load_text"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:text="正在加载"
android:textSize="16sp" />
</LinearLayout>

</LinearLayout>



java代码

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.jwwl.carrierapp.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 自定义ListView用于上拉加载、下拉刷新、左滑菜单
*/
public class MyListView extends ListView implements OnScrollListener {
/**
* 滑动开始时,手指按下Y的坐标
*/
private int downY;
/**
* 每次加载的Item个数,根据此个数判断第一次获取数据后是否需要加载,默认为0个
*/
private int sizeItem = 0;

/**
* 用于保证每次滑动是一个完整的滑动事件,按下的downY坐标值每次事件中只被记录一次(按下->滑动->松开,为一个完整事件),默认为false
* true处于一个事件中
* false未开始事件
*/
private boolean isRecored = false;
/**
* 外部调用设置,是否可以下拉刷新,默认为false
* true可以下拉刷新
* false不可以下拉刷新
*/
private boolean isRefreshable = false;
/**
* 外部调用设置,是否可以上拉加载数据,默认为false
* true 可以上拉加载
* false 不可以上拉加载
*/
private boolean isLoadable = false;
/**
* 是否处于可下拉刷新状态,当Item为0时为true,默认为false
* true可以下拉刷新状态
* false不可以下拉刷新状态
*/
private boolean isRefresh = false;
/**
* 是否处于可上拉加载状态,当前屏幕最后一个Item下标为总的Item下标时为true,默认为false
* true可以上拉加载状态
* false不可以上拉加载状态
*/
private boolean isLoad = false;
/**
* 用于判断是否是从下拉要刷新状态滑动到下拉要返回状态,默认为false
* true 是
* false 不是
*/
private boolean isBack = false;
/**
* 是否处于上拉加载中,默认为false
* true上拉加载中
* false不在上拉加载中
*/
private boolean isLoading = false;
/**
* 下拉刷新头部布局,各个控件
*/
private View headerView; // 头部布局
private TextView headerTipsTv; //下来刷新提示
private TextView headerTimeTv; //下来刷新时间
private ImageView headerArrowIv; //下来刷新箭头
private ProgressBar headerProgressBar; //下来刷新进度条
/**
* 底部布局
*/
private View footerView;
/**
* 加载监听接口
*/
private OnLoaderListener onLoaderListener;
/**
* 刷新监听接口
*/
private OnRefreshListener refreshListener;
/**
* 下拉刷新头部布局的高度
*/
private int headerContentHeight;

/**
* 下来刷新箭头动画
*/
private RotateAnimation rotateAnimation; //正向旋转,
private RotateAnimation reverseRotateAnimation; //反向旋转,

/**
* 下拉刷新状态值:下拉状态,松开不刷新
*/
private final static int PULL_TO_BACK = 0;
/**
* 下拉刷新状态值:下拉状态,松开会刷新
*/
private final static int PULL_TO_REFRESHING = 1;
/**
* 下拉刷新状态值:刷新中
*/
private final static int REFRESHING = 2;
/**
* 上拉刷新状态值:完成
*/
private final static int DONE = 3;
/**
* 上拉刷新状态,默认为完成状态
*/
private int state = DONE;

/**
* 滑动的偏移比例,当滑动距离除以此比例大于等于布局高度时,则达到可以刷新加载状态
*/
private final static int RATIO = 3;

//构造方法
public MyListView(Context context) {
super(context);
initView(context);
}

public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}

public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}

/**
* 初始化View
*/
private void initView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
setCacheColorHint(context.getResources().getColor(R.color.app_white)); //ListView滑动时背景

/*
* 下拉刷新:
*/
//获取布局
headerView = inflater.inflate(R.layout.my_listview_header, null); //获取头部控件
headerView.setClickable(false); //设置布局不可点击
headerTipsTv = (TextView) headerView.findViewById(R.id.my_list_view_header_hint_textview); //下来刷新标题
headerTimeTv = (TextView) headerView.findViewById(R.id.my_list_view_header_time); //下拉刷新时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
headerTimeTv.setText(sdf.format(new Date()));
headerArrowIv = (ImageView) headerView.findViewById(R.id.my_list_view_header_arrow); //下拉刷新箭头
headerProgressBar = (ProgressBar) headerView.findViewById(R.id.my_list_view_header_progressbar); //下拉刷新进度条
//获取下拉刷新控件高度
headerView.measure(0, 0);
headerContentHeight = headerView.getMeasuredHeight();
//设置内边距,正好距离顶部为一个负的整个布局的高度,正好把头部隐藏
headerView.setPadding(0, -headerContentHeight, 0, 0);
//重绘一下
headerView.invalidate();
//将下拉刷新的布局加入ListView的顶部
addHeaderView(headerView, null, false);
//设置正向旋转动画事件
rotateAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setDuration(200);
rotateAnimation.setFillAfter(true);
//设置反向旋转动画事件
reverseRotateAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
reverseRotateAnimation.setInterpolator(new LinearInterpolator());
reverseRotateAnimation.setDuration(200);
reverseRotateAnimation.setFillAfter(true);

/*
* 上拉加载
*/
//获取布局
footerView = inflater.inflate(R.layout.my_listview_footer, null); //获取底部加载控件
footerView.findViewById(R.id.my_listview_footer_load_layout).setVisibility(GONE);
footerView.setClickable(false);//设置布局不可点击
// 将上拉加载的布局加入ListView的底部
addFooterView(footerView);

/*
滑动监听事件
*/
setOnScrollListener(this);
}

/**
* 设置是否可以下拉刷新
*
* @param enable true-可以下拉刷新,false-不可以下拉刷新
*/
public void setRefreshEnable(boolean enable) {
isRefreshable = enable;
}

/**
* 设置是否可以上拉加载
*
* @param enable true-可以上拉加载,false-不可以上拉加载
*/
public void setLoadEnable(boolean enable) {
isLoadable = enable;
}

/**
* 设置每次加载的Item个数
*/
public void setSizeItem(int sizeItem) {
this.sizeItem = sizeItem;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: //按下

downY = (int) ev.getY(); //记录按下时的y坐标

/*
* 如果可以刷新或加载,并且未开始一个事件,则开始一个滑动事件
*/
if (isRefreshable && !isRecored && (isRefresh || isLoad)) {
isRecored = true;
}
break;

case MotionEvent.ACTION_MOVE: //滑动
int moveY = (int) ev.getY();
/*
* 如果可以刷新或加载,并且未开始一个事件,则开始一个滑动事件
*/
if (isRefreshable && !isRecored && (isRefresh || isLoad)) {
isRecored = true;
downY = moveY;
}

/**
* 根据是否处于滑动事件和刷新加载状态及是否处于可刷新判断刷新
*/
if (isRecored && state != REFRESHING && isRefresh) {
/*
如果在完成状态滑动
当下滑时,状态设置为下拉状态,并更新header的信息
*/
if (state == DONE) {
//1、如果滑动值为正,则是往下滑,状态设置为下拉要返回状态
if (moveY - downY > 0) {
state = PULL_TO_BACK;
changeViewByState();//更新hander信息
}
}

/*
如果在下拉要返回状态滑动
*/
if (state == PULL_TO_BACK) {
setSelection(0);
//1、如果滑动值为负,则是上滑,当滑动超出原按下位置时设为完成状态
if ((moveY - downY) <= 0) {
state = DONE;
changeViewByState();//更新hander信息
}
//2、如果滑动值为正,则是下滑,当滑动达到可以刷新时设为下拉可刷新状态
if ((moveY - downY) > 0 && (moveY - downY) / RATIO >= headerContentHeight) {
state = PULL_TO_REFRESHING;
changeViewByState();//更新hander信息
}
//3、设置header的padding值
headerView.setPadding(0, (moveY - downY) / RATIO - headerContentHeight, 0, 0);
}

/*
如果在下拉可刷新状态滑动
*/
if (state == PULL_TO_REFRESHING) {
setSelection(0);
//1、如果滑动值为正,则是下滑,当滑动达回到下拉要返回状态时,设置为下拉要返回状态
if ((moveY - downY) > 0 && (moveY - downY) / RATIO < headerContentHeight) {
isBack = true;
state = PULL_TO_BACK;
changeViewByState();//更新hander信息
}
//2、设置header的padding值
headerView.setPadding(0, (moveY - downY) / RATIO - headerContentHeight, 0, 0);
}
}

/**
* 根据是否处于滑动事件和刷新加载状态及是否处于可刷新判断加载
*/
if (isRecored && !isLoading && isLoad) {

//1、如果滑动值为负,则是往上滑,状态设置上拉加载状态
if (moveY - downY < 0 && onLoaderListener != null) {
isLoading = true;
Log.i("TAG", "数据加载load");
footerView.findViewById(R.id.my_listview_footer_load_layout).setVisibility(VISIBLE);
onLoaderListener.onLoad();
}
}
break;

case MotionEvent.ACTION_UP: //松开
isBack = false;
isRecored = false; //按下、滑动、松开整个事件结束

/**
*  根据松开时的刷新状态判断是否刷新
*  只有不处于刷新中才判断
*/
if (state != REFRESHING) {
//下拉到要返回状态松开
if (state == PULL_TO_BACK) {
state = DONE; //状态设为完成
changeViewByState();//更新hander信息
}

//下拉到要刷新状态松开
if (state == PULL_TO_REFRESHING) {
//回调刷新方法
if (refreshListener != null) {
state = REFRESHING; //状态设为刷新
changeViewByState();//更新hander信息
refreshListener.onRefresh();
} else {
state = DONE; //状态设为完成
changeViewByState();//更新hander信息
}
}
}
break;
}

return super.onTouchEvent(ev);
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
/*
* 滑动时会一直回调该方法,直到停止滑动。单击时回调一次该方法。
* firstVisibleItem表示当前屏幕显示的第一个listItem在整个listView里的位置(下标从0开始);
* visibleItemCount表示当前屏幕可见的listItem(部分显示的listItem也算)总数;
* totalItemCount表示listView里listItem总数。
* <p/>
* 当滑动到列表首个Item时,判断是否可以下拉刷新
* 当滑动到列表结尾之前,判断是否加载数据
*/

//根据当前首个item下标是否是0并且是否可以下拉刷新,判断是否能够下拉刷新
if (isRefreshable && firstVisibleItem == 0) {
isRefresh = true;
} else {
isRefresh = false;
}

//当前屏幕首个item的下标与当前屏幕item个数之和同item总个数比较,判断是否加载数据
if (isLoadable && totalItemCount == (firstVisibleItem + visibleItemCount) && sizeItem <= totalItemCount && totalItemCount != 0) {
isLoad = true;
} else {
isLoad = false;
}
}

/**
* 当刷新状态改变时候,调用该方法,根据当前状态更新header界面信息
*/
private void changeViewByState() {
switch (state) {
case PULL_TO_BACK: //松开不刷新
headerProgressBar.setVisibility(View.GONE); //隐藏进度条
headerTipsTv.setVisibility(View.VISIBLE); //显示刷新提示
headerTipsTv.setText("下拉刷新"); //设置刷新提示内容
headerTimeTv.setVisibility(View.VISIBLE); //显示上次刷新时间
headerArrowIv.setVisibility(View.VISIBLE); //显示刷新箭头
headerArrowIv.clearAnimation(); //清除刷新箭头动画
//根据是否是从下拉要刷新状态滑动到下拉要返回状态,来判断是否调用反向旋转的箭头动画
if (isBack) {
isBack = false;
headerArrowIv.startAnimation(reverseRotateAnimation);//设置刷新动画为反向旋转动画
}
break;

case PULL_TO_REFRESHING: //松开要刷新
headerProgressBar.setVisibility(View.GONE);//隐藏进度条
headerTipsTv.setVisibility(View.VISIBLE);//显示刷新提示
headerTipsTv.setText("松开刷新");//设置刷新提示内容
headerTimeTv.setVisibility(View.VISIBLE);//显示上次刷新时间
headerArrowIv.setVisibility(View.VISIBLE);//显示刷新箭头
headerArrowIv.clearAnimation(); //清除刷新箭头动画
headerArrowIv.startAnimation(rotateAnimation);//设置刷新动画为正向旋转动画
break;

case REFRESHING: //刷新中
headerView.setPadding(0, 0, 0, 0); //设置刷新头布局padding
headerProgressBar.setVisibility(View.VISIBLE);//显示进度条
headerTimeTv.setVisibility(View.VISIBLE);//显示刷新提示
headerTipsTv.setText("正在刷新");//设置刷新提示内容
headerArrowIv.clearAnimation();//清除刷新箭头动画
headerArrowIv.setVisibility(View.GONE);//隐藏刷新箭头
break;

case DONE: //刷新完成
headerView.setPadding(0, -1 * headerContentHeight, 0, 0);//设置刷新头部布局padding
headerProgressBar.setVisibility(View.GONE);//隐藏进度条
headerTimeTv.setVisibility(View.VISIBLE);//显示刷新提示
headerTipsTv.setText("下拉刷新");//设置刷新提示内容
headerArrowIv.clearAnimation();//清除刷新箭头动画
headerArrowIv.setVisibility(View.VISIBLE);//显示刷新箭头
break;
}
}

//刷新监听方法,用于刷新接口实现
public void setRefreshListener(OnRefreshListener refreshListener) {
this.refreshListener = refreshListener;
}

//刷新回调接口,用于回调刷新方法
public interface OnRefreshListener {
void onRefresh();
}

//刷新完成调用方法
public void refreshComplete() {
//刷新状态设为完成状态
state = DONE;
//设置刷新时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
headerTimeTv.setText(sdf.format(new Date()));
//更新header信息
changeViewByState();
}

//加载监听方法,用于加载接口的实现
public void setLoaderListener(OnLoaderListener onLoaderListener) {
this.onLoaderListener = onLoaderListener;
}

// 加载回调接口,用于回调加载方法
public interface OnLoaderListener {
void onLoad();
}

//加载完毕调用方法
public void loadComplete() {
isLoading = false;
footerView.findViewById(R.id.my_listview_footer_load_layout).setVisibility(GONE);
}
}


[/code]


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: