Android 自定義控件 GuideView 引導界面

最近看了hyman的博客:http://blog.csdn.net/lmj623565791/article/details/23692439 ,由於個人技術還比較薄弱,就在這個自定義控件的基礎上做了個拓展,支持水平和垂直的

下面是主要代碼;


    GuideView:

    

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Scroller;
import android.widget.Toast;

@SuppressLint("ClickableViewAccessibility")
public class GuideView extends ViewGroup {

	/**
	 * 
	 <attr name="orientation"> <enum name="horizontal" value="1" /> <enum
	 * name="vertical" value="2" /> </attr>
	 **/

	/** 滑動方向 */
	private int mOrientation = 0;

	/** 水平方向 */
	private int mHorientation = 0;

	/** 垂直方向 */
	private int mVertical = 1;

	/** 屏幕寬度 */
	private int mScreenWidth;

	/** 屏幕高度 */
	private int mScreenHeight;

	/** 滑動狀態 */
	private boolean isScrolling;

	/** 滑動輔助類 */
	private Scroller mScroller;

	/** 記錄當前的x/y的值 */
	private PointF mPointF;

	/** 記錄上一次的x、y值 */
	private PointF mLastPointF;

	/** Scroller 對應的開始座標 */
	private Point mScrollStartPoint;

	/** Scroller 對應的結束座標 */
	private Point mScrollStopPoint;

	/** 記錄滑動的距離 */
	private PointF mDistancePointF;

	/**ScrollXY 的差值*/
	private Point mDistanceScrollPoint;

	/** 加速度檢測 */
	private VelocityTracker mVelocityTracker;

	/**切換屏幕時的回調函數*/
	private OnPageChangeListener mOnPageChangeListener;

	/**
	 * 記錄當前頁
	 */
	private int currentPage = 0;

	public GuideView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub
		// 獲取自定義屬性
		TypedArray mTypeArray = context.obtainStyledAttributes(
				attrs, R.styleable.GuideView_orientation);

		mOrientation = mTypeArray.getInteger(
				R.styleable.GuideView_orientation_orientation, mOrientation);

		mTypeArray.recycle();
		// 獲取屏幕寬高
		initialScreen(context);

		mScroller = new Scroller(context);

		mPointF = new PointF();
		mLastPointF = new PointF();
		mScrollStartPoint = new Point();
		mScrollStopPoint = new Point();
		mDistancePointF = new PointF();
		mDistanceScrollPoint=new Point();
	}

	public GuideView(Context context, AttributeSet attrs) {
		this(context, attrs, 1);
		// TODO Auto-generated constructor stub
	}

	public GuideView(Context context) {
		this(context, null);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// 獲取子佈局,重新測量子佈局寬高
		int count = getChildCount();
		for (int i = 0; i < count; i++) {
			measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// TODO Auto-generated method stub

		if (changed) {
			// 重新測量layout的位置
			MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
			int childCount = getChildCount();
			if (mOrientation == mHorientation) {
				params.width = mScreenWidth * getChildCount();
				setLayoutParams(params);

				for (int i = 0; i < childCount; i++) {
					View view = getChildAt(i);
					if (view.getVisibility() != View.GONE) {
						view.layout(i * mScreenWidth, t, i * mScreenWidth
								+ mScreenWidth, b);
					}
				}
			} else if (mOrientation == mVertical) {

				params.height = mScreenHeight * getChildCount();
				setLayoutParams(params);

				for (int i = 0; i < childCount; i++) {
					View view = getChildAt(i);
					// view 沒有隱藏掉,就重新定位
					if (view.getVisibility() != View.GONE) {
						view.layout(l, i * mScreenHeight, r, i * mScreenHeight
								+ mScreenHeight);
					}
				}
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		// 先進行事件判斷攔截
		if(currentPage==getChildCount()-1){
			Toast.makeText(getContext(), "finish", Toast.LENGTH_SHORT).show();
			return super.onTouchEvent(event);
		}
		
		if (isScrolling) 
			return super.onTouchEvent(event);
		
			mPointF.x = event.getX();
			mPointF.y = event.getY();

			// 初始化加速度檢測器
			initialVelocity(event);

			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				// 當用戶觸摸時記錄下座標信息
				Log.i("info"," *******mPoint value****"+"x:"+mPointF.x+"y:"+mPointF.y);
				getStartScrollXY();
				mLastPointF.x = mPointF.x;
				mLastPointF.y = mPointF.y;
			} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
				Log.i("info"," *******mLastPoint value****"+"x:"+mLastPointF.x+"y:"+mLastPointF.y);
				Log.i("info"," *******mPoint value****"+"x:"+mPointF.x+"y:"+mPointF.y);
				Log.i("info"," *******************************************");
				Log.i("info"," *******************************************");
				/**
				 * Stops the animation. Contrary to
				 * {@link #forceFinished(boolean)}, aborting the animating cause
				 * the scroller to move to the final x and y position 源碼說明:
				 * mScroller.abortAnimation() 如果滑動還沒有結束,那麼就終止滑動。
				 * 
				 * @see #forceFinished(boolean)
				 */
				if (!mScroller.isFinished()) {
					mScroller.abortAnimation();
				}

				mDistancePointF.x = mLastPointF.x - mPointF.x;
				mDistancePointF.y = mLastPointF.y - mPointF.y;

				Log.i("info"," *******mDistancePointF value ******"+"dx: "+mDistancePointF.x+" dy: "+mDistancePointF.y);
				getStopScrollXY();

				// 先判斷滑動的方向確定滑動的距離 scrollBy(x,y)

				// 1.y軸---向上滑動--下一個視圖
				// 2.y軸---向下滑動--上一個視圖
				// 3.x軸---向左滑動--下一個視圖
				// 4.x軸---向右滑動--上一個視圖

				/**
				 * 320*480 -8 mlasty=-10 currenty=-2
				 * distance=mlasty-currenty=-8《0 scrolly+distance<0?
				 * 
				 * 條件都滿足時,確定視圖向上滑動,加載 下一個視圖
				 * 
				 * 重新定義distanceY的值以便於ScrollBy(x,y)調用
				 * 
				 * 補充說明:
				 * getScrollX()說明:=手機屏幕顯示區域左上角x座標減去MultiViewGroup視圖左上角x座標=320
				 * 
				 * getScrollY()說明:=手機屏幕顯示區域左上角y座標減去MultiViewGroup視圖左上角y座標=0(
				 * 因爲子視圖的高度和手機屏幕高度一樣)
				 * 
				 * 
				 **/

				if (mOrientation == mHorientation) {
					if (mDistancePointF.x > 0
							&& mScrollStopPoint.x + mDistancePointF.x > getWidth()-mScreenWidth) {
						mDistancePointF.x = getWidth() - mScreenWidth -mScrollStopPoint.x;
					} else if (mDistancePointF.x < 0
							&& mScrollStopPoint.x + mDistancePointF.x < 0) {
						mDistancePointF.x = - mScrollStopPoint.x;
					}
					scrollBy((int) mDistancePointF.x, 0);
				} else if (mOrientation == mVertical) {
					if (mDistancePointF.y < 0
							&& mScrollStopPoint.y + mDistancePointF.y < 0) {
						mDistancePointF.y = -mScrollStopPoint.y;
					}
					if (mDistancePointF.y > 0
							&& mScrollStopPoint.y + mDistancePointF.y > getHeight()
							- mScreenHeight) {
						mDistancePointF.y = getHeight() - mScreenHeight
								- mScrollStopPoint.y;
					}
					scrollBy(0, (int) mDistancePointF.y);
				}
				mLastPointF.x = mPointF.x;
				mLastPointF.y = mPointF.y;
			} else if (event.getAction() == MotionEvent.ACTION_UP) {
				getStopScrollXY();
				getDistanceScrollXY();
				//比較滑動方向趨勢
				//判斷是上滑動還是下滑動
				if(checkDirection()){
					//上滑動《加載更多》
					if(isScrollToNext()){
						//能滑動到下一頁
						if(mOrientation==mHorientation){
							mScroller.startScroll(getScrollX(), 0,mScreenWidth - mDistanceScrollPoint.x,0);
						}else if (mOrientation==mVertical){
							mScroller.startScroll(0, getScrollY(), 0, mScreenHeight
									- mDistanceScrollPoint.y);
						}
					}else{
						//不能滑動到下一頁
						if(mOrientation==mHorientation){
							mScroller.startScroll(getScrollX(), 0,-mDistanceScrollPoint.x,0);
						}else if (mOrientation==mVertical){
							mScroller.startScroll(0, getScrollY(), 0, -mDistanceScrollPoint.y);
						}
					}
				}else{
					//《下滑動,刷新》
					if(isScrollToprivew()){
						//能滑動到上一頁
						if(mOrientation==mHorientation){
							mScroller.startScroll( getScrollX(), 0,
									-mScreenWidth - mDistanceScrollPoint.x,0);
						}else if (mOrientation==mVertical){
							mScroller.startScroll(0, getScrollY(), 0,
									-mScreenHeight - mDistanceScrollPoint.y);
						}
					}else{
						//不能滑動到上一頁
						if(mOrientation==mHorientation){
							mScroller.startScroll(getScrollX(),0, -mDistanceScrollPoint.x, 0);
						}else if (mOrientation==mVertical){
							mScroller.startScroll(0, getScrollY(), 0, -mDistanceScrollPoint.y);
						}
					}
				}

				isScrolling = true;
				postInvalidate();
				recycleVelocity();
			}

			return true;
	}

	/**
	 * Called by a parent to request that a child update its values for mScrollX
	 * and mScrollY if necessary. This will typically be done if the child is
	 * animating a scroll using a {@link android.widget.Scroller Scroller}
	 * object.
	 * 
	 * 爲了易於控制滑屏控制,Android框架提供了 computeScroll()方法去控制這個流程。在繪製View時,會在draw()過程調用該
	 * 方法。因此, 再配合使用Scroller實例,我們就可以獲得當前應該的偏移座標,手動使View/ViewGroup偏移至該處。
	 * computeScroll()方法原型如下,該方法位於ViewGroup.java類中   
	 */
	@Override
	public void computeScroll() {
		// TODO Auto-generated method stub
		super.computeScroll();

		if (mOrientation== mVertical) {
			if (mScroller.computeScrollOffset()) {
				scrollTo(0, mScroller.getCurrY());
				postInvalidate();
			} else {
				int position = getScrollY() / mScreenHeight;
				if (position != currentPage) {
					if (mOnPageChangeListener != null) {
						currentPage = position;
						mOnPageChangeListener.onPageChange(currentPage);
					}
				}
			}
		} else if (mOrientation== mHorientation) {
			if (mScroller.computeScrollOffset()) {
				scrollTo(mScroller.getCurrX(), 0);
				postInvalidate();
			} else {
				int position = getScrollX() / mScreenWidth;
				if (position != currentPage) {
					if (mOnPageChangeListener != null) {
						currentPage = position;
						mOnPageChangeListener.onPageChange(currentPage);
					}
				}
			}
		}
		isScrolling = false;
	}
	/************************************ Method *********************************************/

	/**
	 * 獲取屏幕寬高
	 */
	public void initialScreen(Context context) {
		WindowManager mWindowManager = (WindowManager) context
				.getSystemService(Context.WINDOW_SERVICE);
		DisplayMetrics outMetrics = new DisplayMetrics();
		mWindowManager.getDefaultDisplay().getMetrics(outMetrics);
		mScreenWidth = outMetrics.widthPixels;
		mScreenHeight = outMetrics.heightPixels;
	}

	/**
	 * 初始化加速度檢測器
	 * 
	 * @param event
	 */
	private void initialVelocity(MotionEvent event) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);
	}
	/**
	 * 初始化scrollX scrollY
	 */
	private void getStartScrollXY(){
		mScrollStartPoint.x = getScrollX();
		mScrollStartPoint.y = getScrollY();
	}
	/**
	 * 停止滑動後的ScrollX ScrollY
	 */
	private void getStopScrollXY(){
		mScrollStopPoint.x = getScrollX();
		mScrollStopPoint.y = getScrollY();
	}
	/**
	 * 比較滑動的ScrollX ScrollY差值
	 */
	private void getDistanceScrollXY(){
		mDistanceScrollPoint.x = mScrollStopPoint.x-mScrollStartPoint.x;
		mDistanceScrollPoint.y = mScrollStopPoint.y-mScrollStartPoint.y;
	}
	/**
	 * 檢查滑動方向
	 * @return  true 加載更多  false 刷新
	 */
	public boolean checkDirection(){
		boolean mDirection =false;
		if (mOrientation == mVertical) {
			mDirection = mDistanceScrollPoint.y > 0 ? true : false;
		} else if (mOrientation== mHorientation) {
			mDirection = - mDistanceScrollPoint.x < 0 ? true : false;
		}
		return mDirection;
	}
	/**
	 * 根據滑動距離判斷 是否能夠滑動到下一屏
	 *  加載跟多
	 * @return
	 */
	private boolean isScrollToNext() {
		boolean isScrollTo = false;
		if (mOrientation == mVertical) {
			isScrollTo = mDistanceScrollPoint.y > mScreenHeight / 2
					|| Math.abs(getVelocity()) > 600;
		} else if (mOrientation == mHorientation) {
			isScrollTo = mDistanceScrollPoint.x > mScreenWidth / 2
					|| Math.abs(getVelocitx()) > 600;
		}
		return isScrollTo;
	}

	/**
	 * 根據滑動距離判斷 是否能夠滑動到上一屏
	 * 刷新
	 * @return
	 */
	private boolean isScrollToprivew() {
		boolean isScrollTo = false;
		if (mOrientation == mVertical) {
			isScrollTo = -mDistanceScrollPoint.y > mScreenHeight / 2
					|| Math.abs(getVelocity()) > 600;
		} else if (mOrientation == mHorientation) {
			isScrollTo = -mDistanceScrollPoint.x > mScreenWidth / 2
					|| Math.abs(getVelocitx()) > 600;
		}
		return isScrollTo;
	}
	/**
	 * 獲取x方向的加速度
	 * 
	 * @return
	 */
	private int getVelocitx() {
		mVelocityTracker.computeCurrentVelocity(1000);
		int velocitx = (int) mVelocityTracker.getXVelocity(1000);
		velocitx = (int) mVelocityTracker.getXVelocity(1000);
		return velocitx;
	}
	/**
	 * 獲取y方向的加速度
	 * 
	 * @return
	 */
	private int getVelocity() {
		mVelocityTracker.computeCurrentVelocity(1000);
		int velocity = (int) mVelocityTracker.getYVelocity(1000);
		velocity = (int) mVelocityTracker.getYVelocity(1000);
		return velocity;
	}
	/**
	 * 釋放資源
	 */
	private void recycleVelocity() {
		if (mVelocityTracker != null) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
	}
	/**
	 * 設置回調接口
	 * 
	 * @param onPageChangeListener
	 */
	public void setOnPageChangeListener(
			OnPageChangeListener onPageChangeListener) {
		mOnPageChangeListener = onPageChangeListener;
	}

	/**
	 * 回調接口
	 * 
	 * @author zhy
	 * 
	 */
	public interface OnPageChangeListener {
		void onPageChange(int currentPage);
	}
}

    attrs:

    

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="GuideView_orientation">
        <attr name="orientation">
            <enum name="Horientation" value="0" />
            <enum name="Vertical" value="1" />
        </attr>
    </declare-styleable>

</resources>

源碼下載地址:  http://download.csdn.net/detail/anddroid_lanyan/8660753

在項目還存在遺留問題:(取消page的攔截事件)當我把背景色改成白色時,水平佈局,左右滑動反覆,會出現白邊,還沒搞明白具體問題,誰若是知道麻煩告知。

改完這個自定義控件後,我發現一件事,只要你願意堅持去做一件事,就一定會有所收穫。

發佈了81 篇原創文章 · 獲贊 2 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章