android view 自定義viewgroup 例項--螢幕滑動

心鑫發表於2014-03-19
package com.qin.scrollerview;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;

//自定義ViewGroup , 包含了三個LinearLayout控制元件,存放在不同的佈局位置,通過scrollBy或者scrollTo方法切換
public class MultiViewGroup extends ViewGroup {

	private Context mContext;
	
	private static String TAG = "MultiViewGroup";
    private int curScreen = 0 ;  //當前屏
	
    private Scroller mScroller = null ;
    
    
	public MultiViewGroup(Context context) {
		super(context);
		mContext = context;
		init();
	}

	public MultiViewGroup(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		init();
	}
   //startScroll 滑屏
	public void startMove(){
		curScreen ++ ;
		Log.i(TAG, "----startMove---- curScreen " + curScreen);
		
		Log.i(TAG, "----width  " + getWidth());
		//採用Scroller類控制滑動過程
		mScroller.startScroll((curScreen-1) *getWidth(), 0, 
				getWidth(), 0,3000);
		//暴力點直接到目標出
		//scrollTo(curScreen * getWidth(), 0);
		//其實在點選按鈕的時候,就回觸發View繪製流程,這兒我們在強制繪製下View
		invalidate();
	}
	//停止滑屏
	public void stopMove(){
		
		Log.v(TAG, "----stopMove ----");
		
		if(mScroller != null){
			//如果動畫還沒結束,我們就按下了結束的按鈕,那我們就結束該動畫,即馬上滑動指定位置
			if(!mScroller.isFinished()){
				
				int scrollCurX= mScroller.getCurrX() ;
              	//判斷是否達到下一屏的中間位置,如果達到就抵達下一屏,否則保持在原螢幕
				//int moveX = scrollCurX - mScroller.getStartX()   ;
                // Log.i(TAG, "----mScroller.is not finished ---- shouldNext" + shouldNext);
				//boolean shouldNext = moveX >= getWidth() / 2 ;
				int descScreen = ( scrollCurX +  getWidth() / 2) /  getWidth() ;
				
				Log.i(TAG, "----mScroller.is not finished ---- shouldNext" + descScreen);
				
				Log.i(TAG, "----mScroller.is not finished ---- scrollCurX " + scrollCurX);
				mScroller.abortAnimation();

			    //停止了動畫,我們馬上滑倒目標位置
				scrollTo(descScreen * getWidth() , 0);
				mScroller.forceFinished(true);
				
				curScreen = descScreen ;
			}
	    }
			else
				Log.i(TAG, "----OK mScroller.is  finished ---- ");
	}
	// 只有當前LAYOUT中的某個CHILD導致SCROLL發生滾動,才會致使自己的COMPUTESCROLL被呼叫
	@Override
	public void computeScroll() {	
		// TODO Auto-generated method stub
		Log.e(TAG, "computeScroll");
		// 如果返回true,表示動畫還沒有結束
		// 因為前面startScroll,所以只有在startScroll完成時 才會為false
		if (mScroller.computeScrollOffset()) {
			Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());
			// 產生了動畫效果 每次滾動一點
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			
			Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());
			
