在第二篇中主要讨论了将顶部布局加载到ListView中,重点分析了init,measureView和topping三个方法的实现;
这一篇主要是收尾部分,即判断状态,加载相应的函数并实现函数回调机制;
onTouchEvent:判断手势动作的方法:
public
boolean
onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch
(ev.getAction()) {
case
MotionEvent.ACTION_DOWN:
///当前是在下拉;
if
(firstVisibleItem ==
0
) {
///根据 当前可见的第一个的编号判断是不是在顶部;
isRemark =
true
;
///设置在顶部下拉的标志
startY = (
int
) ev.getY();
///记录开始时手指的位置高度;
}
break
;
case
MotionEvent.ACTION_MOVE:
onMove(ev);
///判断是否还在移动;
break
;
case
MotionEvent.ACTION_UP:
///
if
(state == RELESE) {
state = REFLASHING;
// 加载最新数据;
reflashViewByState();
iReflashListener.onReflash();
///响应事件;
}
else
if
(state == PULL) {
state = NONE;
isRemark =
false
;
reflashViewByState();
}
break
;
}
return
super
.onTouchEvent(ev);
}
|
首先调用移动事件对象的getAction方法获得动作,并用case来和MotionEvent的静态常量做比较,判断具体的动作类型。 (MotionEvent类是继承自抽象类InputEvent的,官方描述类的作用是:用于报告运动(鼠标、笔、手指,轨迹球)事件。运动事件可能持有 绝对或相对运动和其他数据,根据设备的类型)
动作类型:
MotionEvent.ACTION_DOWN:下拉动作,判断 当前的第一个Item的标号是不是0,firstVisibleItem在onScroll函数中就被赋好值了,并且这是由系统自动调用的。不是0的话, 就表示还没有到顶部;如果是0的话表示到了顶部,就要设置一个标志(isRemark)用来通知后面的相关函数,可以对顶部的距离进行调整,从而将顶部布 局显示出来。还要设置一个startY,记录当前手指所在的Y坐标位置,从而在下一次记录位置时,计算手指移动的距离,据此来显示顶部要显示的部分。
MotionEvent.ACTION_MOVE: 直接调用onMove函数,如果isRemark是true的话,表明已经到了顶部,onMove就会做相关的处理;如果是false的话,表明还未到顶 部,onMove就会直接返回。具体的onMove函数会在下面部分讨论,现在只要了解到onMove的调用时机和所起到的作用就可以了。
MotionEvent.ACTION_UP:根据状态state来判断:
ReflashListView定义了几种状态:
final
int
NONE =
0
;
// 正常状态;
final
int
PULL =
1
;
// 提示下拉状态;
final
int
RELESE =
2
;
// 提示释放状态;
final
int
REFLASHING =
3
;
// 刷新状态;
|
如果state是正常状态的话,上拉对顶部布局是不起作用的;
如果state是下拉状态的话,顶部布局还没有完全展开,说明你现在是想放弃刷新的操作,那么此时就将state置为正常状态,将isRemark置为false,表明还未到顶部;并调用reflashViewByState方法来重新显示顶部布局;
如果state是提示释放状态,现在上拉表示要刷新,调用reflashViewByState方法来重新显示顶部布局,并调用接口的函数,用接口回调机制,在Activity中实现函数体部分;
onMove:判断移动的距离和绘制顶部布局
private
void
onMove(MotionEvent ev) {
if
(!isRemark) {
///不是顶部下拉则直接返回;
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:
topPadding(topPadding);
if
(space > headerHeight +
30
///说明此时下拉距离超过高度30了,不能再拉了;
&& scrollState == SCROLL_STATE_TOUCH_SCROLL) {
state = RELESE;
reflashViewByState();
}
break
;
case
RELESE:
///表示已经到头了,不能下拉了,松开就可以刷新;
topPadding(topPadding);
if
(space < headerHeight +
30
) {
///到头了,但手指向上移动了,但还是可以看见头部的
state = PULL;
reflashViewByState();
}
else
if
(space <=
0
) {
///到头了,手指移出顶部;
state = NONE;
isRemark =
false
;
reflashViewByState();
}
break
;
}
}
|
首先isRemark是false表示还没有到达顶部布局,就直接返回;
之后记录当前手指的位置(tempY),与在onTouchEvent中记录的手指位置做减,得到手指移动的相对距离(space)。将距离与顶部布局的 高度做比较,得到一个新的tapping,作用是如果传入到第二篇中介绍的tapping函数中,就可以实时显示出顶部布局;
接着根据之前的状态和距离的大小,对布局的显示和新的状态做相应的调整:
如果state为NULL,并且spcae>0,就表示开始下移了,将state设置为PULL即下移状态,并调用reflashViewByState()重新加载布局;
如果state为PULL,调用topping,将顶部布局重新绘制。之后再判断space > headerHeight + 30 && scrollState == SCROLL_STATE_TOUCH_SCROLL:条件成立的话说明此时下拉距离超过高度30了并且手指此时是静止的,那么就将state状态置为 RELESE,表示释放可以刷新,并调用reflashViewByState()重新加载布局;
如果state为PULL,调用 topping,将顶部布局重新绘制。之后再判断space < headerHeight + 30,表示到头了,但手指向上移动了,但还是可以看见头部的,这时就要将state置为PULL,因为没有一下子移动到头,并调用 reflashViewByState;或者else space <= 0到头了,手指移出顶部,就要state = NONE,isRemark = false并调用reflashViewByState;
reflashViewByState:设置顶部布局的控件内容;
private
void
reflashViewByState() {
TextView tip = (TextView) header.findViewById(R.id.tip);
ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
ProgressBar progress = (ProgressBar) header.findViewById(R.id.progress);
RotateAnimation anim =
new
RotateAnimation(
0
,
180
,
RotateAnimation.RELATIVE_TO_SELF,
0
.5f,
RotateAnimation.RELATIVE_TO_SELF,
0
.5f);
anim.setDuration(
500
);
anim.setFillAfter(
true
);
RotateAnimation anim1 =
new
RotateAnimation(
180
,
0
,
RotateAnimation.RELATIVE_TO_SELF,
0
.5f,
RotateAnimation.RELATIVE_TO_SELF,
0
.5f);
anim1.setDuration(
500
);
anim1.setFillAfter(
true
);
switch
(state) {
case
NONE:
arrow.clearAnimation();
topPadding(-headerHeight);
break
;
case
PULL:
arrow.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
tip.setText(
"下拉可以刷新!"
);
arrow.clearAnimation();
arrow.setAnimation(anim1);
break
;
case
RELESE:
arrow.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
tip.setText(
"松开可以刷新!"
);
arrow.clearAnimation();
arrow.setAnimation(anim);
break
;
case
REFLASHING:
topPadding(
50
);
arrow.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
tip.setText(
"正在刷新..."
);
arrow.clearAnimation();
break
;
}
}
|
在switch之前的部分是获得顶部布局的控件,和设置动画(其实个人感觉可以在初始化的时候就设置,这样就不要每次都重新设置一遍);
之后根据状态来显示顶部布局:
如果state是NONE:隐藏顶部布局;
如果state是PULL:下拉;
如果state是RELESE:松开刷新;
如果state是REFLASHING:正在刷新;
reflashComplete:获取完数据;
public
void
reflashComplete() {
state = NONE;
isRemark =
false
;
reflashViewByState();
TextView lastupdatetime = (TextView) header
.findViewById(R.id.lastupdate_time);
SimpleDateFormat format =
new
SimpleDateFormat(
"yyyy年MM月dd日 hh:mm:ss"
);
Date date =
new
Date(System.currentTimeMillis());
String time = format.format(date);
lastupdatetime.setText(time);
}
|
主要是状态和标志的设置,记录刷新的时间,以便下次刷新时查看;
最后是接口的回调机制:
public
interface
IReflashListener{
public
void
onReflash();
}
public
void
setInterface(IReflashListener iReflashListener){
this
.iReflashListener = iReflashListener;
}
|
在Activity中,实现了接口IReflashListener,并重写了onReflash函数
public
void
onReflash() {
Handler handler =
new
Handler();
handler.postDelayed(
new
Runnable() {
public
void
run() {
//获取最新数据
setReflashData();
//通知界面显示
showList(apk_list);
//通知listview 刷新数据完毕;
listview.reflashComplete();
}
},
2000
);
}
|
关于函数回调机制,因为篇幅有限,就不具体讨论,可以看看我的博客: 常用但忽略的anroid知识2-回调问题
就这样,我们把用ListView来实现下拉刷新给全部分析了一遍,说说我在学习过程中的收获:不在惧怕developer的api文档了,看见一个常用的类我会去了解他与其他类的继承关系。对接口回调机制也有了比较深刻的理解;真心希望这三篇会给你带来一些新的理解。
接下来的几天中,我会开始学习自定义VIewGroup,也希望自己可以有比较深刻的体会,和大家一起分享!
一直在路上...