您的位置:首页 > 其它

自定义控件——ListView下拉刷新和上拉加载

2016-10-26 22:52 344 查看

进入冰冷的季节了。好想在被窝敲代码。。。。

首先放一下效果

添加HeadView实现下拉刷新



添加FooterView实现上拉加载



创建类继承ListView ,构造方法继承前两个

/**
* 重写创建时用的构造方法
*
* @param context
*/
public PullPushListView(Context context) {
super(context);
}

/**
* 重写赋值参数时用的构造方法
*
* @param context
* @param attrs
*/
public PullPushListView(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化头部信息
initHeaderView();
//初始化尾部信息
initFooterView();
}


4.下拉刷新的实现原理 通过padding属性,当属性为负数的时候就会隐藏如图:


5.上拉加载同样原理,不再演示–

首先我们分为两部分去学习

第一部分: 下拉部分–

首先我们要想加入一个headView 需要我们对这个要加入到顶部的条目进行布局

<?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">

<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp">

<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/arrow" />

<ProgressBar
android:id="@+id/iv_circle"
style="@android:style/Widget.ProgressBar"
android:layout_width="30dp"
android:layout_height="30dp"
android:lay
4000
out_centerInParent="true"
android:indeterminateDrawable="@drawable/progress_medium_white"
android:visibility="invisible" />
</RelativeLayout>

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:orientation="vertical">

<TextView
android:id="@+id/tv_loadtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="下拉刷新"
android:textColor="#ffff0000"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="上次刷新 2016-10-23 12:41:58" />
</LinearLayout>

</LinearLayout>


1.1 初始化headView,将设定的头部信息隐藏,主要是对要inflate的头部布局进行测量

/**
* 初始化HeaderView
*/
private void initHeaderView() {
//为自定义ListView添加头部信息
headerView = View.inflate(getContext(), R.layout.item_headview, null);
tv_loadtext = (TextView) headerView.findViewById(R.id.tv_loadtext);
iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
pb_circle = (ProgressBar) headerView.findViewById(R.id.iv_circle);
//将progressBar 设定为Gone
pb_circle.setVisibility(View.GONE);
//通知进行布局测量<<<<<<卧槽尼玛的,不能测量RelativeLayout布局开头>>>>>>>
headerView.measure(0, 0);
//获取测量高度,在之前需要测量一下,否则无法获 取正确数据
meatureHeight = headerView.getMeasuredHeight();
//通知进行布局测量
headerView.measure(0, 0);
Log.i(TAG, "PullPushListView: " + meatureHeight);
//添加headerView到页面
super.addHeaderView(headerView);
//整体隐藏headerView
inithideheaderView(-meatureHeight);
headerView.setClickable(false);
STATE_CURRENT = STATE_PULL;

}


1.2 通过onTouchEvent(MotationEvent event);,对触摸事件处理实现动态拉出头部条目,以及涉及到状态的改变

在此之前,我们需要设定三个状态,用于记录当前是那种状态

1.2.1状态可以通过设定三个全局变量

/**
* 下拉状态
*/
private static final int STATE_PULL = 0;
/**
* 松开刷新状态
*/
private static final int STATE_PUSH = 1;
/**
* 正在刷新状态
*/
private static final int STATE_REFRESH = 2;
/**
* 当前状态
*/
private int STATE_CURRENT;


1.2.2 也可以通过获取TextView 中的条目内容获取,不过麻烦,这里舍弃

下面是触摸事件的实现

/**
* headerView 根据传入的数值动态改变HeaderView据上边界的高度
*/
private void inithideheaderView(int top) {
headerView.setPadding(0, top, 0, 0);
}