		    //重新整理View 否則效果可能有誤差
			postInvalidate();
		}
		else
			Log.i(TAG, "have done the scoller -----");
	}
	/////以上可以演示Scroller類的使用
	//// --------------------------------
	/////--------------------------------
	
	private static final int TOUCH_STATE_REST = 0;
	private static final int TOUCH_STATE_SCROLLING = 1;
	private int mTouchState = TOUCH_STATE_REST;
	//-------------------------- 
	//處理觸控事件 ~
	public static int  SNAP_VELOCITY = 600 ;
	private int mTouchSlop = 0 ;
	private float mLastionMotionX = 0 ;
	private float mLastMotionY = 0 ;
	//處理觸控的速率
	private VelocityTracker mVelocityTracker = null ;
	
	// 這個感覺沒什麼作用 不管true還是false 都是會執行onTouchEvent的 因為子view裡面onTouchEvent返回false了
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);

		final int action = ev.getAction();
		//表示已經開始滑動了,不需要走該Action_MOVE方法了(第一次時可能呼叫)。
		if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
			return true;
		}

		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_MOVE:
			Log.e(TAG, "onInterceptTouchEvent move");
			final int xDiff = (int) Math.abs(mLastionMotionX - x);
			//超過了最小滑動距離
			if (xDiff > mTouchSlop) {
				mTouchState = TOUCH_STATE_SCROLLING;
			}
			break;

		case MotionEvent.ACTION_DOWN:
			Log.e(TAG, "onInterceptTouchEvent down");
			mLastionMotionX = x;
			mLastMotionY = y;
			Log.e(TAG, mScroller.isFinished() + "");
			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;

			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			Log.e(TAG, "onInterceptTouchEvent up or cancel");
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		Log.e(TAG, mTouchState + "====" + TOUCH_STATE_REST);
		return mTouchState != TOUCH_STATE_REST;
	}
	public boolean onTouchEvent(MotionEvent event){
		
		Log.i(TAG, "--- onTouchEvent--> " );

		// TODO Auto-generated method stub
		Log.e(TAG, "onTouchEvent start");
		if (mVelocityTracker == null) {
			
			Log.e(TAG, "onTouchEvent start-------** VelocityTracker.obtain");
			
			mVelocityTracker = VelocityTracker.obtain();
		}
		
		mVelocityTracker.addMovement(event);
		
		super.onTouchEvent(event);
		
		//手指位置地點
		float x = event.getX();
		float y = event.getY();
		
		
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
			//如果螢幕的動畫還沒結束,你就按下了,我們就結束該動畫
			if(mScroller != null){
				if(!mScroller.isFinished()){
					mScroller.abortAnimation();
				}
			}
			
			mLastionMotionX = x ;
			break ;
		case MotionEvent.ACTION_MOVE:
			int detaX = (int)(mLastionMotionX - x );
			scrollBy(detaX, 0);
			
			Log.e(TAG, "--- MotionEvent.ACTION_MOVE--> detaX is " + detaX );
			mLastionMotionX = x ;
			
			break ;
		case MotionEvent.ACTION_UP:
			
			final VelocityTracker velocityTracker = mVelocityTracker  ;
			velocityTracker.computeCurrentVelocity(1000);
			
			int velocityX = (int) velocityTracker.getXVelocity() ;
			
			Log.e(TAG , "---velocityX---" + velocityX);
			
			//滑動速率達到了一個標準(快速向右滑屏,返回上一個螢幕) 馬上進行切屏處理
			if (velocityX > SNAP_VELOCITY && curScreen > 0) {
				// Fling enough to move left
				Log.e(TAG, "snap left");
				snapToScreen(curScreen - 1);
			}
			//快速向左滑屏,返回下一個螢幕)
			else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){
				Log.e(TAG, "snap right");
				snapToScreen(curScreen + 1);
			}
			//以上為快速移動的 ,強制切換螢幕
			else{
				//我們是緩慢移動的,因此先判斷是保留在本螢幕還是到下一螢幕
				snapToDestination();
			}
			
			if (mVelocityTracker != null) {
				mVelocityTracker.recycle();
				mVelocityTracker = null;
			}
			
			mTouchState = TOUCH_STATE_REST ;
			
		    break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST ;
			break;
		}
		
		return true ;
	}
	////我們是緩慢移動的
	private void snapToDestination(){
		//當前的偏移位置
		int scrollX = getScrollX() ;
		int scrollY = getScrollY() ;
		
		Log.e(TAG, "### onTouchEvent snapToDestination ### scrollX is " + scrollX);

		//判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原螢幕	
		//直接使用這個公式判斷是哪一個螢幕 前後或者自己
		//判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原螢幕
		// 這樣的一個簡單公式意思是:假設當前滑屏偏移值即 scrollCurX 加上每個螢幕一半的寬度,除以每個螢幕的寬度就是
		//  我們目標屏所在位置了。 假如每個螢幕寬度為320dip, 我們滑到了500dip處,很顯然我們應該到達第二屏	
		int destScreen = (getScrollX() + getWidth() / 2 ) / getWidth() ;
		
		 
	    Log.e(TAG, "### onTouchEvent  ACTION_UP### dx destScreen " + destScreen);
		
		snapToScreen(destScreen);
	}
    private void snapToScreen(int whichScreen){	
	    //簡單的移到目標螢幕,可能是當前屏或者下一螢幕
	    //直接跳轉過去,不太友好
	    //scrollTo(mLastScreen * getWidth(), 0);
	    //為了友好性,我們在增加一個動畫效果
	    //需要再次滑動的距離 屏或者下一螢幕的繼續滑動距離

	    curScreen = whichScreen ;
	    
	    if(curScreen > getChildCount() - 1)
	    	curScreen = getChildCount() - 1 ;
	    
	    int dx = curScreen*getWidth() - getScrollX() ;
	    
	    Log.e(TAG, "### onTouchEvent  ACTION_UP### dx is " + dx);
	    
	    mScroller.startScroll(getScrollX(), 0, dx, 0,Math.abs(dx) * 2);
	    
	    //此時需要手動重新整理View 否則沒效果
	    invalidate();
	    
    }
		
    private void init() {
		
		mScroller = new Scroller(mContext);
		
		// 初始化3個 LinearLayout控制元件
		LinearLayout oneLL = new LinearLayout(mContext);
		oneLL.setBackgroundColor(Color.RED);
        addView(oneLL);
		
		LinearLayout twoLL = new LinearLayout(mContext);
		twoLL.setBackgroundColor(Color.YELLOW);
		addView(twoLL);
		
		LinearLayout threeLL = new LinearLayout(mContext);
		threeLL.setBackgroundColor(Color.BLUE);
		addView(threeLL);
		
		//初始化一個最小滑動距離
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	// measure過程
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		Log.i(TAG, "--- start onMeasure --");

		// 設定該ViewGroup的大小
		int width = MeasureSpec.getSize(widthMeasureSpec);
		int height = MeasureSpec.getSize(heightMeasureSpec);
		setMeasuredDimension(width, height);

		int childCount = getChildCount();
		Log.i(TAG, "--- onMeasure childCount is -->" + childCount);
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			// 設定每個子檢視的大小 , 即全屏
			child.measure(getWidth(), MultiScreenActivity.scrrenHeight);
		}
	}

	private int curPage = 0 ;
	
	// layout過程
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// TODO Auto-generated method stub
		Log.i(TAG, "--- start onLayout --");
		int startLeft = 0; // 每個子檢視的起始佈局座標
		int startTop = 10; // 間距設定為10px 相當於 android:marginTop= "10px"
		int childCount = getChildCount();
		Log.i(TAG, "--- onLayout childCount is -->" + childCount );

		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			
			//即使可見的,才劃到螢幕上
			if(child.getVisibility() != View.GONE)
			    child.layout(startLeft, startTop, 
					   startLeft + getWidth(), 
					   startTop + MultiScreenActivity.scrrenHeight );
			    
			    startLeft = startLeft + getWidth() ; //校準每個子View的起始佈局位置
			    //三個子檢視的在螢幕中的分佈如下 [0 , 320] / [320,640] / [640,960]
		}
	}
}

相關文章