前一篇寫到Android事件分發機制學習筆記,下面我們通過一個實例的應用來實踐理解下Android事件分發的機制。我們這裏來實現一個圖片的輪播功能,最後順便實現下圖片的自動輪播。
我們的圖片輪播是封裝在一個ViewGroup裏,當我們進行橫向滑動的時候,我們需要阻止事件從ViewGroup往子控件分發,ViewGroup來消費我們當前的滑動圖片何去何從。下面我們貼出我們的封裝的ViewGroup的代碼實現如下:
public class ImageSwitcher extends ViewGroup {
private String TAG = ImageSwitcher.class.getSimpleName();
private static final int SNAP_VELOCITY = 300;
private Scroller scroller;
private VelocityTracker mVelocityTracker;
private int mTouchSlop;
private float mMotionX;
private int mImageWidth;
private int imageCount;
private int mIndex;
private int mImageHeight;
private int[] imageItems;
private boolean forceToRelayout;
private int mTouchState = TOUCH_STATE_REST;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
private static final int AUTO_MSG = 0;
private static final int START_MSG =2;
private static final int HANDLE_MSG = 1;
private static final long PHOTO_CHANGE_TIME = 4000;
private Handler mHandler = new Handler(){ //處理圖片自動或者手動滾動操作
public void handleMessage(Message msg) {
switch (msg.what) {
case AUTO_MSG:
scrollToNext();
mHandler.sendEmptyMessageDelayed(AUTO_MSG, PHOTO_CHANGE_TIME);
break;
case START_MSG:
mHandler.sendEmptyMessageDelayed(AUTO_MSG, PHOTO_CHANGE_TIME);
break;
case HANDLE_MSG:
mHandler.removeMessages(AUTO_MSG);
mHandler.sendEmptyMessageDelayed(AUTO_MSG, PHOTO_CHANGE_TIME);
default:
break;
}
}
};
/**
* 表示滾動到下一張圖片這個動作
*/
private static final int SCROLL_NEXT = 0;
/**
* 表示滾動到上一張圖片這個動作
*/
private static final int SCROLL_PREVIOUS = 1;
private static final int SCROLL_BACK = 2;
public ImageSwitcher(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
/**
* 當View被添加到Window容器的時候纔開始執行:生命週期依次先後 onMeasure > onLayout > onDraw >onAttachedToWindow
*/
@Override
protected void onAttachedToWindow(){
super.onAttachedToWindow();
mHandler.sendEmptyMessage(START_MSG); //發送消息讓圖片自動開始滾動
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(changed || forceToRelayout){
imageCount = getChildCount();
mImageWidth = getMeasuredWidth();
mImageHeight = getMeasuredHeight();
int marginLeft = 0;
scroller.abortAnimation(); //設置scroller爲滾動狀態
this.scrollTo(0, 0); //每次重新佈局時候,重置滾動初始位置
int[] items = { getIndexForItem(1), getIndexForItem(2),
getIndexForItem(3), getIndexForItem(4),
getIndexForItem(5) };
imageItems = items;
for (int i = 0; i < items.length; i++) {
ImageView childView = (ImageView)getChildAt(items[i]);
childView.layout(marginLeft, 0, marginLeft
+ mImageWidth , mImageHeight);
marginLeft = marginLeft + mImageWidth;
}
refreshImageView();
forceToRelayout = false;
}
}
private void refreshImageView(){
for (int i = 0; i < imageItems.length; i++) {
ImageView childView = (ImageView)getChildAt(imageItems[i]);
childView.invalidate();
}
}
private int getIndexForItem(int item) {
int index = -1;
index = mIndex + item - 3;
while (index < 0) {
index = index + imageCount;
}
while (index > imageCount - 1) {
index = index - imageCount;
}
return index;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE)
&& (mTouchState != TOUCH_STATE_REST)) {
return true;
}
float xLoc = ev.getX();
switch(action){
case MotionEvent.ACTION_DOWN:
mMotionX = xLoc;
mTouchState = TOUCH_STATE_REST;
Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
int xDif = (int)Math.abs(mMotionX - xLoc);
if(xDif > mTouchSlop){ //當我們的水平距離滾動達到我們滾動的最小距離,開始攔截ViewGroup的事件給子控件分發
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onInterceptTouchEvent ACTION_CANCEL");
mTouchState = TOUCH_STATE_REST;
break;
default:
Log.e(TAG, "onInterceptTouchEvent DEFAULT");
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(scroller.isFinished()){ //scroller還沒有開始或者已經完成,以下代碼在手指滑動的時候纔開始執行
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
int action = event.getAction();
float x = event.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 記錄按下時的橫座標
mMotionX = x;
case MotionEvent.ACTION_MOVE:
int disX = (int)(mMotionX - x);
mMotionX = x;
scrollBy(disX, 0);
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) mVelocityTracker.getXVelocity();
if (judeScrollToNext(velocityX)) {
// 下一張圖
scrollToNext();
} else if (judeScrollToPrevious(velocityX)) {
//上一張圖
scrollToPrevious();
} else {
// 當前圖片
scrollBack();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mHandler.sendEmptyMessageDelayed(HANDLE_MSG, PHOTO_CHANGE_TIME);
return true;
}
}
return false;
}
private void scrollBack() {
if (scroller.isFinished()) {
beginScroll(getScrollX(), 0, -getScrollX(), 0,SCROLL_BACK);
}
}
private void scrollToPrevious() {
if(scroller.isFinished()){
setImageSwitchIndex(SCROLL_PREVIOUS);
int disX = -mImageWidth - getScrollX();
beginScroll(getScrollX(), 0, disX, 0,SCROLL_PREVIOUS);
}
}
private void scrollToNext() {
if (scroller.isFinished()) {
setImageSwitchIndex(SCROLL_NEXT);
int disX = mImageWidth - getScrollX();
beginScroll(getScrollX(), 0, disX, 0,SCROLL_NEXT);
}
}
/**
* 圖片開始滑動
*/
private void beginScroll(int startX, int startY, int dx, int dy,
final int action) {
int duration = (int) (700f / mImageWidth * Math.abs(dx));
scroller.startScroll(startX, startY, dx, dy, duration);
invalidate();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (action == SCROLL_NEXT || action == SCROLL_PREVIOUS) {
forceToRelayout = true;
requestLayout();
}
}
}, duration);
}
private void setImageSwitchIndex(int action) {
if(action == SCROLL_NEXT){
if(mIndex < imageCount){
mIndex++;
}else{
mIndex = 0;
}
}else if(action == SCROLL_PREVIOUS){
if(mIndex > 0){
mIndex--;
}else{
mIndex = imageCount -1;
}
}
}
/**
* 判斷時候滑向前一個
* @param velocityX
* @return
*/
private boolean judeScrollToPrevious(int velocityX) {
return velocityX > SNAP_VELOCITY || getScrollX() < -mImageWidth / 2;
}
/**
* 判斷時候滑向後一個
* @param velocityX
* @return
*/
private boolean judeScrollToNext(int velocityX) {
return velocityX < -SNAP_VELOCITY|| getScrollX() > mImageWidth / 2;
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
//刷新View 否則效果可能有誤差
postInvalidate();
}
}
}
從代碼分析我們知道,我們在判斷手指事件的時候,我們在ACTION_MOVE的時候,我們判斷當水平移動距離可判爲大於移動的最小距離,我們這個時候攔截VIewGroup往下分發的事件,事件這個時候會走ViewGroup的onTouchEvent來消費事件,我們把讀圖片的輪滑處理在onTouchEvent裏來處理。
這個Demo裏我們要注意的一點,我們來看我們的佈局文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zhanglei.imageswitcher.ImageSwitcher
android:id="@+id/image_switch_view"
android:layout_width="match_parent"
android:layout_height="200dp" >
<ImageView
android:id="@+id/image1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:clickable="true"
android:src="@drawable/item01"/>
<ImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:clickable="true"
android:src="@drawable/item02"/>
<ImageView
android:id="@+id/image3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:clickable="true"
android:src="@drawable/item03"/>
<ImageView
android:id="@+id/image4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:clickable="true"
android:src="@drawable/item04"/>
<ImageView
android:id="@+id/image5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:clickable="true"
android:src="@drawable/item05"/>
</com.zhanglei.imageswitcher.ImageSwitcher>
</RelativeLayout>
我們看到我們對ImageView設置了clickable = "true"的屬性,如果這裏我們去掉該屬性,我們就不能手動去滑動圖片了。
大家一定會好奇這是爲什麼?下面我們來根據前一篇的Android事件分發機制學習筆記 的事件分發分析來做出解答。我們通過分析,如果我們不給ImageView設置clickable="true",ImageView的onEventTouch嘗試消費時候會發現 if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) 進到判斷裏去,onEventTouch 返回值爲false,那ImageView的dispatchTouchEvent返回爲false,意思就是ImageView控件是默認不消費事件的。還記得我們在Android的事件分發中提到,當我們的一次事件從Activity開始分發到葉子控件,到葉子控件開始一層一層回溯嘗試消費事件,還記得我們上次說的事件“記憶”功能,當我們從Activity分發的事件一直回溯到Activity都沒有被消費掉,後面的事件就不會從根控件DecorView繼續往下分發。
下面我們要問,那我們不給ImageView 設置clickable = "true",有沒有辦法讓圖片可以滑動呢?答案當然有,辦法一,我們這裏用Button來代替ImageVIew;辦法二,我們ImageView沒有消費掉事件,我們的事件就會回溯到ViewGroup去嘗試消費,我們可以在VIewGroup的onTouchEvent去消費,通過修改onTouchEvent的返回值爲true,來達到消費的效果。那我們後面的動作也就會記住事件分發消費“迴路”,我們的後續事件也就能得到消費,我們方法二的辦法就是,在ViewGroup裏去返回onTouchEvent的返回值來消費,此處我們完全可以把事件攔截onInterceptTouchEvent註釋掉,同樣達到我們上面代碼的效果。以上是對圖片輪播的事件處理過程的主要講解,代碼裏還加入處理圖片自動輪播的代碼。
後面有時間,我會來嘗試分析更復雜的事件分發的過程,比如,ListView的基類AbsLIstView已經加入了自己的事件分發、攔截處理,我們怎麼對ListView做我們自己的事件分發攔截處理,歡迎大家來拍磚。最後附上圖片輪播例子的Demo.
轉載請註明出處:http://blog.csdn.net/johnnyz1234/article/details/43737455