`

有弹性的ScrollView&ListView&HorizontalScrollView

阅读更多
原帖:
http://dev.10086.cn/cmdn/bbs/thread-40685-1-1.html

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;

/**
 * ElasticScrollView有弹性的ScrollView
 */
public class ElasticScrollView extends ScrollView {
	private View inner;
	private float y;
	private Rect normal = new Rect();;

	public ElasticScrollView(Context context) {
		super(context);
	}
	
	public ElasticScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected void onFinishInflate() {
		if (getChildCount() > 0) {
			inner = getChildAt(0);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (inner == null) {
			return super.onTouchEvent(ev);
		} else {
			commOnTouchEvent(ev);
		}
		return super.onTouchEvent(ev);
	}

	public void commOnTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			y = ev.getY();
			break;
		case MotionEvent.ACTION_UP:
			if (isNeedAnimation()) {
				animation();
			}
			break;
		case MotionEvent.ACTION_MOVE:
			final float preY = y;
			float nowY = ev.getY();
			int deltaY = (int) (preY - nowY);
			// 滚动
			scrollBy(0, deltaY);

			y = nowY;
			// 当滚动到最上或者最下时就不会再滚动,这时移动布局
			if (isNeedMove()) {
				if (normal.isEmpty()) {
					// 保存正常的布局位置
					normal.set(inner.getLeft(), inner.getTop(), inner
							.getRight(), inner.getBottom());

				}
				// 移动布局
				inner.layout(inner.getLeft(), inner.getTop() - deltaY, inner
						.getRight(), inner.getBottom() - deltaY);
			}
			break;
		default:
			break;
		}
	}

	// 开启动画移动

	public void animation() {
		// 开启移动动画
		TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(),
				normal.top);
		ta.setDuration(200);
		inner.startAnimation(ta);
		// 设置回到正常的布局位置
		inner.layout(normal.left, normal.top, normal.right, normal.bottom);
		normal.setEmpty();
	}

	// 是否需要开启动画
	public boolean isNeedAnimation() {
		return !normal.isEmpty();
	}

	// 是否需要移动布局
	public boolean isNeedMove() {
		int offset = inner.getMeasuredHeight() - getHeight();
		int scrollY = getScrollY();
		if (scrollY == 0 || scrollY == offset) {
			return true;
		}
		return false;
	}

}


<?xml version="1.0" encoding="utf-8"?>
<com.rebound.myscroll.ElasticScrollView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" 
	android:layout_width="fill_parent"
	android:id="@+id/sv" 
	android:layout_height="fill_parent"
	>
	<TextView android:id="@+id/tv" 
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
          android:text="......一个很长很长的字符串......"

 />
</com.rebound.myscroll.ElasticScrollView>


或者另一种实现方式(推荐)
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.ScrollView;

public class MtScrollView extends ScrollView {
private static final int MAX_Y_OVERSCROLL_DISTANCE = 200; 
    
    private Context mContext; 
    private int mMaxYOverscrollDistance; 
      
    public MtScrollView(Context context){ 
        super(context); 
        mContext = context; 
        initBounceListView(); 
    } 
      
    public MtScrollView(Context context, AttributeSet attrs){ 
        super(context, attrs); 
        mContext = context; 
        initBounceListView();
    } 
      
    public MtScrollView(Context context, AttributeSet attrs, int defStyle){ 
        super(context, attrs, defStyle); 
        mContext = context; 
        initBounceListView(); 
    } 
      
    private void initBounceListView(){ 
        final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 
            final float density = metrics.density; 
          
        mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE); 
    } 
    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent){  
        //这块是关键性代码
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent);   
    }
}



通过上面的方法,也可以实现有弹性的ListView:

public class BounceListView extends ListView
{
    private static final int MAX_Y_OVERSCROLL_DISTANCE = 200;
    
    private Context mContext;
	private int mMaxYOverscrollDistance;
	
	public BounceListView(Context context) 
	{
		super(context);
		mContext = context;
		initBounceListView();
	}
	
	public BounceListView(Context context, AttributeSet attrs) 
	{
		super(context, attrs);
		mContext = context;
		initBounceListView();
	}
	
	public BounceListView(Context context, AttributeSet attrs, int defStyle) 
	{
		super(context, attrs, defStyle);
		mContext = context;
		initBounceListView();
	}
	
	private void initBounceListView()
	{
		//get the density of the screen and do some maths with it on the max overscroll distance
		//variable so that you get similar behaviors no matter what the screen size
		
		final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
        	final float density = metrics.density;
        
		mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
	}
	
	@Override
	protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) 
	{ 
		//This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverscrollDistance; 
		return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent);  
	}
	
}


左右滑动加弹性的HorizontalScrollView
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.HorizontalScrollView;

public class ElasticHorizontalScrollView extends HorizontalScrollView {
    private View inner;
    private Rect normal = new Rect();
    private float x;

    public ElasticHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ElasticHorizontalScrollView(Context context) {
        super(context);
    }

    @Override
    protected void onFinishInflate() {
        if (getChildCount() > 0) {
            inner = getChildAt(0);
        }
        super.onFinishInflate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev == null) {
            return super.onTouchEvent(ev);
        } else {
            commOnTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

    private void commOnTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            x = ev.getX();
            break;
        case MotionEvent.ACTION_UP:
            if (isNeedAnimation()) {
                animation();
            }
            break;
        case MotionEvent.ACTION_MOVE:
            final float preX = x;
            float nowX = ev.getX();
            int distanceX = (int) (preX - nowX);
            scrollBy(distanceX, 0);
            x = nowX;
            if (isNeedMove()) {
                if (normal.isEmpty()) {
                    normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
                }
                inner.layout(inner.getLeft() - distanceX, inner.getTop(), inner.getRight() - distanceX, inner.getBottom());
            }

            break;

        default:
            break;
        }
    }

    private void animation() {
        TranslateAnimation mTranslateAnimation = new TranslateAnimation(inner.getLeft(), 0, normal.left, 0);
        mTranslateAnimation.setDuration(50);
        inner.setAnimation(mTranslateAnimation);
        inner.layout(normal.left, normal.top, normal.right, normal.bottom);
        normal.setEmpty();

    }

    private boolean isNeedAnimation() {
        return !normal.isEmpty();
    }

    private boolean isNeedMove() {
        int offset = inner.getMeasuredWidth() - getWidth();
        int scrollX = getScrollX();
        if (scrollX == 0 || offset == scrollX)
            return true;
        return false;
    }
}

<com.example.horiztalscrollviewtext.ElasticHorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#D1EEEE"
    android:scrollbars="none" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#F5F5F5"
        android:orientation="horizontal" >

        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:src="@drawable/main_guide_0" />

        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:src="@drawable/main_guide_0" />

        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:src="@drawable/main_guide_0" />
    </LinearLayout>

</com.example.horiztalscrollviewtext.ElasticHorizontalScrollView>


也可以这样实现有弹性的HorizontalScrollView: (推荐)
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.HorizontalScrollView;

public class BouncyHScrollView extends HorizontalScrollView {

	private static final int MAX_X_OVERSCROLL_DISTANCE = 200;   
    private Context mContext;   
    private int mMaxXOverscrollDistance;
    
	public BouncyHScrollView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		mContext = context;   
		initBounceDistance();
	}
	public BouncyHScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		mContext = context;   
		initBounceDistance();
	}
	public BouncyHScrollView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
		mContext = context;   
		initBounceDistance();
	}
	
	private void initBounceDistance(){   
        final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();   
        mMaxXOverscrollDistance = (int) (metrics.density * MAX_X_OVERSCROLL_DISTANCE);   
    }   
	
    @Override  
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent){    
        //这块是关键性代码  
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, mMaxXOverscrollDistance, maxOverScrollY, isTouchEvent);     
    }  

}


另付:
自定义ScrollView,以解决viewflipper 与scrollview的手势冲突
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ScrollView;

/**
* 自定义ScrollView,并重写其onTouchEvent和dispatchTouchEvent方法,
* 以解决viewflipper 与scrollview的手势冲突
* @author yangjiantong
*
*/
public class MyScrollView extends ScrollView {

        GestureDetector gestureDetector;
        public MyScrollView(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
        }

        public MyScrollView(Context context, AttributeSet attrs) {
                super(context, attrs);
                // TODO Auto-generated constructor stub
        }

        public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
                // TODO Auto-generated constructor stub
        }

        public void setGestureDetector(GestureDetector gestureDetector) {
                this.gestureDetector = gestureDetector;
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                super.onTouchEvent(ev);
                return gestureDetector.onTouchEvent(ev);
        }

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev){
                gestureDetector.onTouchEvent(ev);
                super.dispatchTouchEvent(ev);
                return true;
        } 
        
}


如何解决ViewPager在ScrollView中滑动经常失效、无法正常滑动问题?(推荐)
解决方法只需要在接近水平滚动时ScrollView不处理事件而交由其子View(即这里的ViewPager)处理即可,重写ScrollView的onInterceptTouchEvent函数,如下:
package cc.newnews.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VerticalScrollView extends ScrollView {

	private GestureDetector mGestureDetector;

	public VerticalScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mGestureDetector = new GestureDetector(context, new YScrollDetector());
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		return super.onInterceptTouchEvent(ev)
				&& mGestureDetector.onTouchEvent(ev);
	}

	class YScrollDetector extends SimpleOnGestureListener {

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
			/**
			 * 如果我们滚动更接近水平方向,返回false,让子视图来处理它
			 */
			return (Math.abs(distanceY) > Math.abs(distanceX));
		}
	}
}

再将xml中的ScrollView改为<xxx.xxx.xxx.VerticalScrollView>即包名.重写的ScrollView的类名)即可。
本方法同样适用于ScrollView中ListView等其他View无法滚动。


https://github.com/johannilsson/android-pulltorefresh
分享到:
评论
1 楼 lkf871224 2011-10-18  
滑动list抖得相当厉害!

相关推荐

Global site tag (gtag.js) - Google Analytics