有時候,我們會有這樣的需求,一個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詳細代碼: