Android学习小demo(3)自定义ViewGroup 利用 scroller 实行屏幕滚动

前面一篇博文《关于View的ScrollTo, getScrollX 和 getScrollY》,我们讲解View 的scrollTo() 和 getScrollX() 的功能,也提到了它们其实是一般是配合scroller 类来进行屏幕的滑动的。有的朋友可能会问,为什么有了scrollTo() 之后,还要有scroller 类呢,区别在于,scrollTo()  和 scrollBy() 他们实现的是一个结果,也就是说,当你调用scrollTo(100, 0) 的时候,再重新绘制的时候,内容就已经出现在(100,0)的位置上了,缺少一个过程,而scroller 类就是来帮助我们展现这个滚动的过程的。

动画的原理其实是不停地重绘位置变化的内容,在视觉效果上,就会产生动画的效果。scroller 类的原理其实也正是如此,通过循环地绘制不同位置上的内容,来展现屏幕滚动。

它的原理图大概如下:

Android学习小demo(3)自定义ViewGroup 利用 scroller 实行屏幕滚动_第1张图片

这样讲就太抽象了,还是结合例子来看看吧。

拿上次写的自定义ViewGroup, 我们来进行扩展一下,代码如下:

public class CustomRotateViewGroup extends ViewGroup implements OnTouchListener{

	private static final int snapVelocity = 200;
	
	private static final int rows = 3;
	
	private static final int padding = 10;
	
	private Scroller mScroller;		
	
	private float downX;
	
	private float curX;	
	
	private int distanceX;
	
	private int screenWidth;	
	
	private VelocityTracker velocityTracker;
	
	private enum Direction{
		Current,
		Next		
	}
		
	public CustomRotateViewGroup(Context context) {
		super(context);
		initialize(context);
	}
	
	public CustomRotateViewGroup(Context context, AttributeSet attrs) {
		super(context, attrs);
		initialize(context);
	}
	
	//初始化操作
	public void initialize(Context context){
		DisplayMetrics displayMetrics = new DisplayMetrics();
		WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
		windowManager.getDefaultDisplay().getMetrics(displayMetrics);
		screenWidth = displayMetrics.widthPixels;//获得屏幕宽度
		
		mScroller = new Scroller(context);//获取scroller实例
		setOnTouchListener(this);//设置触摸监听
		//添加图片
		addCustomRotateView(context, R.drawable.photo1, 60, Color.YELLOW);
		addCustomRotateView(context, R.drawable.photo2, 30, Color.CYAN);
		addCustomRotateView(context, R.drawable.photo3, -30, Color.MAGENTA);
		addCustomRotateView(context, R.drawable.photo4, 60, Color.CYAN);
		addCustomRotateView(context, R.drawable.photo5, 30, Color.MAGENTA);
		addCustomRotateView(context, R.drawable.photo6, -30, Color.LTGRAY);
		addCustomRotateView(context, R.drawable.photo3, 60, Color.YELLOW);
		addCustomRotateView(context, R.drawable.photo4, 30, Color.LTGRAY);
		addCustomRotateView(context, R.drawable.photo5, -30, Color.CYAN);
	}
	
