首页 > 代码库 > ListView实现下拉刷新-3-将顶部布局动态的显示出来

ListView实现下拉刷新-3-将顶部布局动态的显示出来

在第二篇中主要讨论了将顶部布局加载到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(0180,
                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);
         anim.setDuration(500);
         anim.setFillAfter(true);
         RotateAnimation anim1 = new RotateAnimation(1800,
                 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,也希望自己可以有比较深刻的体会,和大家一起分享!

 一直在路上...



ListView实现下拉刷新-3-将顶部布局动态的显示出来