分析:
解析上图:
好了,既然知道了这图形的意思,那么我们就来自定义我们的listview吧。
首先先解释一下怎么实现,我们在listview加入了头部(HeaderView)用来刷新提示,加入了尾部(用来加载更多的提示),当然在没有产生这两个动作之前,这两个是要隐藏的,只有我们做了相应的动作之后才会有显示的。
public class RefreshListView extends ListView {
//重写构造方法
public RefreshListView(Context context) {
//在这里我们用到了this这个关键字,这样一层调一层,不用每个构造方法中都去调用我们的初始化方法了
this(context, null);
}
public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
initFooterView();
}
}
在这里我们先看下布局吧
<?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" android:padding="10dp"> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="20dp"> <ImageView android:id="@id/ivImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@mipmap/common_listview_headview_red_arrow"/> <ProgressBar android:visibility="invisible" android:id="@+id/progressBar" <!--在这里我们自定义了ProgressBar的样式,系统自带的不太好看--> android:indeterminateDrawable="@drawable/custom_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> </FrameLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <TextView android:id="@id/tvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="@color/red_lovenews" android:textSize="24dp"/> <TextView android:id="@id/tvData" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="最后刷新时间:2016-01-17 14:54:07" android:textSize="18dp"/> </LinearLayout> </LinearLayout>
自定义的ProgressBar的样式
<?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="5dp" android:useLevel="false">
<gradient android:centerColor="#3F00" android:endColor="#F00" android:startColor="#FFF"/>
</shape>
</rotate>
在RefreshListView中初始化布局控件
/** * 初始化布局控件 */
private void initViews() {
tvData = (TextView) mHeaderView.findViewById(R.id.tvData);
tvData.setText("最后刷新时间:" + getCurrentTime());
tvTitle = (TextView) mHeaderView.findViewById(R.id.tvTitle);
progressBar = (ProgressBar) mHeaderView.findViewById(R.id.progressBar);
ivImage = (ImageView) mHeaderView.findViewById(R.id.ivImage);
}
/** * 初始化头布局 */
private void initHeaderView() {
mHeaderView = View.inflate(getContext(), R.layout.refresh_header, null);
this.addHeaderView(mHeaderView);
initViews();
initAnimation();
//测量布局
mHeaderView.measure(0, 0);
//获得HeaderView的高度,这个要在测量(measure)之后才能获取到
measuredHeight = mHeaderView.getMeasuredHeight();
//隐藏HeaderView(只有在下拉的时候才去显示)
mHeaderView.setPadding(0, -measuredHeight, 0, 0);
}
当然了,既然要下拉刷新,改变布局,就要相应触摸事件了,在这里我们先定义三种STATE状态
/** * 下拉刷新 */
private static final int STATE_PULL_REFRESH = 0;
/** * 松开刷新 */
private static final int STATE_RELEASE_REFRESH = 1;
/** * 正在刷新 */
private static final int STATE_REFRESHING = 2;
/** * 当前刷新状态 */
private static int CURRENT_STATE = STATE_PULL_REFRESH;
重写onTouchEvent方法
在这里实现逻辑,解释在代码里面,在这里直接上代码:
/** * 点击触摸事件 * @param ev * @return */
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
/** * 获取到按下的时候的坐标 */
startY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1) {
startY = ev.getRawY();
}
/** * 获取到移动的时候的坐标 */
float endY = ev.getRawY();
/** * 移动偏移量 */
float dy = endY - startY;
//dy大于0的时候才能出来,并且是第一个元素item显示
//也就是说,只有当我们下拉的显示的是第一个item(在下拉的时候表示我们要去刷新了)
//getFirstVisiblePosition()获取到第一个item显示的位置
if (dy > 0 && getFirstVisiblePosition() == 0) {
//得到padding值
int padding = (int) (dy - measuredHeight);
//设置HeaderView的padding值
mHeaderView.setPadding(0, padding, 0, 0);
if (padding > 0) {
//状态改为松开刷新
CURRENT_STATE = STATE_RELEASE_REFRESH;
} else if (padding < 0 && CURRENT_STATE != STATE_PULL_REFRESH) {
//如果padding<0 ,并且状态不是下拉刷新的时候,我们要设置状态为下拉刷新,因为这个时候达不到执行其他状态的条件(padding>=0)
CURRENT_STATE = STATE_PULL_REFRESH;
}
return true;
}
break;
case MotionEvent.ACTION_UP:
//重置
startY = -1;
if (CURRENT_STATE == STATE_RELEASE_REFRESH) {
CURRENT_STATE = STATE_REFRESHING;
//显示
mHeaderView.setPadding(0, 0, 0, 0);
refreshState();
} else if (CURRENT_STATE == STATE_PULL_REFRESH) {
//隐藏标题
//这种情况是没有达到刷新的条件
mHeaderView.setPadding(0, -measuredHeight, 0, 0);
}
default:
break;
}
return super.onTouchEvent(ev);
}
/** * 根据当前状态改变刷新的时候的显示 */
private void refreshState() {
switch (CURRENT_STATE) {
case STATE_PULL_REFRESH:
tvTitle.setText("下拉刷新");
ivImage.setVisibility(VISIBLE);
progressBar.setVisibility(INVISIBLE);
//箭头下拉动画
ivImage.startAnimation(animDown);
break;
case STATE_RELEASE_REFRESH:
tvTitle.setText("松开刷新");
ivImage.setVisibility(VISIBLE);
progressBar.setVisibility(INVISIBLE);
//箭头上升动画
ivImage.startAnimation(animUp);
break;
case STATE_REFRESHING:
tvTitle.setText("正在刷新");
//必须先清除动画,才能隐藏
ivImage.clearAnimation();
ivImage.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
if (mListener != null) {
mListener.onRefresh();
}
break;
default:
break;
}
}
private void initAnimation() {
/** * 箭头向上的动画 */
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);
animUp.setDuration(200);
animUp.setFillAfter(true);
}
获取系统时间
/** * 获得系统当前的时间 * @return */
public String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String currentTime = sdf.format(new Date());
return currentTime;
}
既然下拉已经写好了,这个时候我们就要对外界提供调用的接口了,要不然外界怎么去调用我们写的呢。
OnRefreshListener mListener;
public void setOnRefreshListener(OnRefreshListener listener) {
mListener = listener;
}
/** * 下拉刷新和上拉加载更多接口 */
public interface OnRefreshListener {
//刷新
void onRefresh();
//加载更多
void onLoadMore();
}
好了,下拉逻辑基本完成,下面我们看下上拉加载更多逻辑
/** * 初始化FooterView */
private void initFooterView() {
//加载FooterView布局
mFooterView = View.inflate(getContext(), R.layout.refresh_listview_footer, null);
//添加
this.addFooterView(mFooterView);
//测量mFooterView
mFooterView.measure(0, 0);
//获取到mFooterView的高度
mFotterViewHeight = mFooterView.getMeasuredHeight();
//隐藏mFooterView
mFooterView.setPadding(0, -mFotterViewHeight, 0, 0);
//设置滑动监听事件
this.setOnScrollListener(this);
}
<?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:id="@+id/pb_pull_list_header" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="5dp" android:indeterminateDrawable="@drawable/custom_progress" />
<TextView android:id="@+id/tv_pull_list_header_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加载中..." android:textColor="#ff0000" android:textSize="18sp" />
</LinearLayout>
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
/** * SCROLL_STATE_IDLE 滑动停止 * SCROLL_STATE_FLING 滑动完成 * 在停止滑动并且滑动停止的时候判断 */
if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) {
//判断是否显示的是最后一个item,并且是否是加载更多的事件
if (getLastVisiblePosition() == getCount() -1&& !isLoadingMore) {
//滑动到最后
//Log.d("RefreshListView", "滑动到底了");
//显示mFooterView
mFooterView.setPadding(0,0,0,0);
//改变listview显示的位置
setSelection(getCount());
//加载更多设置为true
isLoadingMore = true;
if (mListener != null){
//调用接口 加载更多的接口
mListener.onLoadMore();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
在这下面我们对头和尾进行处理,当加载是否完成,我们都要去隐藏头尾布局,恢复设置
/** * 加载完成 要隐藏我们的头布局和尾布局 * * @param success */
public void OnRefreshComplete(boolean success) {
if (isLoadingMore){
//隐藏
mFooterView.setPadding(0,-mFotterViewHeight,0,0);
//加载更多设置为false
isLoadingMore = false;
} else {
//
CURRENT_STATE = STATE_PULL_REFRESH;
tvTitle.setText("下拉刷新");
ivImage.setVisibility(VISIBLE);
progressBar.setVisibility(INVISIBLE);
mHeaderView.setPadding(0, -measuredHeight, 0, 0);
if (success) {
tvData.setText("最后刷新时间:" + getCurrentTime());
}
}
}
基本上拉加载更多和下拉刷新初步完成,当然了,这个只是动画,和上拉下拉响应,完成加载和刷新还是得在你获取数据的时候处理,也就是说,我们可以给listview设置,比如:
/** * 设置下拉刷新的事件监听 */
mListView.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
/** * 下拉刷新 */
@Override
public void onRefresh() {
//这个就是下拉刷新,重新获取服务器数据即可
getDataFromServer();
}
/** * 上拉加载更多 */
@Override
public void onLoadMore() {
if (mMoreUrl != null) {
//这个就是获取下一页数据(加载更多)
getMoreDataFromServer();
} else {
Toast.makeText(mActivity, "已经是最后一页了", Toast.LENGTH_SHORT).show();
mListView.OnRefreshComplete(false); //收起脚部局
}
}
});
接下来,在来优化一下,存在的一个毛病,或者我们优化一下会更好,就是我们加入了头布局,这样,我们在对listview设置触摸监听事件的时候,由于加入了头布局,那我们在 点击获取 item的position的时候,会获取到的和我们想要的是不一样的额,这是因为我们的头布局加入了,就会把我们的listview的条目增加,然后获取到的position也会增加,好了,我们来稍微改一下代码,实现点击还是获取到我们想要的效果。
@Override
public void setOnItemClickListener(OnItemClickListener listener) {
/** * 把当前的mItemClickListener传递到底层,重写 */
super.setOnItemClickListener(this);
mItemClickListener = listener;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mItemClickListener !=null){
//减去两个头布局getHeaderViewsCount()
//getHeaderViewsCount()获取到头布局的个数
//在这里我们对onItemClick中的position直接减去我们的headerview
//然后我们在重写这个setOnItemClickListener(),方法,把我们这个修改过的事件监听传给当前,然后我们在我们的代码中使用的时候,就去重写我们修改过后的setOnItemClickListener()这个方法
mItemClickListener.onItemClick(parent,view,position - getHeaderViewsCount(),id);
}
}
好了,到此,自定义的ListView下拉刷新和上拉加载已经全部完成了,逻辑就那么多,希望有帮助,也是我的总结。
接下来看下完整代码:
package com.example.lovenews.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.lovenews.R;
import java.text.SimpleDateFormat;
import java.util.Date;
/** * Created by 若兰 on 2016/1/17. * 一个懂得了编程乐趣的小白,希望自己 * 能够在这个道路上走的很远,也希望自己学习到的 * 知识可以帮助更多的人,分享就是学习的一种乐趣 * QQ:1069584784 * csdn:http://blog.csdn.net/wuyinlei */
public class RefreshListView extends ListView implements AbsListView.OnScrollListener, 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 static int CURRENT_STATE = STATE_PULL_REFRESH;
/** * Y轴坐标 */
private float startY;
/** * 头布局 */
private View mHeaderView;
/** * 尾布局 */
private View mFooterView;
/** * 头布局高度 */
private int measuredHeight;
/** * 尾布局高度 */
private int mFotterViewHeight;
/** * 布局控件 */
private ImageView ivImage;
private ProgressBar progressBar;
private TextView tvTitle, tvData;
/** * 箭头动画 */
private RotateAnimation animUp, animDown;
public RefreshListView(Context context) {
this(context, null);
}
public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
initFooterView();
}
/** * 初始化头布局 */
private void initHeaderView() {
mHeaderView = View.inflate(getContext(), R.layout.refresh_header, null);
this.addHeaderView(mHeaderView);
initViews();
initAnimation();
//测量布局
mHeaderView.measure(0, 0);
//获得HeaderView的高度,这个要在测量(measure)之后才能获取到
measuredHeight = mHeaderView.getMeasuredHeight();
//隐藏HeaderView
mHeaderView.setPadding(0, -measuredHeight, 0, 0);
}
/** * 初始化布局控件 */
private void initViews() {
tvData = (TextView) mHeaderView.findViewById(R.id.tvData);
tvData.setText("最后刷新时间:" + getCurrentTime());
tvTitle = (TextView) mHeaderView.findViewById(R.id.tvTitle);
progressBar = (ProgressBar) mHeaderView.findViewById(R.id.progressBar);
ivImage = (ImageView) mHeaderView.findViewById(R.id.ivImage);
}
/** * 点击触摸事件 * @param ev * @return */
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
/** * 获取到按下的时候的坐标 */
startY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1) {
startY = ev.getRawY();
}
if (CURRENT_STATE == STATE_REFRESHING) {
break;//正在刷新的时候,不做处理
}
/** * 获取到移动的时候的坐标 */
float endY = ev.getRawY();
/** * 移动偏移量 */
float dy = endY - startY;
//dy大于0的时候才能出来,并且是第一个元素item显示
if (dy > 0 && getFirstVisiblePosition() == 0) {
//得到padding值
int padding = (int) (dy - measuredHeight);
//设置HeaderView的padding值
mHeaderView.setPadding(0, padding, 0, 0);
if (padding > 0) {
//状态改为松开刷新
CURRENT_STATE = STATE_RELEASE_REFRESH;
refreshState();
} else if (padding < 0 && CURRENT_STATE != STATE_PULL_REFRESH) {
CURRENT_STATE = STATE_PULL_REFRESH;
}
return true;
}
break;
case MotionEvent.ACTION_UP:
//重置
startY = -1;
if (CURRENT_STATE == STATE_RELEASE_REFRESH) {
CURRENT_STATE = STATE_REFRESHING;
//显示
mHeaderView.setPadding(0, 0, 0, 0);
refreshState();
} else if (CURRENT_STATE == STATE_PULL_REFRESH) {
//隐藏标题
mHeaderView.setPadding(0, -measuredHeight, 0, 0);
}
default:
break;
}
return super.onTouchEvent(ev);
}
/** * 根据当前状态改变刷新的时候的显示 */
private void refreshState() {
switch (CURRENT_STATE) {
case STATE_PULL_REFRESH:
tvTitle.setText("下拉刷新");
ivImage.setVisibility(VISIBLE);
progressBar.setVisibility(INVISIBLE);
ivImage.startAnimation(animDown);
break;
case STATE_RELEASE_REFRESH:
tvTitle.setText("松开刷新");
ivImage.setVisibility(VISIBLE);
progressBar.setVisibility(INVISIBLE);
ivImage.startAnimation(animUp);
break;
case STATE_REFRESHING:
tvTitle.setText("正在刷新");
//必须先清除动画,才能隐藏
ivImage.clearAnimation();
ivImage.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
if (mListener != null) {
mListener.onRefresh();
}
break;
default:
break;
}
}
private void initAnimation() {
/** * 箭头向上的动画 */
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);
animUp.setDuration(200);
animUp.setFillAfter(true);
}
OnRefreshListener mListener;
public void setOnRefreshListener(OnRefreshListener listener) {
mListener = listener;
}
/** * 下拉刷新和上拉加载更多接口 */
public interface OnRefreshListener {
void onRefresh();
void onLoadMore();
}
/** * 加载完成 要隐藏我们的头布局和尾布局 * * @param success */
public void OnRefreshComplete(boolean success) {
if (isLoadingMore){
//隐藏
mFooterView.setPadding(0,-mFotterViewHeight,0,0);
//加载更多设置为false
isLoadingMore = false;
} else {
//
CURRENT_STATE = STATE_PULL_REFRESH;
tvTitle.setText("下拉刷新");
ivImage.setVisibility(VISIBLE);
progressBar.setVisibility(INVISIBLE);
mHeaderView.setPadding(0, -measuredHeight, 0, 0);
if (success) {
tvData.setText("最后刷新时间:" + getCurrentTime());
}
}
}
/** * 获得系统当前的时间 * @return */
public String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String currentTime = sdf.format(new Date());
return currentTime;
}
/** * 初始化FooterView */
private void initFooterView() {
//加载FooterView布局
mFooterView = View.inflate(getContext(), R.layout.refresh_listview_footer, null);
//添加
this.addFooterView(mFooterView);
//测量mFooterView
mFooterView.measure(0, 0);
//获取到mFooterView的高度
mFotterViewHeight = mFooterView.getMeasuredHeight();
//隐藏mFooterView
mFooterView.setPadding(0, -mFotterViewHeight, 0, 0);
//设置滑动监听事件
this.setOnScrollListener(this);
}
/** * 是否加载更多 */
private boolean isLoadingMore ;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
/** * SCROLL_STATE_IDLE 滑动停止 * SCROLL_STATE_FLING 滑动完成 * 在停止滑动并且滑动停止的时候判断 */
if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) {
//判断是否显示的是最后一个item,并且是否是加载更多的事件
if (getLastVisiblePosition() == getCount() -1&& !isLoadingMore) {
//滑动到最后
//Log.d("RefreshListView", "滑动到底了");
//显示mFooterView
mFooterView.setPadding(0,0,0,0);
//改变listview显示的位置
setSelection(getCount());
//加载更多设置为true
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(OnItemClickListener listener) {
/** * 把当前的mItemClickListener传递到底层,重写 */
super.setOnItemClickListener(this);
mItemClickListener = listener;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mItemClickListener !=null){
//减去两个头布局getHeaderViewsCount()
mItemClickListener.onItemClick(parent,view,position - getHeaderViewsCount(),id);
}
}
}