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方法,看下就行?;旧显创a的注釋很清楚。
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));}}//創建imgprivate 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() {@Overridepublic boolean onDown(MotionEvent e) {isFling = false;return true;}@Override//velocityX這個參數是x軸方向的速率,向左是負的,向右是正的public boolean onFling(MotionEvent e1, MotionEvent e2,float velocityX, float velocityY) {// TODOif (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 ResourceTypemContentView.setId(CONTENT_VIEW_ID);}//noinspection ResourceTypemMenuView.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就是0Log.i("tag", "dis = " + dis);//這個值一直是0//DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1Log.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());// opensmoothOpenMenu();} else {// closesmoothCloseMenu();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 negativeif (Math.signum(dis) != mSwipeDirection) {//left=1;right =-1dis = 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的viewSwipeMenu menu = new SwipeMenu(mContext); //創建SwipeMenumenu.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 defaultprivate 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) {@Overridepublic void createMenu(SwipeMenu menu) {if (mMenuCreator != null) {mMenuCreator.create(menu);}}@Overridepublic void onItemClick(SwipeMenuView view, SwipeMenu menu,int index) {boolean flag = false;if (mOnMenuItemClickListener != null) {flag = mOnMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index);}//再次點擊list中的item關閉menuif (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的positionmTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//得到那個點擊的item對應的view,就是SwipeMenuLayoutView view = getChildAt(mTouchPosition - getFirstVisiblePosition());//只在空的時候賦值 以免每次觸摸都賦值,會有多個open狀態if (view instanceof SwipeMenuLayout) {//如果有打開了 就攔截.mTouchView是SwipeMenuLayout//如果兩次是一個mTouchView,更新mTouchView;如果不是一個view,就攔截返回trueif (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);}@Overridepublic 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在MOVELog.i("tag","Listview中的onTouchEvent ACTION_DOWN。是否點擊了另一個item");int oldPos = mTouchPosition; //這里設計不合理,onInterceptTouchEvent之后直接調用的這個事件,mTouchPosition是一樣的if(mOldTouchPosition == -1){//-1 is the original valuemOldTouchPosition = mTouchPosition;}mDownX = ev.getX();mDownY = ev.getY();mTouchState = TOUCH_STATE_NONE;mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//list中//這里改了,pldPos沒有用,改為mOldTouchPositionif (mTouchPosition == mOldTouchPosition && mTouchView != null&& mTouchView.isOpen()) {mTouchState = TOUCH_STATE_X; //x方向(橫著)滑開//調用SwipeMenuLayout的onSwipe()事件接口mTouchView.onSwipe(ev);Log.i("tag","Listview中的onTouchEvent ACTION_DOWN?;瑒恿嘶螯c擊了另一個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()) {//關閉swipeMenumTouchView.smoothCloseMenu();mTouchView = null;// return super.onTouchEvent(ev);// try to cancel the touch eventMotionEvent 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 。 所以要用位置判斷是否滑動的是一個viewif (!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事件后的Moveif (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: //關閉了menuLog.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()) {//關閉swipeMenumTouchView.smoothCloseMenu();//mTouchView = null;// return super.onTouchEvent(ev);// try to cancel the touch eventMotionEvent cancelEvent = MotionEvent.obtain(ev);cancelEvent.setAction(MotionEvent.ACTION_CANCEL);onTouchEvent(cancelEvent); //取消事件,時間結束//進行menu close的回掉if (mOnMenuStateChangeListener != null) {mOnMenuStateChangeListener.onMenuClose(oldPos);}return true;}在代碼中我已經修改了這個問題。目前已經在github上提交給原作者啦。
轉載請注明出處:http://www.cnblogs.com/jycboy/p/SwipeMenuListView.html
?
轉載于:https://www.cnblogs.com/jycboy/p/SwipeMenuListView.html
總結
以上是生活随笔為你收集整理的Android 滑动菜单框架--SwipeMenuListView框架完全解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDU 2874 Connections
- 下一篇: navicat premium 连接出现