/**
* 对ListView 添加触摸事件
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//手指按下事件
case MotionEvent.ACTION_DOWN:
if (STATE_CURRENT == STATE_REFRESH) {//如果状态是正在刷新状态,就不执行其他操作
iv_arrow.setVisibility(GONE);
pb_circle.setVisibility(View.VISIBLE);
return super.onTouchEvent(event);
}
down_dot = event.getY();//如果不是正在刷新状态记录按下的点
break;
//手指移动事件
case MotionEvent.ACTION_MOVE:
if (STATE_CURRENT == STATE_REFRESH && getFirstVisiblePosition() == 0) {//如果状态是正在刷新状态,就不执行其他操作
iv_arrow.setVisibility(GONE);
pb_circle.setVisibility(View.VISIBLE);
return super.onTouchEvent(event);
} else if ((STATE_CURRENT == STATE_PULL || STATE_CURRENT == STATE_PUSH) && getFirstVisiblePosition() == 0) {

//手指移动的点
float move_dot = event.getY();
//距离按下点的相对向下移动的距离
float movePullDistance = move_dot - down_dot;
//判断是否向下滑动,第一个条目是否是0,
if (movePullDistance > 0 && getFirstVisiblePosition() == 0 && movePullDistance < meatureHeight) {
inithideheaderView((int) movePullDistance - meatureHeight);
//向下滑动,动态改变HeardView距离上边界的距离,但是还没有到达修改箭头的边界
if (STATE_CURRENT == STATE_PUSH) {
tv_loadtext.setText("下拉刷新");
rotateArrow_changeText();
STATE_CURRENT = STATE_PULL;//修改状态,在这个范围内不再修改状态
}
} else if (movePullDistance == meatureHeight) {
STATE_CURRENT = STATE_CURRENT == STATE_PUSH ? STATE_PUSH : STATE_PULL;//判断状态
} else if (movePullDistance > meatureHeight && movePullDistance < 2 * meatureHeight) {
inithideheaderView((int) ((movePullDistance - meatureHeight) / 2 + 0));
//旋转箭头,修改文本,下拉幅度到边界值大于一个HeadView高度
if (STATE_CURRENT == STATE_PULL) {//如果是下拉状态,改变
tv_loadtext.setText("松开刷新");
rotateArrow_changeText();
STATE_CURRENT = STATE_PUSH;//修改状态,在这个范围内不再修改状态
}
}
return super.onTouchEvent(event);
}
break;
//手指抬起事件
case MotionEvent.ACTION_UP:
if (STATE_CURRENT == STATE_PUSH) {//如果是松开刷新状态那就改变状态
inithideheaderView(0);
//松开手指后,图片修改为旋转progressBar,修改文本
pb_circle.setVisibility(View.VISIBLE);
//必须将动画清除掉。。不然无法隐藏
iv_arrow.clearAnimation();
iv_arrow.setVisibility(GONE);
tv_loadtext.setText("正在刷新");
STATE_CURRENT = STATE_REFRESH;//修改为刷新状态
if (mOnRefreshStateListener != null && !refreshingState) {//如果不是正在刷新的状态,而且回调接口非空
refreshingState = true;
mOnRefreshStateListener.OnRefresh();//执行回调的接口
}
} else if (STATE_CURRENT == STATE_PULL) {
inithideheaderView(-meatureHeight);
pb_circle.setVisibility(View.GONE);
iv_arrow.setVisibility(View.VISIBLE);
//因为已经清除了动画,所以这里自动回去了,不必在设定转回原点
tv_loadtext.setText("下拉刷新");
STATE_CURRENT = STATE_PULL;
}
break;
}
return super.onTouchEvent(event);//必须有这个,这个是让ListView滑动的
}


1.3 刷新中需要设定一些小动画,这些动画实现方法,如下

/**
* 创建旋转动画
*/
private void setRotateAnimation(float fromDegrees, float toDegrees) {
mRotateAnimation = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
mRotateAnimation.setDuration(500);
mRotateAnimation.setFillAfter(true);
}

/**
* 刷新完成后的状态
*/
public void refreshComplement() {
inithideheaderView(-meatureHeight);
pb_circle.setVisibility(View.GONE);
iv_arrow.setVisibility(View.VISIBLE);
//因为已经清除了动画,所以这里自动回去了,不必在设定转回原点
tv_loadtext.setText("下拉刷新");
STATE_CURRENT = STATE_PULL;
refreshingState = false;
}

