偷懒了一天,去给人装系统去了~ 今天继续更 自己敲代码实现下拉刷新上滑加载更多
"你会装系统吗?""会""我电脑打不开了""好我马上到"....然后就是从WIN7开始装 装完直接装WIN10 然后装软件...然后..........妹子为什么不能自己学学装系统呢......
下拉刷新的原理很简单,下拉刷新也是listview的头布局 只不过靠setPadding(0,-PaddingTop,0,0),让他隐藏在头部局上面(这么说只是为了更形象,其实是头部的一条缝)
下拉刷新的原理就是这样. 下面来敲代码 自定义我们的refreshListView
第一步 先实现我们的下拉刷新这几个字
网上下的后台资源 JSON数据有点旧 不过这不是重点,第一步 我们给listview添加下拉刷新的头布局, 很简单就不多说.
<?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:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingTop="10dp" >
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/common_listview_headview_red_arrow" />
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible"
android:indeterminateDrawable="@drawable/custom_progress" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
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="17sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="2016-02-16 22:01"
android:textColor="#9e9e9e"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
自带progressbar的太丑了简单自定义了一个 旋转动画和渐变效果 我设置
android:visibility="invisible"
刷新ing的时候再设置可见.
custom_progress
<?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="15dp"
android:shape="ring"
android:thickness="3dp"
android:useLevel="false" >
<gradient
android:centerColor="#5f00"
android:endColor="#fff"
android:startColor="#f00"
android:type="sweep" />
</shape>
</rotate>
2 这一步我们要他隐藏:
那么 我们在我们的RefreshListView中先拿到头布局的高度 并把它隐藏:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/**
* 初始化头布局
*/
private void initHeaderView() {
mHeaderView = View.inflate(getContext(), R.layout.list_refrsh_header,
null);
this.addHeaderView(mHeaderView);// 添加头布局
// 隐藏头布局(1, 获取头布局高度, 2.设置负paddingTop,布局就会往上走)
// int height = mHeaderView.getHeight();//此处无法获取高度,因为布局还没有绘制完成
// 绘制之前就要获取布局高度
mHeaderView.measure(0, 0);// 手动测量布局
mHeaderViewHeight = mHeaderView.getMeasuredHeight();// 测量之后的高度
// 隐藏头布局
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title);
ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeaderView.findViewById(R.id.pb_loading);
tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time);
}
3 这样就看不到了. 那么下部我们应该把它拉出来, 通过获得手指在Y轴上的移动距离dy , 当dy-mHeaderViewHeight > 0 且是第一个可见条目的id == 0 那么 头布局可见
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1) {// 如果用户按住头条新闻向下滑动, 会导致listview无法拿到ACTION_DOWN,
// 此时要重新获取startY
startY = (int) ev.getY();
}
// 如果当前正在刷新, 什么都不做了
if (mCurrentState == STATE_REFRESHING) {
break;
}
int endY = (int) ev.getY();
int dy = endY - startY;
if (dy > 0 && getFirstVisiblePosition() == 0) {// 向下滑动&当前显示的是第一个item,才允许下拉刷新
int paddingTop = dy - mHeaderViewHeight;// 计算当前的paddingtop值
// 根据padding切换状态
if (paddingTop >= 0
&& mCurrentState != STATE_RELEASE_TO_REFRESH) {
// 切换到松开刷新
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
} else if (paddingTop < 0
&& mCurrentState != STATE_PULL_TO_REFRESH) {
// 切换到下拉刷新
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
mHeaderView.setPadding(0, paddingTop, 0, 0);// 重新设置头布局padding
return true;
}
break;
case MotionEvent.ACTION_UP:
startY = -1;// 起始坐标归零
if (mCurrentState == STATE_RELEASE_TO_REFRESH) {
// 如果当前是松开刷新, 就要切换为正在刷新
mCurrentState = STATE_REFRESHING;
// 显示头布局
mHeaderView.setPadding(0, 0, 0, 0);
refreshState();
// // 下拉刷新回调
// if (mListener != null) {
// mListener.onRefresh();
// }
} else if (mCurrentState == STATE_PULL_TO_REFRESH) {
// 隐藏头布局
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
/**
* 初始化箭头动画
*/
private void initAnim() {
animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animUp.setDuration(500);
animUp.setFillAfter(true);// 保持状态
animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animDown.setDuration(500);
animDown.setFillAfter(true);// 保持状态
}
/**
* 根据当前状态刷新界面
*/
private void refreshState() {
switch (mCurrentState) {
case STATE_PULL_TO_REFRESH:
tvTitle.setText("下拉刷新");
// 箭头向下移动
ivArrow.startAnimation(animDown);
// 隐藏进度条
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
break;
case STATE_RELEASE_TO_REFRESH:
tvTitle.setText("松开刷新");
// 箭头向上移动
ivArrow.startAnimation(animUp);
// 隐藏进度条
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
break;
case STATE_REFRESHING:
tvTitle.setText("正在刷新...");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation();// 必须清除动画,才能隐藏控件
ivArrow.setVisibility(View.INVISIBLE);
break;
default:
break;
}
}
4 我们暂时完成了下拉刷新 是不是有点激动! 原理居然这么简单!说白了就是个paddingTop再加入状态判断和设置.
但是我们现在的下拉刷新不会回去. 那么我们来实现它回去的逻辑 ,下拉刷新接口回调刷新数据后会返回成功或者失败 ,那么 我们创建一个方法
onRefreshComplete
来通知listview 是成功还是失败然后做相应的UI操作
/** * 从服务器获取数据 */ private void getDataFromSevice() { HttpUtils httpUtils = new HttpUtils(); httpUtils.send(HttpMethod.GET, mUrl, new RequestCallBack<String>() { @Override public void onSuccess(ResponseInfo<String> responseInfo) { // 请求成功调用 String result = responseInfo.result;// 获得json字符串 processResult(result); CacheUtils.setCache(mUrl, result, mActivity); mListView.onRefreshComplete(true); } @Override public void onFailure(HttpException error, String msg) { // 请求失败调用 error.printStackTrace(); Toast.makeText(mActivity, msg, Toast.LENGTH_LONG).show(); mListView.onRefreshComplete(false); } }); }
RefreshListView: 不管成功还是失败都隐藏掉
// 刷新完成 public void onRefreshComplete(boolean success) { if (success) { // 隐藏头布局 mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0); mCurrentState = STATE_PULL_TO_REFRESH; // 隐藏进度条 pbLoading.setVisibility(View.INVISIBLE); ivArrow.setVisibility(View.VISIBLE); tvTitle.setText("下拉刷新"); setCurrentTime(); }else{ // 隐藏头布局 mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0); mCurrentState = STATE_PULL_TO_REFRESH; // 隐藏进度条 pbLoading.setVisibility(View.INVISIBLE); ivArrow.setVisibility(View.VISIBLE); tvTitle.setText("下拉刷新"); Toast.makeText(getContext(), "刷新失败请检查网络连接", Toast.LENGTH_LONG).show(); } }
现在是这样 我把tomcat服务器关了:
5 继续. 上滑加载更多: 原理 添加listview脚布局 然后通过设置-paddingTop隐藏掉
跟下拉过程一样 只不过回调的链接不一样 上拉加载更多的链接一般是more : "/10007/list_2.json"这样的 需要重新解析more的json数据
首先 新建一个脚布局:
很简单 一个ProgressBar 和一个TextView
<?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" android:orientation="horizontal" > <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminateDrawable="@drawable/custom_progress" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:text="加载中..." android:textColor="#f00" android:textSize="17sp" /> </LinearLayout>
设置给我们的 RefreshListView
/** * 初始化脚布局 */ private void initFooterView() { mFooterView = View.inflate(getContext(), R.layout.list_refresh_footer, null); this.addFooterView(mFooterView); mFooterView.measure(0, 0); mFooterViewHeight = mFooterView.getMeasuredHeight(); }
好 大概就这样了.
6 隐藏它 并设置监听 当listview是可见的最后一个条目的时候显示并加载更多
/** * 初始化脚布局 */ private void initFooterView() { mFooterView = View.inflate(getContext(), R.layout.list_refresh_footer, null); this.addFooterView(mFooterView); mFooterView.measure(0, 0); mFooterViewHeight = mFooterView.getMeasuredHeight(); // 隐藏脚布局 mFooterView.setPadding(0, -mFooterViewHeight, 0, 0); // 设置滑动监听 this.setOnScrollListener(this); }
监听是否是listview可见的最后一个条目,然后通过接口的回调 来实现加载更多的方法
声明一个全局变量 private boolean isLoadingMore;// 来标记是否正在加载更多
@Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE) { int lastVisiblePosition = getLastVisiblePosition();// 当前界面显示的最后一个item的位置 if (lastVisiblePosition >= getCount() - 1 && !isLoadingMore) { isLoadingMore = true; // System.out.println("到底了"); // 加载更多了....(到底了) // 显示脚布局 mFooterView.setPadding(0, 0, 0, 0); // listview设置当前要展示的item的位置 setSelection(getCount() - 1);// 跳到加载更多item的位置去展示 if (mListener != null) { mListener.loadMore(); } } } }
7 然后再新闻界面页签页面 重写接口的 loadMore方法 并判断more的Url是否为空
// 设置下拉刷新监听 lvList.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { // 从网络加载数据 getDataFromServer(); } @Override public void loadMore() { // 加载更多数据 if (mMoreUrl != null) { System.out.println("加载下一页数据..."); getMoreDataFromServer(); } else { lvList.onRefreshComplete(true);// 收起加载更多布局 Toast.makeText(mActivity, "没有更多数据了", Toast.LENGTH_SHORT) .show(); } } });
8 新建 getMoreDataFromServer(); 来请求more的数据
/** * 加载更多数据 */ protected void getMoreDataFromServer() { HttpUtils utils = new HttpUtils(); utils.send(HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() { @Override public void onSuccess(ResponseInfo<String> responseInfo) { String result = responseInfo.result; processResult(result, true); // 收起加载更多布局 mListView.onRefreshComplete(true); } @Override public void onFailure(HttpException error, String msg) { error.printStackTrace(); Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show(); // 收起加载更多布局 mListView.onRefreshComplete(false); } }); }
9 然后新建一个boolean 用来判断是是否是ismore 不是的话就是普通的加载
protected void processResult(String result, Boolean isMore) { Gson gson = new Gson(); NewsDatas mNewsDatas = gson.fromJson(result, NewsDatas.class); if (!TextUtils.isEmpty(mNewsDatas.data.more) ) { // 初始化地址 mMoreUrl = Contants.SERVER_URL + mNewsDatas.data.more; } else { // 没有下一页了 mMoreUrl = null; } if (!isMore) { // 获取头条新闻数据 mTopNewsList = mNewsDatas.data.topnews; if (mTopNewsList != null) { mTopNewsAdapter = new TopNewsAdapter(); mViewPager.setAdapter(mTopNewsAdapter); // 给页面指示器设置Viewpager mCirclePageIndicator.setViewPager(mViewPager);//将指示器和viewpager绑定 mCirclePageIndicator.setSnap(true); // 给mCirclePageIndicator 设置监听 mCirclePageIndicator.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { // TODO Auto-generated method stub String title = mTopNewsList.get(position).title; topNewsTitle.setText(title); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // TODO Auto-generated method stub } @Override public void onPageScrollStateChanged(int state) { // TODO Auto-generated method stub } }); mCirclePageIndicator.onPageSelected(0);// 将小圆点位置归零, // 解决它会在页面销毁时仍记录上次位置的bug topNewsTitle.setText(mTopNewsList.get(0).title);// 初始化第一页标题 } // 初始化新闻列表 newsListViewData = mNewsDatas.data.news; // System.out.println("新闻列表数据:" + newsListViewData); if (newsListViewData != null) { newsListViewDataAdapter = new MyListViewAdapter(); mListView.setAdapter(newsListViewDataAdapter); } } else { // 加载更多 ArrayList<News> moreData = mNewsDatas.data.news; newsListViewData.addAll(moreData);// 追加数据 newsListViewDataAdapter.notifyDataSetChanged();// 刷新listview } }
10 重写刷新完成的方法 区分隐藏头布局还是 脚布局
// 刷新完成 public void onRefreshComplete(boolean success) { if (!isLoadingMore) { // 隐藏头布局 mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0); mCurrentState = STATE_PULL_TO_REFRESH; // 隐藏进度条 pbLoading.setVisibility(View.INVISIBLE); ivArrow.setVisibility(View.VISIBLE); tvTitle.setText("下拉刷新"); // 刷新失败,不需要更新时间 if (success) { setCurrentTime(); } } else { // 隐藏脚布局 mFooterView.setPadding(0, -mFooterViewHeight, 0, 0); isLoadingMore = false; } }
最终效果: 标题带2的是刷新出来的
项目地址
https://github.com/AceInAndroid/AnYangNews