一、先写头布局view文件:
<?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" >
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp" >
1、箭头:
<ImageView
android:id="@+id/iv_arr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/common_listview_headview_red_arrow" />
2、进度条圆圈(隐藏):
<ProgressBar
android:id="@+id/pb_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/custom_progress"
android:visibility="invisible" />
</FrameLayout>
3、下拉刷新的文字:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新"
android:textColor="#f00"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="最后刷新时间:2015-03-10 17:07:07"
android:textColor="@android:color/darker_gray"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
二、自定义刷新时候的圆环
必须在progressbar里面设置android:indeterminateDrawable才能定义自己想要的环形
<ProgressBar
android:id="@+id/pb_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/custom_progress"
android:visibility="invisible" />
custom_progress.xml外面是旋转动画,里面套一个自定义shape
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" >
<shape
android:innerRadius="12dp"
android:shape="ring"
android:thickness="3dp"
android:useLevel="false" > 这个让环形变成圆圈,缺点是让原来的旋转动画消失了,所以要在外面套上一层自定义旋转动画
<gradient
android:centerColor="#3f00"
android:endColor="#f00"
android:startColor="#fff" />
</shape>
</rotate>
三、自定义的ListView
public class RefreshListView extends ListView implements OnScrollListener,
android.widget.AdapterView.OnItemClickListener {
//首先定义三种状态
private static final int STATE_PULL_REFRESH = 0;// 下拉刷新
private static final int STATE_RELEASE_REFRESH = 1;// 松开刷新
private static final int STATE_REFRESHING = 2;// 正在刷新
private View mHeaderView;
private int startY = -1;// 滑动起点的y坐标
private int mHeaderViewHeight;
private int mCurrrentState = STATE_PULL_REFRESH;// 当前状态
private TextView tvTitle;
private TextView tvTime;
private ImageView ivArrow;
private ProgressBar pbProgress;
private RotateAnimation animUp;
private RotateAnimation animDown;
public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initHeaderView(); //初始化头部view
initFooterView(); //初始化尾部view
}
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
initHeaderView();
initFooterView();
}
public RefreshListView(Context context) {
super(context);
initHeaderView();
initFooterView();
}
/**
* 第一步!初始化头布局
*/
private void initHeaderView() {
//1、找到头布局view
mHeaderView = View.inflate(getContext(), R.layout.refresh_header, null);
//2、添加这个头布局到当前这个自定义ListView的头部
this.addHeaderView(mHeaderView);
tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title);
tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time);
ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arr);
pbProgress = (ProgressBar) mHeaderView.findViewById(R.id.pb_progress);
//3、测量这个头布局,得到头布局view的高度
mHeaderView.measure(0, 0);
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
//4、根据刚刚测量到的头布局高度,隐藏头布局
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
//5、先定义好动画,等到需要的时候再用
initArrowAnim();
//6、getCurrentTime:获取当前时间
tvTime.setText("最后刷新时间:" + getCurrentTime());
}
/*
* 初始化脚布局
*/
private void initFooterView() {
mFooterView = View.inflate(getContext(),
R.layout.refresh_listview_footer, null);
this.addFooterView(mFooterView);
mFooterView.measure(0, 0);
mFooterViewHeight = mFooterView.getMeasuredHeight();
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);// 隐藏
this.setOnScrollListener(this);
}
//二、触摸事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//6、得到落地坐标
startY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//7、 确保startY有效,其实没什么用
if (startY == -1) {
startY = (int) ev.getRawY();
}
//8、 正在刷新时不做处理
if (mCurrrentState == STATE_REFRESHING) {
break;
}
//9、得到移动的时候的即时坐标
int endY = (int) ev.getRawY();
//10、得到移动偏移量
int dy = endY - startY;
//11、只有是下拉并且当前是第一个item,才允许下拉
if (dy > 0 && getFirstVisiblePosition() == 0) {
//12、计算padding
int padding = dy - mHeaderViewHeight;
//13、设置当前头部view的padding
mHeaderView.setPadding(0, padding, 0, 0);
//14、如果padding=0,表示整个头布局已经显示出来了,而且当前的状态不是松开刷新状态,此时要改变状态为松开刷新
if (padding > 0 && mCurrrentState != STATE_RELEASE_REFRESH) {
//15、状态改为松开刷新
mCurrrentState = STATE_RELEASE_REFRESH;
//16、根据当前状态改变view布局
refreshState();
//17、elseif padding小于0,代表没完全拉出来,而且不等于下拉刷新状态
} else if (padding < 0 && mCurrrentState != STATE_PULL_REFRESH) {
//18、改为下拉刷新状态
mCurrrentState = STATE_PULL_REFRESH;
//19、根据当前状态改变view布局
refreshState();
}
//只有是下拉并且当前是第一个item,才允许自己处理
return true;
}
break;
case MotionEvent.ACTION_UP:
startY = -1;// 重置
//20、若松开手的时候是松开刷新状态,将状态变成正在刷新状态
if (mCurrrentState == STATE_RELEASE_REFRESH) {
mCurrrentState = STATE_REFRESHING;// 正在刷新
//21、把padding设置成0,完全显示头部view
mHeaderView.setPadding(0, 0, 0, 0);// 显示
//22、根据当前状态更改头布局view
refreshState();
//23、如果松开手时还是下拉刷新状态,恢复隐藏的位置
} else if (mCurrrentState == STATE_PULL_REFRESH) {
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
/**
* 根据状态修改头布局view
*/
private void refreshState() {
switch (mCurrrentState) {
case STATE_PULL_REFRESH:
tvTitle.setText("下拉刷新");
ivArrow.setVisibility(View.VISIBLE);
pbProgress.setVisibility(View.INVISIBLE);
//设置箭头转向动画
ivArrow.startAnimation(animDown);
break;
case STATE_RELEASE_REFRESH:
tvTitle.setText("松开刷新");
ivArrow.setVisibility(View.VISIBLE);
pbProgress.setVisibility(View.INVISIBLE);
ivArrow.startAnimation(animUp);
break;
case STATE_REFRESHING:
tvTitle.setText("正在刷新...");
// 必须先清除动画,才能隐藏
ivArrow.clearAnimation();
ivArrow.setVisibility(View.INVISIBLE);
pbProgress.setVisibility(View.VISIBLE);
//24、当状态为正在刷新时,检测是否实现了监听回调,若有,调用接口方法,实现回调
if (mListener != null) {
mListener.onRefresh();//告诉调用者,正在刷新状态呢,快点请求网络吧
}
break;
default:
break;
}
}
/**
* 初始化箭头动画
*/
private void initArrowAnim() {
// 箭头向上动画
animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animUp.setDuration(200);
animUp.setFillAfter(true);
// 箭头向下动画
animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animDown.setDuration(200);
animDown.setFillAfter(true);
}
private View mFooterView;
private int mFooterViewHeight;
//25、写一个接口传递消息给外部
OnRefreshListener mListener;
public void setOnRefreshListener(OnRefreshListener listener) {
mListener = listener;
}
public interface OnRefreshListener {
public void onRefresh();
public void onLoadMore();// 加载下一页数据
}
/*
*26、 当外部数据成功获取或失败之后,调用此方法隐藏头/尾布局
*/
public void onRefreshComplete(boolean success) {
if (isLoadingMore) {// 正在加载更多...
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);// 隐藏脚布局
isLoadingMore = false;
} else {
mCurrrentState = STATE_PULL_REFRESH;
tvTitle.setText("下拉刷新");
ivArrow.setVisibility(View.VISIBLE);
pbProgress.setVisibility(View.INVISIBLE);
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏
//如果成功了才更新时间
if (success) {
tvTime.setText("最后刷新时间:" + getCurrentTime());
}
}
}
/**
* 获取当前时间
*/
public String getCurrentTime() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(new Date());
}
private boolean isLoadingMore;
!!! 上拉刷新就implements OnScrollListener,实现未实现的方法:
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE
|| scrollState == SCROLL_STATE_FLING) {
//当滑动的状态是 fling或者空闲状态时,而且getLastVisiblePosition() == getCount() - 1,并且又不是LoadingMore状态,显示尾部布局,改变listview显示位置,设置isLoadingMore = true,最后实现接口回调,请求网络
if (getLastVisiblePosition() == getCount() - 1 && !isLoadingMore) { // 滑动到最后
System.out.println("到底了.....");
mFooterView.setPadding(0, 0, 0, 0);// 显示
setSelection(getCount() - 1);// 改变listview显示位置
isLoadingMore = true;
if (mListener != null) {
mListener.onLoadMore();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
OnItemClickListener mItemClickListener;
@Override
public void setOnItemClickListener(
android.widget.AdapterView.OnItemClickListener listener) {
super.setOnItemClickListener(this);
mItemClickListener = listener;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(parent, view, position
- getHeaderViewsCount(), id);
}
}
}
- 上拉加载更多:
一、在initData时,
得到缓存数据,
1、假如有缓存数据,则调用
parseData(cache, false)解析result,得到“more”,保存下一页地址,然后判断是否加载更多,如果不是的话就把获取到的数据展示在ListView上,若是加载更多,则从原来的集合上追加数据,再notifyDataSetChanged();
2、假如没有缓存数据,则通过请求地址
getDataFromServer
()
网络获取数据,获取成功再解析(parseData)数据,隐藏下拉刷新框,保存result;获取失败则提示吐司,并隐藏下拉刷新框:
public void initData() {
String cache = CacheUtils.getCache(mUrl, mActivity);
if (!TextUtils.isEmpty(cache)) {
parseData(cache, false);
}
getDataFromServer();
二、给ListView设置监听事件
//在initView()时,ListView设置的下拉刷新监听回调
lvList.setOnRefreshListener(new OnRefreshListener() {
//当下拉刷新时,会自动回调此函数加载网络数据
@Override
public void onRefresh() {
getDataFromServer();
}
//当上拉加载时,若mMoreUrl不为空,调用加载更多数据方法
@Override
public void onLoadMore() {
if (mMoreUrl != null) {
getMoreDataFromServer();
} else {
//若
mMoreUrl为空,那就是没有下一页了,则应该收起加载更多的布局Toast.makeText(mActivity, "最后一页了", Toast.LENGTH_SHORT)
.show();
lvList.onRefreshComplete(false);// 收起加载更多的布局
}
}
});
三、网络获取数据
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = (String) responseInfo.result;
System.out.println("页签详情页返回结果:" + result);
parseData(result, false);
//调用ListView的方法,通知ListView隐藏刷新头view
lvList.onRefreshComplete(true);
// 设置缓存
CacheUtils.setCache(mUrl, result, mActivity);
}
@Override
public void onFailure(HttpException error, String msg) {
Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
error.printStackTrace();
lvList.onRefreshComplete(false);
}
});
}
四、网络获取更多(下一页)数据
private void getMoreDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = (String) responseInfo.result;
parseData(result, true);
lvList.onRefreshComplete(true);
}
@Override
public void onFailure(HttpException error, String msg) {
Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
error.printStackTrace();
lvList.onRefreshComplete(false);
}
});
}
五、解析result数据:
protected void parseData(String result, boolean isMore) {
Gson gson = new Gson();
mTabDetailData = gson.fromJson(result, TabData.class);
System.out.println("页签详情解析:" + mTabDetailData);
// 1、在得到数据之后,解析数据,得到“more”中的地址String more = mTabDetailData.data.more;
//
2、若“more”不为空,则拼接下一页的链接地址,赋值给 mMoreUrlif (!TextUtils.isEmpty(more)) {
mMoreUrl = GlobalContants.SERVER_URL + more;
} else {
//3、否则没有下一页mMoreUrl = null;
}
//4、如果不是加载更多,是刷新页面,则ArrayList<TabNewsData> mNewsList(新闻数据集合)= 新获取到的数据
if (!isMore)
{
//5、给集合重新赋值,刷新界面
mNewsList = mTabDetailData . data . news ;if ( mNewsList != null ) {mNewsAdapter = new NewsAdapter();
lvList.setAdapter(mNewsAdapter);
}
} else {
/
/6、如果是加载下一页,需要将数据追加给原来的集合ArrayList<TabNewsData> news = mTabDetailData.data.news;
mNewsList.addAll(news);
mNewsAdapter.notifyDataSetChanged();
}
}