編輯:關於Android編程
周末 特地把Android SwipeMenuListView(滑動菜單)的知識資料整理一番,以下是整理內容:
SwipeMenuListView(滑動菜單)
A swipe menu for ListView.--一個非常好的滑動菜單開源項目。
Demo
一、簡介
看了挺長時間的自定義View和事件分發,想找一個項目練習下。。正好印證自己所學。
在github上找到了這個項目:SwipeMenuListView這的真不錯,對事件分發和自定義View都很有啟發性,雖然還有點小瑕疵,後面說明。想了解滑動菜單怎麼實現的同學,這篇文章絕對對你有幫助,從宏觀微觀角度詳細分析了每個文件。
項目地址:https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 版本:b00e0fe 它的使用很簡單只需要三步,在github上就可以看懂就不占用篇幅啦,本文只分析原理。另外如果你看代碼感覺和我不一樣,看著困難的話,可以看我加了注釋的:http://download.csdn.net/detail/jycboy/9667699
先看兩個圖:有一個大體的了解
這是框架中所有的類。
1.下面的圖是視圖層次:
上面的圖中:SwipeMenuLayout是ListView中item的布局,分左右兩部分,一部分是正常顯示的contentView,一部分是滑出來的menuView;滑出來的SwipeMenuView繼承自LinearLayout,添加view時,就是橫向添加,可以橫向添加多個。
2.下面的圖是類圖結構:
上面是類之間的調用關系,類旁邊注明了類的主要作用。
二、源碼分析
SwipeMenu、SwipeMenuItem是實體類,定義了屬性和setter、getter方法,看下就行。基本上源碼的注釋很清楚。
2.1 SwipeMenuView: 代碼中注釋的很清楚
/** * 橫向的LinearLayout,就是整個swipemenu的父布局 * 主要定義了添加Item的方法及Item的屬性設置 * @author baoyz * @date 2014-8-23 * */ public class SwipeMenuView extends LinearLayout implements OnClickListener { private SwipeMenuListView mListView; private SwipeMenuLayout mLayout; private SwipeMenu mMenu; private OnSwipeItemClickListener onItemClickListener; private int position; public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } public SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView) { super(menu.getContext()); mListView = listView; mMenu = menu; // // MenuItem的list集合 List<SwipeMenuItem> items = menu.getMenuItems(); int id = 0; //通過item構造出View添加到SwipeMenuView中 for (SwipeMenuItem item : items) { addItem(item, id++); } } /** * 將 MenuItem 轉換成 UI控件,一個item就相當於一個垂直的LinearLayout, * SwipeMenuView就是橫向的LinearLayout, */ private void addItem(SwipeMenuItem item, int id) { //布局參數 LayoutParams params = new LayoutParams(item.getWidth(), LayoutParams.MATCH_PARENT); LinearLayout parent = new LinearLayout(getContext()); //設置menuitem的id,用於後邊的點擊事件區分item用的 parent.setId(id); parent.setGravity(Gravity.CENTER); parent.setOrientation(LinearLayout.VERTICAL); parent.setLayoutParams(params); parent.setBackgroundDrawable(item.getBackground()); //設置監聽器 parent.setOnClickListener(this); addView(parent); //加入到SwipeMenuView中,橫向的 if (item.getIcon() != null) { parent.addView(createIcon(item)); } if (!TextUtils.isEmpty(item.getTitle())) { parent.addView(createTitle(item)); } } //創建img private ImageView createIcon(SwipeMenuItem item) { ImageView iv = new ImageView(getContext()); iv.setImageDrawable(item.getIcon()); return iv; } /*根據參數創建title */ private TextView createTitle(SwipeMenuItem item) { TextView tv = new TextView(getContext()); tv.setText(item.getTitle()); tv.setGravity(Gravity.CENTER); tv.setTextSize(item.getTitleSize()); tv.setTextColor(item.getTitleColor()); return tv; } @Override /** * 用傳來的mLayout判斷是否打開 * 調用onItemClick點擊事件 */ public void onClick(View v) { if (onItemClickListener != null && mLayout.isOpen()) { onItemClickListener.onItemClick(this, mMenu, v.getId()); } } public OnSwipeItemClickListener getOnSwipeItemClickListener() { return onItemClickListener; } /** * 設置item的點擊事件 * @param onItemClickListener */ public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } public void setLayout(SwipeMenuLayout mLayout) { this.mLayout = mLayout; } /** * 點擊事件的回調接口 */ public static interface OnSwipeItemClickListener { /** * onClick點擊事件中調用onItemClick * @param view 父布局 * @param menu menu實體類 * @param index menuItem的id */ void onItemClick(SwipeMenuView view, SwipeMenu menu, int index); } }
**SwipeMenuView就是滑動時顯示的View,看他的構造函數SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView);遍歷Items:menu.getMenuItems();調用addItem方法向SwipeMenuView中添加item。
在addItem方法中:每一個item都是一個LinearLayout。
2.2 SwipeMenuLayout:
這個類代碼有點長,我們分成三部分看,只粘貼核心代碼,剩下的看一下應該就懂啦。
public class SwipeMenuLayout extends FrameLayout { private static final int CONTENT_VIEW_ID = 1; private static final int MENU_VIEW_ID = 2; private static final int STATE_CLOSE = 0; private static final int STATE_OPEN = 1; //方向 private int mSwipeDirection; private View mContentView; private SwipeMenuView mMenuView; 。。。。。 public SwipeMenuLayout(View contentView, SwipeMenuView menuView) { this(contentView, menuView, null, null); } public SwipeMenuLayout(View contentView, SwipeMenuView menuView, Interpolator closeInterpolator, Interpolator openInterpolator) { super(contentView.getContext()); mCloseInterpolator = closeInterpolator; mOpenInterpolator = openInterpolator; mContentView = contentView; mMenuView = menuView; //將SwipeMenuLayout設置給SwipeMenuView,用於判斷是否打開 mMenuView.setLayout(this); init(); } private void init() { setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mGestureListener = new SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { isFling = false; return true; } @Override //velocityX這個參數是x軸方向的速率,向左是負的,向右是正的 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // TODO if (Math.abs(e1.getX() - e2.getX()) > MIN_FLING && velocityX < MAX_VELOCITYX) { isFling = true; } Log.i("tag","isFling="+isFling+" e1.getX()="+e1.getX()+" e2.getX()="+e2.getX()+ " velocityX="+velocityX+" MAX_VELOCITYX="+MAX_VELOCITYX); // Log.i("byz", MAX_VELOCITYX + ", velocityX = " + velocityX); return super.onFling(e1, e2, velocityX, velocityY); } }; mGestureDetector = new GestureDetectorCompat(getContext(), mGestureListener); 。。。。 LayoutParams contentParams = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); mContentView.setLayoutParams(contentParams); if (mContentView.getId() < 1) { //noinspection ResourceType mContentView.setId(CONTENT_VIEW_ID); } //noinspection ResourceType mMenuView.setId(MENU_VIEW_ID); mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); addView(mContentView); addView(mMenuView); }
從上邊的init方法中可以看出SwipeMenuLayout由兩部分組成,分別是用戶的 item View 和 menu View 。手指的時候滑動的操作是通過 SimpleOnGestureListener
來完成的。
/** * 滑動事件,用於外邊調用的接口 * 這是一個對外暴露的API,而調用這個API的是SwipeMenuListView,那麼MotionEvent是SwipeMenuListView的MotionEvent * @param event * @return */ public boolean onSwipe(MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX();//記下點擊的x坐標 isFling = false; break; case MotionEvent.ACTION_MOVE: // Log.i("byz", "downX = " + mDownX + ", moveX = " + event.getX()); int dis = (int) (mDownX - event.getX()); if (state == STATE_OPEN) {//當狀態是open時,dis就是0 Log.i("tag", "dis = " + dis);//這個值一直是0 //DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1 dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1 Log.i("tag", "dis = " + dis + ", mSwipeDirection = " + mSwipeDirection); } Log.i("tag", "ACTION_MOVE downX = " + mDownX + ", moveX = " + event.getX()+", dis="+dis); swipe(dis); break; case MotionEvent.ACTION_UP: //判斷滑動距離,是打開還是關閉 //在這裡,如果已經有一個item打開了,此時滑動另外的一個item,還是執行這個方法,怎麼改進? if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) && Math.signum(mDownX - event.getX()) == mSwipeDirection) { Log.i("tag", "ACTION_UP downX = " + mDownX + ", moveX = " + event.getX()); // open smoothOpenMenu(); } else { // close smoothCloseMenu(); return false; } break; } return true; } public boolean isOpen() { return state == STATE_OPEN; } /** * 滑動dis的距離,把mContentView和mMenuView都滑動dis距離 * @param dis */ private void swipe(int dis) { if(!mSwipEnable){ return ; } //left is positive;right is negative if (Math.signum(dis) != mSwipeDirection) {//left=1;right =-1 dis = 0; //不滑動 } else if (Math.abs(dis) > mMenuView.getWidth()) {//大於它的寬度,dis就是mMenuView.getWidth() dis = mMenuView.getWidth()*mSwipeDirection; } //重新設置布局,不斷左移(或者右移), mContentView.layout(-dis, mContentView.getTop(), mContentView.getWidth() -dis, getMeasuredHeight()); if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1 //同上重新設置menuview的布局,畫圖很清晰 mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(), mContentView.getWidth() + mMenuView.getWidth() - dis, mMenuView.getBottom()); } else { mMenuView.layout(-mMenuView.getWidth() - dis, mMenuView.getTop(), - dis, mMenuView.getBottom()); } } /** * 更新狀態state = STATE_CLOSE; * 關閉menu */ public void smoothCloseMenu() { state = STATE_CLOSE; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mBaseX = -mContentView.getLeft(); //滑動mMenuView.getWidth()的距離,正好隱藏掉 mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350); } else { mBaseX = mMenuView.getRight(); mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350); } postInvalidate(); } public void smoothOpenMenu() { if(!mSwipEnable){ return ; } state = STATE_OPEN; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); Log.i("tag","mContentView.getLeft()="+mContentView.getLeft()+", mMenuView="+mMenuView.getWidth());//-451,就是移動的距離dis,-(downX-moveX) //mContentView.getLeft()=-540, mMenuView=540 ,這倆的絕對值是相等的,完全正確!哈哈· } else { mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); } //在非ui thread中調用這個方法,使視圖重繪 postInvalidate(); } 。。。 }
上面主要的方法是onSwipe和swipe這兩個方法,主要邏輯是:onSwipe是暴漏給外面調用的API,
在SwipeMenuListView的onTouchEvent事件處理方法中調用了onSwipe;而swipe就是把mContentView和mMenuView都滑動dis距離。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //寬度是無限擴展的,高度是指定的 mMenuView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY)); } protected void onLayout(boolean changed, int l, int t, int r, int b) { mContentView.layout(0, 0, getMeasuredWidth(), mContentView.getMeasuredHeight()); if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//左滑 //相對於父view,以左邊和上邊為基准,隱藏在右邊 mMenuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + mMenuView.getMeasuredWidth(), mContentView.getMeasuredHeight()); } else { //右滑,隱藏在左邊 mMenuView.layout(-mMenuView.getMeasuredWidth(), 0, 0, mContentView.getMeasuredHeight()); } }
上面的onMeasure、onLayout方法就是自定義View中經常重寫的方法,在onMeasure是測量view的大小,這裡把寬度類型設置為UNSPECIFIED,可以無限擴展。 onLayout是在view的大小測量之後,把view放到父布局的什麼位置,代碼裡可以看出根據滑動方向吧menuView隱藏在左邊(或右邊)。
2.3 SwipeMenuAdapter
public class SwipeMenuAdapter implements WrapperListAdapter, OnSwipeItemClickListener { private ListAdapter mAdapter; private Context mContext; private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener; public SwipeMenuAdapter(Context context, ListAdapter adapter) { mAdapter = adapter; mContext = context; } 。。。。 /** * 添加滑動時的顯示的菜單 * 在這裡可以看出每一個Item都是一個SwipeMenuLayout */ public View getView(int position, View convertView, ViewGroup parent) { SwipeMenuLayout layout = null; if (convertView == null) { View contentView = mAdapter.getView(position, convertView, parent);//item的view SwipeMenu menu = new SwipeMenu(mContext); //創建SwipeMenu menu.setViewType(getItemViewType(position)); createMenu(menu); //測試的,可以先不管 SwipeMenuView menuView = new SwipeMenuView(menu, (SwipeMenuListView) parent); menuView.setOnSwipeItemClickListener(this); SwipeMenuListView listView = (SwipeMenuListView) parent; layout = new SwipeMenuLayout(contentView, menuView, listView.getCloseInterpolator(), listView.getOpenInterpolator()); layout.setPosition(position); } else { layout = (SwipeMenuLayout) convertView; layout.closeMenu(); layout.setPosition(position); View view = mAdapter.getView(position, layout.getContentView(), parent); } if (mAdapter instanceof BaseSwipListAdapter) { boolean swipEnable = (((BaseSwipListAdapter) mAdapter).getSwipEnableByPosition(position)); layout.setSwipEnable(swipEnable); } return layout; } //這個方法在創建時,重寫啦,在這裡是測試的,可以不管。 public void createMenu(SwipeMenu menu) { // Test Code 。。。。。。 } /** * OnSwipeItemClickListener的回掉方法 * 這個方法在該類創建時,重寫啦。 */ public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { if (onMenuItemClickListener != null) { onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index); } } 。。。。//省略了不重要的 }
2.4 核心類:SwipeMenuListview,
這個代碼很長,看的時候需要耐心。
public class SwipeMenuListView extends ListView { private static final int TOUCH_STATE_NONE = 0; private static final int TOUCH_STATE_X = 1; private static final int TOUCH_STATE_Y = 2; public static final int DIRECTION_LEFT = 1; //方向 public static final int DIRECTION_RIGHT = -1; private int mDirection = 1;//swipe from right to left by default private int MAX_Y = 5; private int MAX_X = 3; private float mDownX; private float mDownY; private int mTouchState; private int mTouchPosition; private SwipeMenuLayout mTouchView; private OnSwipeListener mOnSwipeListener; //創建menuItem的 private SwipeMenuCreator mMenuCreator; //menuItem的item點擊事件 private OnMenuItemClickListener mOnMenuItemClickListener; private OnMenuStateChangeListener mOnMenuStateChangeListener; private Interpolator mCloseInterpolator; //動畫變化率 private Interpolator mOpenInterpolator; //----added in myself--下面這兩行是我自己加的, //你如果下下來代碼demo運行下你會發現,當一個item已經滑開時,滑動另外的item,此時原來打開的item沒有關閉,可以看下QQ的側滑,它是關閉的,我這裡就稍微修改了下。 private int mOldTouchPosition = -1; private boolean shouldCloseMenu; //-------- public SwipeMenuListView(Context context) { super(context); init(); } public SwipeMenuListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public SwipeMenuListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } //初始化變量 private void init() { MAX_X = dp2px(MAX_X); MAX_Y = dp2px(MAX_Y); mTouchState = TOUCH_STATE_NONE; } @Override /** * 對參數adapter進行了一次包裝,包裝成SwipeMenuAdapter */ public void setAdapter(ListAdapter adapter) { super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) { @Override public void createMenu(SwipeMenu menu) { if (mMenuCreator != null) { mMenuCreator.create(menu); } } @Override public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { boolean flag = false; if (mOnMenuItemClickListener != null) { flag = mOnMenuItemClickListener.onMenuItemClick( view.getPosition(), menu, index); } //再次點擊list中的item關閉menu if (mTouchView != null && !flag) { mTouchView.smoothCloseMenu(); } } }); } 。。。。。 @Override //攔截事件,判斷事件是點擊事件還是滑動事件 public boolean onInterceptTouchEvent(MotionEvent ev) { //在攔截處處理,在滑動設置了點擊事件的地方也能swip,點擊時又不能影響原來的點擊事件 int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = ev.getX(); mDownY = ev.getY(); boolean handled = super.onInterceptTouchEvent(ev); mTouchState = TOUCH_STATE_NONE; //每次Down都把狀態變為無狀態 //返回item的position mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); //得到那個點擊的item對應的view,就是SwipeMenuLayout View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //只在空的時候賦值 以免每次觸摸都賦值,會有多個open狀態 if (view instanceof SwipeMenuLayout) { //如果有打開了 就攔截.mTouchView是SwipeMenuLayout //如果兩次是一個mTouchView,更新mTouchView;如果不是一個view,就攔截返回true if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) { Log.i("tag","Listview中的onInterceptTouchEvent ACTION_DOWN。"); return true; } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection);//默認是left=1 } //如果摸在另外一個view,攔截此事件 if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) { handled = true; } if (mTouchView != null) { mTouchView.onSwipe(ev); } return handled; case MotionEvent.ACTION_MOVE: //MOVE時攔截事件,在onTouch中進行處理 float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) { //每次攔截的down都把觸摸狀態設置成了TOUCH_STATE_NONE 只有返回true才會走onTouchEvent 所以寫在這裡就夠了 if (mTouchState == TOUCH_STATE_NONE) { if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } return true; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null) return super.onTouchEvent(ev); int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //這個DOWN事件的前提是已經攔截事件啦,所以可能的情況時:1.該menu已經滑出來,再點擊左邊的item區域 //2.menu已經滑出來,點擊了其他的item //3.滑動item時,先DOWN在MOVE Log.i("tag","Listview中的onTouchEvent ACTION_DOWN。是否點擊了另一個item"); int oldPos = mTouchPosition; //這裡設計不合理,onInterceptTouchEvent之後直接調用的這個事件,mTouchPosition是一樣的 if(mOldTouchPosition == -1){//-1 is the original value mOldTouchPosition = mTouchPosition; } mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//list中 //這裡改了,pldPos沒有用,改為mOldTouchPosition if (mTouchPosition == mOldTouchPosition && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; //x方向(橫著)滑開 //調用SwipeMenuLayout的onSwipe()事件接口 mTouchView.onSwipe(ev); Log.i("tag","Listview中的onTouchEvent ACTION_DOWN。滑動了或點擊了另一個item"); return true; } if(mOldTouchPosition != mTouchPosition){ //when the DOWN position is different //shouldCloseMenu = true; mOldTouchPosition = mTouchPosition; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //已經有一個menu滑開了,此時如果點擊了另一個item //這個方法永遠執行不到! if (mTouchView != null && mTouchView.isOpen()) { //關閉swipeMenu mTouchView.smoothCloseMenu(); mTouchView = null; // return super.onTouchEvent(ev); // try to cancel the touch event MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //取消事件,時間結束 //進行menu close的回掉 if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; } if (view instanceof SwipeMenuLayout) { mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); } if (mTouchView != null) { mTouchView.onSwipe(ev); } break; case MotionEvent.ACTION_MOVE: //有些可能有header,要減去header再判斷 mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount(); //如果滑動了一下沒完全展現,就收回去,這時候mTouchView已經賦值,再滑動另外一個不可以swip的view //會導致mTouchView swip 。 所以要用位置判斷是否滑動的是一個view if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) { break; } float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (mTouchState == TOUCH_STATE_X) { //X方向的話 if (mTouchView != null) { mTouchView.onSwipe(ev); //調用滑動事件 } getSelector().setState(new int[]{0}); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev);//事件結束 return true; } else if (mTouchState == TOUCH_STATE_NONE) {//DOWN事件後的Move if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP: //關閉了menu Log.i("tag","onTouchEvent事件的ACTION_UP"); if (mTouchState == TOUCH_STATE_X) { if (mTouchView != null) { Log.i("tag","onTouchEvent事件的ACTION_UP 為什麼沒有關閉"); boolean isBeforeOpen = mTouchView.isOpen(); //調用滑動事件 mTouchView.onSwipe(ev); boolean isAfterOpen = mTouchView.isOpen(); if (isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener != null) { if (isAfterOpen) { mOnMenuStateChangeListener.onMenuOpen(mTouchPosition); } else { mOnMenuStateChangeListener.onMenuClose(mTouchPosition); } } if (!isAfterOpen) { mTouchPosition = -1; mTouchView = null; } } if (mOnSwipeListener != null) { //進行滑動結束的回掉 mOnSwipeListener.onSwipeEnd(mTouchPosition); } ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } break; } return super.onTouchEvent(ev); } public void smoothOpenMenu(int position) { if (position >= getFirstVisiblePosition() && position <= getLastVisiblePosition()) { View view = getChildAt(position - getFirstVisiblePosition()); if (view instanceof SwipeMenuLayout) { mTouchPosition = position; if (mTouchView != null && mTouchView.isOpen()) { mTouchView.smoothCloseMenu(); } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); mTouchView.smoothOpenMenu(); } } } /** * 可以進去看源代碼,就是將不同的單位統一轉換成像素px,這裡是dp->px * @param dp * @return */ private int dp2px(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); } public static interface OnMenuItemClickListener { boolean onMenuItemClick(int position, SwipeMenu menu, int index); } public static interface OnSwipeListener { void onSwipeStart(int position); void onSwipeEnd(int position); } public static interface OnMenuStateChangeListener { void onMenuOpen(int position); void onMenuClose(int position); } 。。。。 }
這個類中最重要的邏輯就是關於事件的判斷和分發,什麼時候攔截事件,不同的事件對應什麼操作。如果對事件分發不清楚的同學,可以在網上找找相關的博客,也可以看我的後續博客,應該這兩天的事。
在這裡分析SwipeMenuListView的事件分發邏輯:核心就是SwipeMenuListView中item的點擊事件和滑動事件的處理。當滑動時SwipeMenuListView攔截事件,自己處理,記住這個邏輯看代碼就一目了然了。下面是我畫的一個事件分發流程圖:
觸摸事件是一個事件序列:ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. 以ACTION_DOWN開始,以ACTION_UP結束。
下邊是我的一個打印的流程:(自己在代碼中加log)
I/tag: Listview中的onInterceptTouchEvent ACTION_DOWN。view=class com.baoyz.swipemenulistview.SwipeMenuLayout I/tag: onInterceptTouchEvent ACTION_DOWN handled=false I/tag: SwipeMenuLayout onTouchEvent I/tag: Listview中的onTouchEvent ACTION_DOWN。是否點擊了另一個item I/tag: oldPos=1 mTouchPosition=1 I/tag: ACTION_MOVE downX = 987, moveX = 906.69666, dis=80 I/tag: ACTION_MOVE downX = 987, moveX = 855.5785, dis=131 I/tag: ACTION_MOVE downX = 987, moveX = 797.6258, dis=189 I/tag: ACTION_MOVE downX = 987, moveX = 735.9639, dis=251 I/tag: ACTION_MOVE downX = 987, moveX = 666.5104, dis=320 I/tag: ACTION_MOVE downX = 987, moveX = 589.0626, dis=397 I/tag: ACTION_MOVE downX = 987, moveX = 509.15567, dis=477 I/tag: ACTION_MOVE downX = 987, moveX = 431.7224, dis=555 I/tag: ACTION_MOVE downX = 987, moveX = 361.2613, dis=625 I/tag: ACTION_MOVE downX = 987, moveX = 319.70398, dis=667 I/tag: onTouchEvent事件的ACTION_UP I/tag: onTouchEvent事件的ACTION_UP 為什麼沒有關閉 I/tag: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500 I/tag: ACTION_UP downX = 987, moveX = 319.70398 I/tag: mContentView.getLeft()=-540, mMenuView=540
三、存在的問題
1.如果你下下來框架運行了,你會發現一個問題:
當ListView的一個item已經滑開,假設為item1;此時滑動另外一個的item,叫它item2;
這種情況下item1不會關閉,item2當然也不會打開。
這種效果並不好,我在代碼中已經修改了這個問題。具體代碼,我已經標明。
2.就是下面的這段代碼:在SwipeMenuListView的onTouchEvent(MotionEvent ev)的ACTION_DOWN中,這段代碼永遠不會執行到,因為onTouchEvent和onInterceptTouchEvent對應的一個MotionEvent。
mTouchPosition ==oldPos永遠相等。
//這個方法永遠執行不到!作者的願意是當mTouchPosition != oldPos時CloseMenu,但是按照這個代碼這兩個值是永遠相等的, //因為對應的是一個MotionEvent當然就相等啦 if (mTouchView != null && mTouchView.isOpen()) { //關閉swipeMenu mTouchView.smoothCloseMenu(); //mTouchView = null; // return super.onTouchEvent(ev); // try to cancel the touch event MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); //取消事件,時間結束 //進行menu close的回掉 if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; }
在代碼中我已經修改了這個問題。目前已經在github上提交給原作者啦。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
前言在自定義ViewGroup中,有時候需要實現觸摸事件攔截,比如ListView下拉刷新就是典型的觸摸事件攔截的例子。觸摸事件攔截就是在觸摸事件被parent view
在開發的時候遇到一個問題,就是一觸摸對話框邊緣外部,對話框會自動消失。這個問題很糾結啊,查找了一下發現從Android 4.0開始,AlertDialog有了
一. 三級緩存簡介如上圖所示,目前App中UI界面經常會涉及到圖片,特別是像“今日關注”新聞這類app中,圖片運用的幾率十分頻繁。當手機上需要顯示
學習目的: 1、掌握在Android中如何建立RadioGroup和RadioButton 2、掌握RadioGroup的常用屬性 3、理解RadioButton和Che