編輯:關於Android編程
前一篇寫到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裡我們要注意的一點,我們來看我們的布局文件如下:
我們看到我們對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.
通過這篇文章,我想說明一下如何創建一個可搜索的“聯系人列表”Android應用程序。使用這個應用程序,用戶可以通過使用導航按鈕浏覽所有保存的聯系人和根據聯系人名稱搜索聯系
最近心血來潮,寫了一個自定義仿iPhone的開關。有需要的同學可以來下載啦。支持點擊自動滾動,速率可以自己根據需要修改。觸摸滾動,大小自定義,支持修改樣式。就不錄制動畫,
0、在認識HTTP前先認識URL 在我們認識HTTP之前,有必要先弄清楚URL的組成,例如: http://www.******.com/china/index.htm
(1)Android Studio菜單Build->Generate Signed APK(2)彈出窗口(3)創建密鑰庫及密鑰,創建後會自動選擇剛創建的密鑰庫和密鑰