Android開發之自定義View專題(四):自定義ViewGroup

     有時候,我們會有這樣的需求,一個activity裏面需要有兩個或者多個界面切換,就像Viewpager那樣。但是在這些界面裏面又需要能夠有listView,gridview等組件。如果是縱向的,似乎還好,沒什麼影響,那麼如果是橫向的,那麼就會出事情。因爲Viewpager會攔截觸摸事件。而如果將Viewpager的觸摸事件攔截掉給裏面的子控件,那麼Viewpager又不能響應滑動事件了。那麼如何又能讓界面之間能夠來回切換,又能讓裏面的子控件的觸摸事件也能毫無影響的響應呢,這個時候,我們需要自定義一個Viewgroup,重寫裏面的觸摸攔截方法即可。

     博主自定義的ViewGroup類似於SlideMenu,包含兩個界面的來回切換,博主特意放了一個可以橫向滑動item的listView組件在的個界面試驗,這個listView控件也是博主之前從網上找出來用的,還不錯的一個控件。詳細看效果圖:




好了,老規矩,完整項目下載地址:

http://download.csdn.net/detail/victorfreedom/8329667

有興趣的同學可以下載下來研究學習。

自定義ViewGroup詳細代碼:




package com.freedom.slideviewgroup.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.Scroller;

import com.freedom.slideviewgroup.FreedomApplication;
import com.freedom.slideviewgroup.utils.DptoPxUtil;

/**
 * @ClassName: SlideMenu
 * @author victor_freedom ([email protected])
 * @createddate 2015-1-5 下午8:00:36
 * @Description: TODO
 */

public class SlideMenu extends ViewGroup {

	private Context mContext;

	// 默認第一個
	private int currentScreen = 0; // 當前屏
	// 移動控制者
	private Scroller mScroller = null;
	// 判斷是否可以移動
	private boolean canScroll = false;
	// 是否攔截點擊事件,false表示攔截,true表示將點擊事件傳遞給子控件
	private boolean toChild = false;
	// 處理觸摸事件的移動速率標準
	public static int SNAP_VELOCITY = 600;
	// 觸發move的最小滑動距離
	private int mTouchSlop = 0;
	// 最後一點的X座標
	private float mLastionMotionX = 0;
	// 處理觸摸的速率
	private VelocityTracker mVelocityTracker = null;
	// 左右子控件的監聽器
	private LeftListener leftListener;
	private RightListener rightListener;
	// 觸摸狀態
	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 TOHCH_LEFT = 140;
	public static int TOHCH_RIGHT = FreedomApplication.mScreenWidth;
	public static final String TAG = "SlideMenu";

	public SlideMenu(Context context) {
		super(context);
		mContext = context;
		init();
	}

	public SlideMenu(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		init();
	}

