標籤:移動 viewpager slidemenu 自訂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 */@Overrideprotected 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 */@Overrideprotected 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 */@Overridepublic 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);}@Overridepublic 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() {@Overridepublic 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詳細代碼:
Android開發之自訂View專題(四):自訂ViewGroup