	private void addCustomRotateView(Context context, int resId, float degree, int color){
		addView(new CustomRotateView(context, resId, degree, color));
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int len = getChildCount();
		int parentHeight = MeasureSpec.getSize(heightMeasureSpec);		
		parentHeight -= padding * rows;
		for(int i = 0; i < len; i++){
			View child = getChildAt(i);
			int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight / rows, MeasureSpec.AT_MOST);
			measureChild(child, widthMeasureSpec, childHeightMeasureSpec);
		}
	}
        //布局函数,将图片从左向右排列,一列3个。
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int startX = 0;
		int startY = 0;		
		int len = getChildCount();
		boolean flagVertical = true;
		for(int i = 0; i <len; i++){
			View child = getChildAt(i);
			int childHeight = child.getMeasuredHeight();
			int childWidth = child.getMeasuredWidth();			
			child.layout(startX, startY, startX + childWidth, startY + childHeight);
			if(flagVertical){								
				if((i + 1) % rows == 0){					
					startX += childWidth;
					flagVertical = false;
					startY = 0;					
				}else{
					startY += childHeight + padding;
				}
			}else{												
				flagVertical = true;
				startY += childHeight + padding;				
			}			
		}
	}
	//重载View的computeScroll函数
	@Override
	public void computeScroll(){
		if(mScroller.computeScrollOffset()){//滚动是否结束,返回true表明滚动还未结束,同时在函数里获取currX和currY的值
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//滚动到当前位置
			postInvalidate();//强制重新绘制View,在框架里,View 的绘制会重新调用到computeScroll方法,达到循环绘制的目的
		}
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		//to add the event to the velocityTracker, in order to calculate the velocity of the slop
		if(velocityTracker == null){
			velocityTracker = VelocityTracker.obtain();
		}
		velocityTracker.addMovement(event);
		
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			//当停止动画的时候,它会马上滚动到终点,然后向动画设置为结束。 
			if(mScroller != null && !mScroller.isFinished()){
				mScroller.abortAnimation();				
			}
			downX = event.getX();
			curX = event.getX();			
			break;
		case MotionEvent.ACTION_MOVE:
			float moveX = event.getX();
			distanceX = (int)(moveX - curX);
			scrollBy(-distanceX, 0);			
			curX = moveX;
			break;
		case MotionEvent.ACTION_UP:			
			float upX = event.getX();
			distanceX = (int)(upX - downX);			
			velocityTracker.computeCurrentVelocity(1000);
			float vx = velocityTracker.getXVelocity();

			if (getScrollX() < 0) { // scroll to right (the first screen)
				mScroller.startScroll(getScrollX(), 0, 0 - getScrollX(), 0,1000);
			} else if (getScrollX() > screenWidth * (getChildCount() / 3 - 1)) { // scroll to left
				scrollToScreen(Direction.Current);
			} else {
				if (distanceX > 0) { // scroll to left
					if (distanceX > screenWidth / 2
							|| vx > snapVelocity) {
						scrollToScreen(Direction.Current);
					} else {
						scrollToScreen(Direction.Next);
					}
				} else if (distanceX < 0) {
					if (Math.abs(distanceX) > screenWidth / 2 || vx < -snapVelocity) {
						// scroll to right (the next screen)
						scrollToScreen(Direction.Next);
					} else {
						// scroll to left (the current screen)
						scrollToScreen(Direction.Current);
					}
				}
			}
			velocityTracker.recycle();
			invalidate();// 在这里一定要记得调用invalidate 来促使屏幕重绘,开始循环绘制的过程,不然。。。没有入口去滚。		
			break;
		}
		return true;
	}
	
	private void scrollToScreen(Direction to){
		if(to == Direction.Next){
			mScroller.startScroll(getScrollX(), 0, screenWidth - getScrollX() % screenWidth, 0, 1000);
		}else{
			mScroller.startScroll(getScrollX(), 0,  - getScrollX() % screenWidth, 0, 1000);
		}
	}

}

这个自定义的滚动ViewGroup 实现的主要功能如下:

1)首先我们添加了N(在这个例子中是9) 张自定义的图片放在这个ViewGroup里面

2)将这 N 张图片按照 3 个一列,从左向右排,为了易于展示和写代码,每一列都跟屏幕一样宽,这样在界面上的时候,就只看到第一列图片

3)当我们手指向左向右滑动的时候,屏幕会跟随着手指进行滑动

4)当我们手指滑过窗口的二分之一宽或者滑动的速度大于200的时候,手指松开,屏幕会自动滑向下一屏或者上一屏。

5)边界检测,当屏幕滑到最左屏最右屏的时候,再继续滑动,到达黑暗地域的时候,手指松开,会自动滚动回来。

下面是效果Gif:(之前滑动效果做反了,经同事提醒,才发现的确是有点不对劲,=_=!! ---修正于20140112)

Android学习小demo(3)自定义ViewGroup 利用 scroller 实行屏幕滚动_第2张图片


最后附上源代码,请点击 源代码下载,希望各位指出需要完善的地方,一起进步。

你可能感兴趣的:(android,ViewGroup,scroller,getScollX)