import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;
public class MyListView extends ListView implements OnScrollListener {
private View footer; //下拉刷新
private View header; //分页
private int firstVisibleItem; //第一个可见列表项的下标
private int lastVisibleItem; //最后一个可见列表项的下标
private int totalItemCount; //总共有多少个列表项
private int scrollState; //listView当前滚动状态
private boolean isLoading;
/**
* 分页加载接口
*/
private ILoadListener iLoadListener;
/**
* 刷新数据接口
*/
private IReflashListener iReflashListener;
/**
*
*/
private LayoutInflater inflater;
/**
* 下拉刷新头部的高度
*/
private int headerHeight;
private boolean isTopDown; //标记是否在列表的顶端按下
private int startY; //在列表的顶端按下的Y值
private int state; //当前的状态
private final int NONE = 0; //正常状态
private final int PULL = 1; //提示下拉状态
private final int RELEASE = 2; //提示释放状态
private final int REFLASHING = 3; //刷新状态
public MyListView(Context context) {
super(context);
// TODO Auto-generated constructor stub
initView(context);
}
public MyListView(Context context, boolean hasHeader, boolean hasFooter) {
super(context);
initView(context);
if (hasHeader) {
setDefaultHeaderView();
}
if (hasFooter) {
setDefaultFooterView();
}
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
initView(context);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
initView(context);
}
/**
* 设置底部刷新的布局
* @param viewId 布局的id(R.layout....)
*/
public void setFooterView(int viewId) {
footer = inflater.inflate(viewId, null);
footer.setVisibility(View.GONE);
this.addFooterView(footer);
}
/**
* 设置默认的底部刷新布局
*/
public void setDefaultFooterView() {
footer = inflater.inflate(R.layout.lv_footer_layout, null);
footer.setVisibility(View.GONE);
this.addFooterView(footer);
}
/**
* 设置头部刷新布局
* @param viewId 布局的id(R.layout....)
*/
public void setHeaderView(int viewId) {
header = inflater.inflate(viewId, null);
header.setVisibility(View.GONE);
this.addHeaderView(header);
}
/**
* 设置默认的头部刷新布局
*/
public void setDefaultHeaderView() {
header = inflater.inflate(R.layout.lv_header_layout, null);
measureView(header);
hideHeader();
this.addHeaderView(header);
}
/**
* 添加底部加载提示布局到listView
* @param context
*/
private void initView(Context context) {
this.inflater = LayoutInflater.from(context);
setOnScrollListener(this);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
this.scrollState = scrollState;
/*
* 若滑动到ListView的底部,并且不再滑动状态的时候,显示分页刷新脚布局
* 否则隐藏该布局
*/
if (scrollState == SCROLL_STATE_IDLE && lastVisibleItem == totalItemCount) {
if (!isLoading) {
showFooter();
//通过回调方法实现加载数据
iLoadListener.onLoad();
isLoading = false;
}
} else {
hideFooter();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
//记录第一行课件列表项的下标
this.firstVisibleItem = firstVisibleItem;
//计算最后一行课件列表项的下标
this.lastVisibleItem = firstVisibleItem + visibleItemCount;
//总共有多少个列表项
this.totalItemCount = totalItemCount;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
/**
* 这里实现下拉刷新的功能,通过监听手势的按下,移动,松开来实现
*/
switch (ev.getAction()) {
/*
* 当按下屏幕的时候,如果第一行可见的列表项恰好是列表项的第一个时,把isTopDown标记值设置成true
* 并记录开始的时候的Y坐标
*/
case MotionEvent.ACTION_DOWN:
if (firstVisibleItem == 0) {
isTopDown = true;
startY = (int) ev.getY();
}
break;
/**
* 当动作是移动的时候,这时就要处理图片该如何显示的问题,因为下拉高度不同显示的图片和文字都不同
*/
case MotionEvent.ACTION_MOVE:
onMove(ev);
break;
/**
* 当动作是松开的时候,要判断两种状态:
* 1.如果下拉的高度足够,RELEASE也就是,那么就可以刷新,这时把状态设置成REFLASHING
* 2.如果下拉的状态不够,那么不能刷新,这时把状态设置成NONE,并且把标识isTopDown设置成false
*/
case MotionEvent.ACTION_UP:
if (state == RELEASE) {
state = REFLASHING;
//设置界面应该如何显示
reflashViewByState();
setHeaderPaddingTop(10);
//加载数据
iReflashListener.onReflash();
} else if (state == PULL) {
state = NONE;
isTopDown = false;
//设置界面应该如何显示
reflashViewByState();
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
private void onMove(MotionEvent ev) {
// TODO Auto-generated method stub
//如果该移动操作不是在顶部下拉的,那么不执行操作,直接返回
if (!isTopDown) {
return;
}
int tempY = (int) ev.getY();
//记录下拉了多少距离
int space = tempY - startY;
int topPadding = space - headerHeight;
switch (state) {
case NONE:
if (space > 0) {
state = PULL;
reflashViewByState();
}
break;
case PULL:
setHeaderPaddingTop(topPadding);
if (space > headerHeight + 30
&& scrollState == SCROLL_STATE_TOUCH_SCROLL) {
state = RELEASE;
reflashViewByState();
}
break;
case RELEASE:
setHeaderPaddingTop(topPadding);
if (space < headerHeight + 30) {
state = PULL;
reflashViewByState();
} else if (space <= 0) {
state = NONE;
isTopDown = false;
reflashViewByState();
}
break;
}
}
private void reflashViewByState() {
TextView tip = (TextView) header.findViewById(R.id.tv_flash);
ImageView arrow = (ImageView) header.findViewById(R.id.iv_flash);
ProgressBar progress = (ProgressBar) header.findViewById(R.id.pb_flash);
RotateAnimation anim1 = new RotateAnimation(0, 180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
anim1.setDuration(500);
anim1.setFillAfter(true);
RotateAnimation anim2 = new RotateAnimation(180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
anim2.setDuration(500);
anim2.setFillAfter(true);
switch (state) {
case NONE:
setHeaderPaddingTop(-headerHeight);
arrow.clearAnimation();
break;
case PULL:
arrow.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
tip.setText("下拉可以刷新");
arrow.clearAnimation();
arrow.setAnimation(anim2);
break;
case RELEASE:
arrow.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
tip.setText("释放立即刷新");
arrow.clearAnimation();
arrow.setAnimation(anim1);
break;
case REFLASHING:
setHeaderPaddingTop(headerHeight);
arrow.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
tip.setText("正在刷新");
arrow.clearAnimation();
break;
default:
break;
}
}
public void reflashComplete() {
state = NONE;
isTopDown = false;
reflashViewByState();
}
public void setILoadListener(ILoadListener iLoadListener) {
this.iLoadListener = iLoadListener;
}
public interface ILoadListener {
public void onLoad();
}
/**
* 刷新数据接口
* @author raid
*
*/
public interface IReflashListener {
public void onReflash();
}
public void setIReflashListener(IReflashListener iReflashListener) {
this.iReflashListener = iReflashListener;
}
/**
* 加载完毕
*/
public void loadComplete() {
isLoading = false;
hideFooter();
}
/**
* 如果用户不需要分页功能,没有设置footer,
* 那么隐藏footer的时候需要判断footer是否为空
*/
private void hideFooter() {
if (footer != null) {
footer.setVisibility(View.GONE);
}
}
/**
* 如果用户不需要分页功能,没有设置footer,
* 那么显示footer的时候需要判断footer是否为空
*/
private void showFooter() {
if (footer != null) {
footer.setVisibility(View.VISIBLE);
}
}
/**
* 隐藏header
*/
private void hideHeader() {
headerHeight = header.getMeasuredHeight();
setHeaderPaddingTop(-headerHeight);
}
/**
* 设置组件的上内边距
* @param paddingTop 上内边距
*/
private void setHeaderPaddingTop(int paddingTop) {
header.setPadding(header.getPaddingLeft(),
paddingTop, header.getPaddingRight(),
header.getPaddingBottom());
header.invalidate();
}
/**
* 由于Android程序的运行机制决定了无法再组件类外部使用getWidth和getHeight方法
* 获得高度和宽度(在自定义组件类中可以实现),必须使用View.getMeasuredWidth
* 和View.getMeasureHeight方法获得当前组件的宽度和高度,
* 在调用这两个方法之前,必须调用View.measure方法先测量组件宽度和高度。
* 如果想直接获取在布局文件中定义的组件的宽度和高度,
* 可以直接使用View.getLayoutParams().width和View.getLayoutParams()...
*
* 通知父布局,占用的宽,高
* @param view
*/
private void measureView(View view) {
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* 有三个参数,分别代表:
* 1.父View的详细测量值(即MeasureSpec)
* 2.view的内外边距
* 3.子布局的宽度
*/
int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int height;
int tempHeight = p.height;
if (tempHeight > 0) {
height = MeasureSpec.
makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY);
} else {
height = MeasureSpec.
makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
}
lv_footer_layout.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:orientation="horizontal"
android:padding="15dp"
android:gravity="center" >
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="10dp"
android:text="加载中..."/>
</LinearLayout>
lv_header_layout.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:orientation="horizontal"
android:padding="15dp"
android:gravity="center"
android:background="#5E5E5D" >
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp">
<ImageView
android:id="@+id/iv_flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="@drawable/pull_to_refresh_arrow"/>
<ProgressBar
android:id="@+id/pb_flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"/>
</RelativeLayout>
<TextView
android:id="@+id/tv_flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以刷新"
android:textSize="20sp"
android:textColor="#EBEDEC"/>
</LinearLayout>