`

listview反弹实现详解

 
阅读更多

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

 

 

 

package com.listview.test;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.OnGestureListener;
import android.view.animation.TranslateAnimation;
import android.widget.ListView;

public class CustomerListView extends ListView {

	private Context mContext;
	private boolean outBound = false;
	private int distance;
	private int firstOut;

	public CustomerListView(Context c) {
		super(c);
		this.mContext = c;
	}

	public CustomerListView(Context c, AttributeSet attrs) {
		super(c, attrs);
		this.mContext = c;
	}

	public CustomerListView(Context c, AttributeSet attrs, int defStyle) {
		super(c, attrs, defStyle);
		this.mContext = c;
	}

	GestureDetector gestureDetector = new GestureDetector(
			new OnGestureListener() {

				public boolean onDown(MotionEvent e) {
					// TODO Auto-generated method stub
					return false;
				}

				public boolean onFling(MotionEvent e1, MotionEvent e2,
						float velocityX, float velocityY) {
					// TODO Auto-generated method stub
					return false;
				}

				public void onLongPress(MotionEvent e) {
					// TODO Auto-generated method stub

				}
/**捕捉滑动事件  e1为此处为的ACTION_DOWN事件(无论什么动作,起始都是该动作),而e2是触发调用onScroll的事件。而在此期间,可能已经
 * 触发了多次的onScroll,因为我们滑动过程可能比较长,一旦长于某个值,就会触发一次(即一个Move应该是由多个
 * move事件组成的,开头当然是个ACTION_DOWN事件),也就会发出一个移动的MotionEvent。但是期间开始此次
 * scroll的e1是唯一的。而distance是最近一次调用onScroll以来的距离(前一个e2和现在e2的距离:比如上次是-30,这次是-60(比如向下拉),
 * 那么 distanceY=-60-(-30)=-30)。
 */

				public boolean onScroll(MotionEvent e1, MotionEvent e2,
						float distanceX, float distanceY) {
					/**
					 * firstPos和lastPos是adapter中元素的Id
					 */
					int firstPos = getFirstVisiblePosition();
					int lastPos = getLastVisiblePosition();
					int itemCount = getCount();
					
					/**
					 * 滑出边界,而且是一个极点,即可视部分已经已经不存在了,那么直接回到原点
					 */
					if (outBound && firstPos != 0 && lastPos != (itemCount - 1)) {
						scrollTo(0, 0);
						return false;
					}
					/**
					 * getChildAt是屏幕上可见的元素的id,比如现在屏幕上可见的是adapter中的
					 * 4号到10号,那么你调用getChildAt应该是0~6号 
					 * listView.getChildAt(i) works where 0 is the very first visible row and
					 *  (n-1) is the last visible row (where n is the number of visible views you see).
					 *  进入该onScroll有4种可能,第一种是刚开始的时候,此时firstPos==0,而且可视的item在getChildAt的
					 *  返回也是第一个元素,即adapter元素的index和可视的view的编号一致,所以firstview不为空(lastview也一样)。
					 *  当你向上 滑动时,distanceY是大于0的。此时将不消费此次事件,那么将正常地在没有超出边际出滚动。
					 *  第二种是,若以上是向下拉,那么应该属于超出范围的情况,则要消费此时事件。
					 *  第三种和第一种类似,只是到了当刚好显示最后一个item时,显然firstView和lastView都将是null,因为
					 *  此时的adapter的index和getChildAt的index不是相等的,而是成对应关系,
					 *  即index_adp-firstPos=index_getChild,此时你若使用getChildAt(firstpos-firstpos),那返回的
					 *  将是非null。同理在lastView。第四种是当在第三情况下,向上拉,那么属于超出边界。那么lastView是null这个特征
					 *  将可以判断是否进入了下临界区。
					 *  总结以上四种情况,每当触发临界区时(dispatchTouchEvent时getFirstVisiblePosition()==0
					 *  和getLastVisiblePosition()==getCount()-1),就可以通过distanceY的方向性判断是正常的滑动
					 *  还是将要滑出临界区。若是滑出临界区,说明此次将消费该事件,所以返回true,那么在dispatchTouchEvent
					 *  将设置outBand为true,那么第二次再进入时,将可以通过outBand来确定是否出了临界区。
					 *  
					 *  带方向的函数:onScrollBy/To和onScroll
					 */
					View firstView = getChildAt(firstPos);
					View lastView = getChildAt(lastPos-1);
				/**
				 * 记录下第一次的e2的y轴距离,此次过后outBound就变为了true。这样distance就是跟踪最近的一次e2
				 * 和最开始一次的e2的距离。
				 */
					if (!outBound) {
						firstOut = (int) e2.getRawY();
					}
					if (firstView != null
							&& (outBound || (firstPos == 0
									&& firstView.getTop() == 0 && distanceY < 0))) {
						distance = (int) (firstOut - e2.getRawY());//此处应为负值,即view向下滑动
				/**
				 * scrollBy中的值带有方向,x若为正,则应该以view中该x点显示在新的原点上,即拿新的点去
				*重合y轴,就好像整个布局被往左拉动。
				 * y为正,则向上滑动|y|距离。负则相反。
				 */
						scrollBy(0, distance / 2);
						Log.v("onScroll", "e2.getRawY():"+e2.getRawY());
						Log.v("onScroll", "distance:"+distance);
						Log.v("onScroll", "distanceY:"+distanceY);
						return true;
					}
					if (lastView == null&&(outBound || (lastPos == itemCount - 1 && distanceY > 0))) {
						Log.d("bottom", "bottom");
						distance = (int) (firstOut - e2.getRawY());//此处应为正直,因为view向上滑动
						scrollBy(0, distance/2);
						return true;
					}
					return false;
				}

				public void onShowPress(MotionEvent e) {
					// TODO Auto-generated method stub

				}

				public boolean onSingleTapUp(MotionEvent e) {
					// TODO Auto-generated method stub
					return false;
				}
			});

	/**
	 * 最早响应触屏事件,按下和释放响应两次
	 */
	public boolean dispatchTouchEvent(MotionEvent ev) {
		if(getFirstVisiblePosition()==0){
			int act = ev.getAction();
			if ((act == MotionEvent.ACTION_UP || act == MotionEvent.ACTION_CANCEL)
					&& outBound) {
				outBound = false;
			}
			if (!gestureDetector.onTouchEvent(ev)) {
				outBound = false;
			} else {
				outBound = true;
			}
			Rect rect = new Rect();
			getLocalVisibleRect(rect);
			/**
			 * rect.top是个正的距离值,而TanslateAnimation填的是坐标值(有方向的);
			 */
			TranslateAnimation am = new TranslateAnimation(0, 0, -rect.top, 0);
			/**
			 * 若此处时间设为0,将导致一阵的抖动,因为完成回滚的速度不是分步,而是直接到终点
			 * 因为每次触发onScroll时都会做一次回滚,而当传进又一次move时,上一次的move还没作完
			 * 就将被新的一次覆盖,所以不用担心产生抖动。所以此处给它设时间就是抓住它需要时间来完成回滚的目标,相当
			 * 于给它一个时间的缓冲来实现移动,因为当你在移动时,实际是不需要回滚的,只有你释放了手指还才需要回滚。
			 * 注意,此时调用scrollTo已经将位置返回了0(可以把animation当成是模型,只有使用scrollTo才
			 * 能真正触发该移动,结果是已经知道了的,即移动到原点,而过程是TranslateAnimation参谋的,即
			 * scrollTo在移动时会调用onScrollChange来实际移动,而onScrollChange则根据传入的参数来移动
			 * 而TranslateAnimation则可以控制该参数。可以把scrollTo先去掉,就可以发现new top 和
			 * after scrollBy是一样的值)。也就是new Top=0。所以每次迭代相减都是现在的e2减去最初的e2在y轴上的值,
			 * 这样通过scrollBy就可以将view移动到新的位置,而此时top也就又被写成了新的滑动的位置(是滑动距离的一半位置)。
			 * 11-19 23:51:11.101: V/onScroll(18396): after scrollBy top:0
				11-19 23:51:11.101: V/onScroll(18396): new top:0
				11-19 23:51:11.249: V/onScroll(18396): after scrollBy top:0
				11-19 23:51:11.249: V/onScroll(18396): new top:0
				11-19 23:51:11.288: V/onScroll(18396): after scrollBy top:-6
				11-19 23:51:11.288: V/onScroll(18396): new top:0
				11-19 23:51:11.319: V/onScroll(18396): after scrollBy top:-16
				11-19 23:51:11.319: V/onScroll(18396): new top:0
				11-19 23:51:11.358: V/onScroll(18396): after scrollBy top:-20
				11-19 23:51:11.358: V/onScroll(18396): new top:0
				11-19 23:51:11.374: V/onScroll(18396): after scrollBy top:-27
				11-19 23:51:11.374: V/onScroll(18396): new top:0
			 */
			am.setDuration(300);
			startAnimation(am);
			Log.v("onScroll","after scrollBy top:"+rect.top);
			scrollTo(0, 0);
			getLocalVisibleRect(rect);
     		Log.v("onScroll", "new top:"+rect.top);
		}
		Log.d("getLastVisiblePosition()", getLastVisiblePosition()+"");
		Log.d("getCount()", getCount()+"");
		if(getLastVisiblePosition()==getCount()-1){
			int act = ev.getAction();
			if ((act == MotionEvent.ACTION_DOWN || act == MotionEvent.ACTION_CANCEL)
					&& outBound) {
				outBound = false;
			}
			if (!gestureDetector.onTouchEvent(ev)) {
				outBound = false;
			} else {
				outBound = true;
			}
			if(outBound){
				Rect rect1 = new Rect();
				getLocalVisibleRect(rect1);
				TranslateAnimation am1 = new TranslateAnimation(0, 0, rect1.top, 0);
				am1.setDuration(300);
				startAnimation(am1);
				scrollTo(0, 0);
			}
		}
		return super.dispatchTouchEvent(ev);
	};
}
分享到:
评论
1 楼 真心等候 2012-06-09  
想做这样的功能久了。感谢

相关推荐

    ListView反弹效果实现

    本篇文章将深入探讨如何在Android中实现ListView的反弹效果。 首先,我们要理解Android系统本身并未提供直接的API来实现这种反弹效果。因此,我们需要借助第三方库或者自定义View来实现。这里提到的"ListViewScroll...

    AndroidListView反弹效果源码.zip

    在这个名为"AndroidListView反弹效果源码.zip"的压缩包中,包含了实现这一效果的源代码。这种效果的实现主要涉及到以下几个关键点: 1. **OverScroller**:这是Android系统提供的一个类,用于处理过度滚动动画。它...

    Android ListView反弹效果源码

    本篇将详细解析实现Android ListView反弹效果的源码。 首先,反弹效果主要依赖于两个关键组件:OnScrollListener和 OverscrollByCompat 方法。OnScrollListener 是ListView的监听器,用来捕获用户的滚动事件;...

    Android ListView反弹效果源码.rar

    "Android ListView反弹效果源码" 是一个针对ListView进行优化的资源包,它提供了实现ListView滚动反弹效果的代码。这个源码可能包括自定义滚动监听器、动画处理和视图处理等关键组件,旨在让ListView在滚动至顶部或...

    ListView反弹效果

    本篇文章将详细介绍如何在Android中实现ListView的反弹效果。 首先,我们需要明白,这个反弹效果主要涉及到两个关键组件:一是ListView本身,二是处理滑动手势和动画的库。在Android的开源社区中,有诸如“android-...

    ListView相关控件详解

    在Android开发中,ListView常用于实现下拉刷新、上拉加载更多以及分类显示等功能。本篇文章将深入解析ListView及其相关组件,帮助你更好地理解和运用。 1. **ListView基本结构** ListView由多个列表项(ListView ...

    Android ListView反弹效果源码.zip

    本压缩包中的"Android ListView反弹效果源码.zip"提供了实现这一功能的源代码参考。 首先,我们来看"源码说明.txt"文件,它可能包含了对源码的详细解释和使用指南。通常,开发者会在这里介绍实现反弹效果的关键类、...

    ListView的实现方法

    在Android开发中,ListView是一种非常常见且重要的组件,它用于展示大量的数据列表,通常与适配器(Adapter)配合使用,以实现动态加载和高效的数据绑定。本篇将详细介绍ListView的实现方法,以及如何通过不同类型的...

    listview拖拽实现.rar

    本项目“listview拖拽实现.rar”是基于Visual Studio 2019的一个C# WinForms应用,它实现了用户可以将图片拖放到ListView中,从而增强了用户体验并提供了更直观的操作方式。 在C#编程中,实现ListView控件的拖放...

    ListView详解

    ### ListView详解 #### 一、概述 在Android开发过程中,`ListView`是一个极其重要的UI组件,主要用于以列表的形式展示大量数据。与普通的`View`不同,`ListView`能够根据数据的大小自动调整显示内容的长度,非常...

    listview内容全新详解

    ListView的使用方法 ListView是Android软件开发中非常重要组件之一,基本上是个软件基本都会使用ListView,今天我通过一个demo来教大家怎么样使用ListView组件绘制出漂亮布局

    ListView控件实现grid

    在本场景中,我们关注的是如何利用ListView控件来实现一个网格(grid)展示的效果。这通常涉及到自定义绘制(owner draw)功能,使我们可以根据需求定制ListView的显示样式。 1. **ListView控件基础** - ListView...

    小程序源码 辅助类库 ListView反弹效果源码.rar

    这个压缩包“小程序源码 辅助类库 ListView反弹效果源码.rar”似乎包含了实现ListView在滚动到顶部或底部时产生弹性反弹效果的源代码。这种效果常见于许多现代应用程序,它为用户提供了更加直观和动态的交互体验。 ...

    Android代码-ListView反弹效果源码.zip

    本文将详细探讨如何通过源码实现Android ListView的反弹效果。 首先,我们需要理解反弹效果的原理。当用户滑动到ListView的顶部或底部时,如果继续滑动,ListView应该有一种回弹的动画效果,就像物理世界中的弹性...

    仿微信查看图片效果以及listview反弹效果

    1.listview的动画反弹效果 2.点击图片全屏查看图片,可对图片进行双指放大缩小移动,点击屏幕图片消失,点击返回图片消失,弹出和消失时都有简单动画。

    android-listview事件详解

    android listview事件详解。

Global site tag (gtag.js) - Google Analytics