	/**
	 * @Title: init
	 * @Description: 初始化滑動相關的東西
	 * @throws
	 */
	private void init() {
		mScroller = new Scroller(mContext, new AccelerateInterpolator());
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	/**
	 * @Title: onMeasure
	 * @Description: 設定viewGroup大小
	 * @param widthMeasureSpec
	 * @param heightMeasureSpec
	 * @throws
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int width = MeasureSpec.getSize(widthMeasureSpec);
		int height = MeasureSpec.getSize(heightMeasureSpec);
		setMeasuredDimension(width, height);
		for (int i = 0; i < getChildCount(); i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}
	}

	/**
	 * @Title: onLayout
	 * @Description: 設置子控件的分佈位置
	 * @param changed
	 * @param l
	 *            left
	 * @param t
	 *            top
	 * @param r
	 *            right
	 * @param b
	 *            bottom
	 * @throws
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int startLeft = 0; // 每個子視圖的起始佈局座標
		int childCount = getChildCount();

		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			child.layout(startLeft, 0, startLeft + getWidth(), getHeight());
			startLeft = startLeft + getWidth(); // 校準每個子View的起始佈局位置
		}
	}

	/**
	 * @Title: onInterceptTouchEvent
	 * @Description:觸摸事件攔截判定
	 * @param ev
	 * @return
	 * @throws
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		// 如果當前正在滑動狀態,則攔截事件
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {
			return true;
		}

		final float x = ev.getX();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			mLastionMotionX = x;
			// 判斷當前狀態
			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;
			// 判斷是否能夠響應滑動事件
			if ((ev.getX() < DptoPxUtil.dip2px(mContext, TOHCH_LEFT) && currentScreen == 1)
					|| (ev.getX() > TOHCH_RIGHT
							- DptoPxUtil.dip2px(mContext, 200) && currentScreen == 0)) {
				canScroll = true;
				toChild = false;
				return super.onInterceptTouchEvent(ev);
			} else {
				// 如果不能則不攔截事件
				canScroll = false;
				toChild = true;
				return false;
			}
		case MotionEvent.ACTION_MOVE:
			if (toChild) {
				return false;
			}
			final int differentX = (int) Math.abs(mLastionMotionX - x);
			// 超過了最小滑動距離,並且沒有傳遞事件給子控件,則更改狀態
			if (differentX > mTouchSlop) {
				mTouchState = TOUCH_STATE_SCROLLING;
			}
			break;
		case MotionEvent.ACTION_UP:
			if (toChild) {
				return false;
			}
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		return super.onInterceptTouchEvent(ev);
	}

	/**
	 * @Title: onTouchEvent
	 * @Description: 觸摸事件響應
	 * @param event
	 * @return
	 * @throws
	 */
	public boolean onTouchEvent(MotionEvent event) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		// 獲取移動的速率
		mVelocityTracker.addMovement(event);
		super.onTouchEvent(event);

		// 手指位置地點
		float x = event.getX();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 如果屏幕的動畫還沒結束,你就按下了,我們就結束該動畫
			if (mScroller != null) {
				if (!mScroller.isFinished()) {
					mScroller.abortAnimation();
				}
			}
			mLastionMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			if (canScroll) {
				int detaX = (int) (mLastionMotionX - x);
				mLastionMotionX = x;
				// 移動距離
				scrollBy(detaX, 0);

			}
			break;
		case MotionEvent.ACTION_UP:
			final VelocityTracker velocityTracker = mVelocityTracker;
			velocityTracker.computeCurrentVelocity(1000);
			int velocityX = (int) velocityTracker.getXVelocity();
			// 滑動速率達到了一個標準(快速向右滑屏,返回上一個屏幕) 馬上進行切屏處理
			if (velocityX > SNAP_VELOCITY && currentScreen > 0 && canScroll) {
				changedScreen(currentScreen - 1);
			}
			// 快速向左滑屏,返回下一個屏幕)
			else if (velocityX < -SNAP_VELOCITY
					&& currentScreen < (getChildCount() - 1) && canScroll) {
				changedScreen(currentScreen + 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 super.onInterceptTouchEvent(event);
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {// 如果返回true,則代表正在模擬數據,false表示已經停止模擬數據
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 更新偏移量
			postInvalidate();
		}
	}

	/**
	 * @Title: startMove
	 * @Description: 這是從第一個屏幕跳轉到第二個屏幕的快捷方法
	 * @throws
	 */
	public void startMove() {
		if (currentScreen == 1) {
			return;
		}
		if (currentScreen == 0 && rightListener != null) {
			new Thread(new Runnable() {

				@Override
				public void run() {
					rightListener.postNotifyDataChange();
				}
			}).start();

		}
		currentScreen++;
		mScroller.startScroll((currentScreen - 1) * getWidth(), 0, getWidth(),
				0, 600);
		// 刷新界面
		invalidate();// invalidate -> drawChild -> child.draw -> computeScroll

	}

	/**
	 * @Title: startMoves
	 * @Description: 跳轉到第一個屏幕
	 * @throws
	 */
	public void startMoves() {
		changedScreen(0);
	}

	/**
	 * @Title: snapToDestination
	 * @Description: 當緩慢移動的時候,判斷跳轉屏幕
	 * @throws
	 */
	private void snapToDestination() {
		int destScreen = (getScrollX() + getWidth() / 3) / getWidth();
		changedScreen(destScreen);
	}

	/**
	 * @Title: changedScreen
	 * @Description: 跳轉屏幕
	 * @param whichScreen
	 * @throws
	 */
	private void changedScreen(int whichScreen) {
		currentScreen = whichScreen;
		if (currentScreen > getChildCount() - 1) {
			currentScreen = getChildCount() - 1;
		}
		if (currentScreen == 0 && leftListener != null) {
			leftListener.notifyDataChange();
		}
		if (currentScreen == 1 && rightListener != null) {
			rightListener.notifyDataChange();
		}
		// getScrollX得到的是當前視圖相對於父控件的偏移量。初始值是0,
		int dx = currentScreen * getWidth() - getScrollX();
		// dx爲正值時,屏幕向右滑動,dx爲負值時,屏幕向左滑動
		mScroller.startScroll(getScrollX(), 0, dx, 0, 600);
		postInvalidate();

	}

	public interface LeftListener {
		public void notifyDataChange();
	}

	public interface RightListener {
		public void notifyDataChange();

		public void postNotifyDataChange();
	}

	public void setLeftListener(LeftListener leftListener) {
		this.leftListener = leftListener;
	}

	public void setRightListener(RightListener rightListener) {
		this.rightListener = rightListener;
	}

}


自此自定義View專題已經講解完畢,相信所有人都對自定義View有了一個初步的認識,基本上就是那麼幾個步驟。而自定義ViewGroup相對於自定義View多了一個步驟在於要重寫onLayout方法來擺放包含在裏面的子控件,其餘的,都差不多。

     

好了,老規矩,完整項目下載地址:

http://download.csdn.net/detail/victorfreedom/8329667

有興趣的同學可以下載下來研究學習。

自定義ViewGroup詳細代碼:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章