在第二篇中主要讨论了将顶部布局加载到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,也希望自己可以有比较深刻的体会,和大家一起分享!
一直在路上...
原文地址:http://blog.csdn.net/softtrilobite/article/details/41869575