zoukankan      html  css  js  c++  java
  • listview反弹实现详解

    本文转自 http://jianwang0412.iteye.com/blog/1267885

    重写listview,通过监听滑动事件,根据滑动时所处的位置,以及滑动的方向,使用view的内置scrollTo或scrollBy函数来移动view到你手势互动的距离(此处为一半),然后当确定消费了给事件后,又回滚到(0,0)点。当然只有在超出了边界时才回滚。而且回滚的过程由TranslateAnimation来控制,这样的好处在代码的解释中。我是基于网络上的listviewpress改了一些(有几处好像是被篡改了,我又按我的理解将它改正过来,运行后没问题)。一下是关键的代码,整个代码见附件中。有不懂的可以问问,大家互相学习。

    Java代码  收藏代码
    1. package com.listview.test;  
    2.   
    3. import android.content.Context;  
    4. import android.graphics.Rect;  
    5. import android.util.AttributeSet;  
    6. import android.util.Log;  
    7. import android.view.GestureDetector;  
    8. import android.view.MotionEvent;  
    9. import android.view.View;  
    10. import android.view.GestureDetector.OnGestureListener;  
    11. import android.view.animation.TranslateAnimation;  
    12. import android.widget.ListView;  
    13.   
    14. public class CustomerListView extends ListView {  
    15.   
    16.     private Context mContext;  
    17.     private boolean outBound = false;  
    18.     private int distance;  
    19.     private int firstOut;  
    20.   
    21.     public CustomerListView(Context c) {  
    22.         super(c);  
    23.         this.mContext = c;  
    24.     }  
    25.   
    26.     public CustomerListView(Context c, AttributeSet attrs) {  
    27.         super(c, attrs);  
    28.         this.mContext = c;  
    29.     }  
    30.   
    31.     public CustomerListView(Context c, AttributeSet attrs, int defStyle) {  
    32.         super(c, attrs, defStyle);  
    33.         this.mContext = c;  
    34.     }  
    35.   
    36.     GestureDetector gestureDetector = new GestureDetector(  
    37.             new OnGestureListener() {  
    38.   
    39.                 public boolean onDown(MotionEvent e) {  
    40.                     // TODO Auto-generated method stub  
    41.                     return false;  
    42.                 }  
    43.   
    44.                 public boolean onFling(MotionEvent e1, MotionEvent e2,  
    45.                         float velocityX, float velocityY) {  
    46.                     // TODO Auto-generated method stub  
    47.                     return false;  
    48.                 }  
    49.   
    50.                 public void onLongPress(MotionEvent e) {  
    51.                     // TODO Auto-generated method stub  
    52.   
    53.                 }  
    54. /**捕捉滑动事件  e1为此处为的ACTION_DOWN事件(无论什么动作,起始都是该动作),而e2是触发调用onScroll的事件。而在此期间,可能已经 
    55.  * 触发了多次的onScroll,因为我们滑动过程可能比较长,一旦长于某个值,就会触发一次(即一个Move应该是由多个 
    56.  * move事件组成的,开头当然是个ACTION_DOWN事件),也就会发出一个移动的MotionEvent。但是期间开始此次 
    57.  * scroll的e1是唯一的。而distance是最近一次调用onScroll以来的距离(前一个e2和现在e2的距离:比如上次是-30,这次是-60(比如向下拉), 
    58.  * 那么 distanceY=-60-(-30)=-30)。 
    59.  */  
    60.   
    61.                 public boolean onScroll(MotionEvent e1, MotionEvent e2,  
    62.                         float distanceX, float distanceY) {  
    63.                     /** 
    64.                      * firstPos和lastPos是adapter中元素的Id 
    65.                      */  
    66.                     int firstPos = getFirstVisiblePosition();  
    67.                     int lastPos = getLastVisiblePosition();  
    68.                     int itemCount = getCount();  
    69.                       
    70.                     /** 
    71.                      * 滑出边界,而且是一个极点,即可视部分已经已经不存在了,那么直接回到原点 
    72.                      */  
    73.                     if (outBound && firstPos != 0 && lastPos != (itemCount - 1)) {  
    74.                         scrollTo(00);  
    75.                         return false;  
    76.                     }  
    77.                     /** 
    78.                      * getChildAt是屏幕上可见的元素的id,比如现在屏幕上可见的是adapter中的 
    79.                      * 4号到10号,那么你调用getChildAt应该是0~6号  
    80.                      * listView.getChildAt(i) works where 0 is the very first visible row and 
    81.                      *  (n-1) is the last visible row (where n is the number of visible views you see). 
    82.                      *  进入该onScroll有4种可能,第一种是刚开始的时候,此时firstPos==0,而且可视的item在getChildAt的 
    83.                      *  返回也是第一个元素,即adapter元素的index和可视的view的编号一致,所以firstview不为空(lastview也一样)。 
    84.                      *  当你向上 滑动时,distanceY是大于0的。此时将不消费此次事件,那么将正常地在没有超出边际出滚动。 
    85.                      *  第二种是,若以上是向下拉,那么应该属于超出范围的情况,则要消费此时事件。 
    86.                      *  第三种和第一种类似,只是到了当刚好显示最后一个item时,显然firstView和lastView都将是null,因为 
    87.                      *  此时的adapter的index和getChildAt的index不是相等的,而是成对应关系, 
    88.                      *  即index_adp-firstPos=index_getChild,此时你若使用getChildAt(firstpos-firstpos),那返回的 
    89.                      *  将是非null。同理在lastView。第四种是当在第三情况下,向上拉,那么属于超出边界。那么lastView是null这个特征 
    90.                      *  将可以判断是否进入了下临界区。 
    91.                      *  总结以上四种情况,每当触发临界区时(dispatchTouchEvent时getFirstVisiblePosition()==0 
    92.                      *  和getLastVisiblePosition()==getCount()-1),就可以通过distanceY的方向性判断是正常的滑动 
    93.                      *  还是将要滑出临界区。若是滑出临界区,说明此次将消费该事件,所以返回true,那么在dispatchTouchEvent 
    94.                      *  将设置outBand为true,那么第二次再进入时,将可以通过outBand来确定是否出了临界区。 
    95.                      *   
    96.                      *  带方向的函数:onScrollBy/To和onScroll 
    97.                      */  
    98.                     View firstView = getChildAt(firstPos);  
    99.                     View lastView = getChildAt(lastPos-1);  
    100.                 /** 
    101.                  * 记录下第一次的e2的y轴距离,此次过后outBound就变为了true。这样distance就是跟踪最近的一次e2 
    102.                  * 和最开始一次的e2的距离。 
    103.                  */  
    104.                     if (!outBound) {  
    105.                         firstOut = (int) e2.getRawY();  
    106.                     }  
    107.                     if (firstView != null  
    108.                             && (outBound || (firstPos == 0  
    109.                                     && firstView.getTop() == 0 && distanceY < 0))) {  
    110.                         distance = (int) (firstOut - e2.getRawY());//此处应为负值,即view向下滑动  
    111.                 /**  
    112.                  * scrollBy中的值带有方向,x若为正,则应该以view中该x点显示在新的原点上,即拿新的点去  
    Java代码  收藏代码
    1. <span style="white-space: pre;">                </span>*重合y轴,就好像整个布局被往左拉动。  
    2.                  * y为正,则向上滑动|y|距离。负则相反。  
    3.                  */  
    4.                         scrollBy(0, distance / 2);  
    5.                         Log.v("onScroll""e2.getRawY():"+e2.getRawY());  
    6.                         Log.v("onScroll""distance:"+distance);  
    7.                         Log.v("onScroll""distanceY:"+distanceY);  
    8.                         return true;  
    9.                     }  
    10.                     if (lastView == null&&(outBound || (lastPos == itemCount - 1 && distanceY > 0))) {  
    11.                         Log.d("bottom""bottom");  
    12.                         distance = (int) (firstOut - e2.getRawY());//此处应为正直,因为view向上滑动  
    13.                         scrollBy(0, distance/2);  
    14.                         return true;  
    15.                     }  
    16.                     return false;  
    17.                 }  
    18.   
    19.                 public void onShowPress(MotionEvent e) {  
    20.                     // TODO Auto-generated method stub  
    21.   
    22.                 }  
    23.   
    24.                 public boolean onSingleTapUp(MotionEvent e) {  
    25.                     // TODO Auto-generated method stub  
    26.                     return false;  
    27.                 }  
    28.             });  
    29.   
    30.     /** 
    31.      * 最早响应触屏事件,按下和释放响应两次 
    32.      */  
    33.     public boolean dispatchTouchEvent(MotionEvent ev) {  
    34.         if(getFirstVisiblePosition()==0){  
    35.             int act = ev.getAction();  
    36.             if ((act == MotionEvent.ACTION_UP || act == MotionEvent.ACTION_CANCEL)  
    37.                     && outBound) {  
    38.                 outBound = false;  
    39.             }  
    40.             if (!gestureDetector.onTouchEvent(ev)) {  
    41.                 outBound = false;  
    42.             } else {  
    43.                 outBound = true;  
    44.             }  
    45.             Rect rect = new Rect();  
    46.             getLocalVisibleRect(rect);  
    47.             /** 
    48.              * rect.top是个正的距离值,而TanslateAnimation填的是坐标值(有方向的); 
    49.              */  
    50.             TranslateAnimation am = new TranslateAnimation(00, -rect.top, 0);  
    51.             /** 
    52.              * 若此处时间设为0,将导致一阵的抖动,因为完成回滚的速度不是分步,而是直接到终点 
    53.              * 因为每次触发onScroll时都会做一次回滚,而当传进又一次move时,上一次的move还没作完 
    54.              * 就将被新的一次覆盖,所以不用担心产生抖动。所以此处给它设时间就是抓住它需要时间来完成回滚的目标,相当 
    55.              * 于给它一个时间的缓冲来实现移动,因为当你在移动时,实际是不需要回滚的,只有你释放了手指还才需要回滚。 
    56.              * 注意,此时调用scrollTo已经将位置返回了0(可以把animation当成是模型,只有使用scrollTo才 
    57.              * 能真正触发该移动,结果是已经知道了的,即移动到原点,而过程是TranslateAnimation参谋的,即 
    58.              * scrollTo在移动时会调用onScrollChange来实际移动,而onScrollChange则根据传入的参数来移动 
    59.              * 而TranslateAnimation则可以控制该参数。可以把scrollTo先去掉,就可以发现new top 和 
    60.              * after scrollBy是一样的值)。也就是new Top=0。所以每次迭代相减都是现在的e2减去最初的e2在y轴上的值, 
    61.              * 这样通过scrollBy就可以将view移动到新的位置,而此时top也就又被写成了新的滑动的位置(是滑动距离的一半位置)。 
    62.              * 11-19 23:51:11.101: V/onScroll(18396): after scrollBy top:0 
    63.                 11-19 23:51:11.101: V/onScroll(18396): new top:0 
    64.                 11-19 23:51:11.249: V/onScroll(18396): after scrollBy top:0 
    65.                 11-19 23:51:11.249: V/onScroll(18396): new top:0 
    66.                 11-19 23:51:11.288: V/onScroll(18396): after scrollBy top:-6 
    67.                 11-19 23:51:11.288: V/onScroll(18396): new top:0 
    68.                 11-19 23:51:11.319: V/onScroll(18396): after scrollBy top:-16 
    69.                 11-19 23:51:11.319: V/onScroll(18396): new top:0 
    70.                 11-19 23:51:11.358: V/onScroll(18396): after scrollBy top:-20 
    71.                 11-19 23:51:11.358: V/onScroll(18396): new top:0 
    72.                 11-19 23:51:11.374: V/onScroll(18396): after scrollBy top:-27 
    73.                 11-19 23:51:11.374: V/onScroll(18396): new top:0 
    74.              */  
    75.             am.setDuration(300);  
    76.             startAnimation(am);  
    77.             Log.v("onScroll","after scrollBy top:"+rect.top);  
    78.             scrollTo(00);  
    79.             getLocalVisibleRect(rect);  
    80.             Log.v("onScroll""new top:"+rect.top);  
    81.         }  
    82.         Log.d("getLastVisiblePosition()", getLastVisiblePosition()+"");  
    83.         Log.d("getCount()", getCount()+"");  
    84.         if(getLastVisiblePosition()==getCount()-1){  
    85.             int act = ev.getAction();  
    86.             if ((act == MotionEvent.ACTION_DOWN || act == MotionEvent.ACTION_CANCEL)  
    87.                     && outBound) {  
    88.                 outBound = false;  
    89.             }  
    90.             if (!gestureDetector.onTouchEvent(ev)) {  
    91.                 outBound = false;  
    92.             } else {  
    93.                 outBound = true;  
    94.             }  
    95.             if(outBound){  
    96.                 Rect rect1 = new Rect();  
    97.                 getLocalVisibleRect(rect1);  
    98.                 TranslateAnimation am1 = new TranslateAnimation(00, rect1.top, 0);  
    99.                 am1.setDuration(300);  
    100.                 startAnimation(am1);  
    101.                 scrollTo(00);  
    102.             }  
    103.         }  
    104.         return super.dispatchTouchEvent(ev);  
    105.     };  
    106. }  
  • 相关阅读:
    javascript基础全等号运算符
    javascript 使用ScriptX实现打印
    跨服务器与本地服务器不同数据库的SQL操作语句
    ASP.NET网络上实现单点登录
    FGMap API 帮助文档
    基于ArcEngine写的GoogleMap地图切割程序
    基于SuperMap Objects写的GoogleMap地图切割程序(三)
    使用SuperSocket开发联网斗地主(四):出牌
    JAVA创建对象方法
    Mysql 外键约束
  • 原文地址:https://www.cnblogs.com/xiaoQLu/p/2652085.html
Copyright © 2011-2022 走看看