/**
* 旋转箭头,修改文本
*/
private void rotateArrow_changeText() {
if (STATE_CURRENT == STATE_PULL) {//如果状态为下拉状态
fromDegrees = 0f;
toDegrees = 180f;
//iv_arrow.clearAnimation();//先
f53d
清除一下动画效果
setRotateAnimation(fromDegrees, toDegrees);
iv_arrow.startAnimation(mRotateAnimation);
} else if (STATE_CURRENT == STATE_PUSH) {
fromDegrees = 180f;
toDegrees = 360f;
//iv_arrow.clearAnimation();//先清除一下动画效果
setRotateAnimation(fromDegrees, toDegrees);
iv_arrow.startAnimation(mRotateAnimation);
}
}


写到这儿,下拉刷新的方法基本完成,在下一步就是在主Activity中创建监听,在自定义ListView中实现回调接口,

/**
* 创建是否刷新的监听
*/
public void setOnRefreshStateListener(OnRefreshStateListener listener) {
mOnRefreshStateListener = listener;
}
/**
* 定义正在刷新回调接口
*/
public interface OnRefreshStateListener {
void OnRefresh();
}


主Activity中调用监听方法

pplv_test.setOnRefreshStateListener(new PullPushListView.OnRefreshStateListener() {
@Override
public void OnRefresh() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
objects.add(0, "卧槽,刷新出来了");
adapter.notifyDataSetChanged();
pplv_test.refreshComplement();//通知控件刷新操作已经完成
}
}, 3000);
}
});


华丽分割线

第二部分:实现上拉加载,同样的原理使用padding隐藏,不过要注意的是,同样使用-padding数值,因为 -数值才能类似于隐藏效果

2.1 footerView的布局效果,布局没什么好说的

<?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_loadmore"
style="@android:style/Widget.ProgressBar.Small"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginBottom="13dp"
android:layout_marginTop="13dp"
android:indeterminateDrawable="@drawable/progress_medium_white" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="加载更多"
android:textColor="#ff0000"
android:textSize="17sp" />
</LinearLayout>


2.2 初始化footerView 数据

/**
* 添加尾部头目,加载更多
*/
private void initFooterView() {
//打气筒添加布局
footerView = View.inflate(getContext(), R.layout.item_footerview, null);
//获取测量的宽高
footerView.measure(0, 0);
footerViewHeight = footerView.getMeasuredHeight();
int top = -footerViewHeight;
//设定初始padding的位置
initHideFooterView(top);
//将布局添加到Listview 中
super.addFooterView(footerView);
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//如果滑动状态为空闲,且滑动到了最后一条条目
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition() == getCount() - 1 && !loadState) {
setSelection(getCount() - 1);//设定选择最后一条条目
initHideFooterView(0);
loadState = true;
mOnLoadStateListener.onLoadState();
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}
});
}

/**
* 动态设定FooterView的Padding
*
* @param top 动态设定
*/
private void initHideFooterView(int top) {
footerView.setPadding(0, top, 0, 0);
}

/**


2.3 这里上拉刷新的操作不再OnTouch()中实现了,我们在OnscrollListener()方法中实现

super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//如果滑动状态为空闲,且滑动到了最后一条条目
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition() == getCount() - 1 && !loadState) {
setSelection(getCount() - 1);//设定选择最后一条条目
initHideFooterView(0);
loadState = true;
mOnLoadStateListener.onLoadState();
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}
});


2.4 实现接口回调,以及在主Activity中实现监听

/**
* 创建是否加载更多的刷新
*/
public void setOnLoadStateListener(onLoadStateListener listener) {
mOnLoadStateListener = listener;
}

/**
* 完成加载,修改状态
*/
public void loadComplement() {
initHideFooterView(-footerViewHeight);
loadState = false;
}

/**
* 定义正在加载的回调接口
*/
public interface onLoadStateListener {
void onLoadState();
}


主方法中创建加载监听

pplv_test.setOnLoadStateListener(new PullPushListView.onLoadStateListener() {
@Override
public void onLoadState() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
objects.add("卧槽,加载出来了");
adapter.notifyDataSetChanged();
pplv_test.loadComplement();//通知控件刷新操作已经完成
}
}, 3000);
}
});


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