English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
周末,特意把Android SwipeMenuListView(滑动菜单)的知识资料整理一番,以下是整理内容:
SwipeMenuListView(滑动菜单)
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: I commenti nel codice sono chiari
/** * Un LinearLayout orizzontale, è il layout padre di tutto lo SwipeMenu * Definisce i metodi per aggiungere Item e le proprietà degli 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; // // Collection di MenuItem List<SwipeMenuItem> items = menu.getMenuItems(); int id = 0; // Aggiungi View costruita tramite item al SwipeMenuView for (SwipeMenuItem item : items) { addItem(item, id++); } } /** * Converti MenuItem in un componente UI, un item è equivalente a un LinearLayout verticale, * SwipeMenuView è un LinearLayout orizzontale, */ private void addItem(SwipeMenuItem item, int id) { // Layout parameters 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 /** * utilizzare il mLayout trasmesso per determinare se è aperto * chiamare l'evento di click onItemClick */ public void onClick(View v) { if (onItemClickListener != null && mLayout.isOpen()) { onItemClickListener.onItemClick(this, mMenu, v.getId()); } } public OnSwipeItemClickListener getOnSwipeItemClickListener() { return onItemClickListener; } /** * impostare l'evento di click dell'item * @param onItemClickListener */ public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } public void setLayout(SwipeMenuLayout mLayout) { this.mLayout = mLayout; } /** * interfaccia di callback per l'evento di click */ public static interface OnSwipeItemClickListener { /** * nel click event chiamare onItemClick * @param view layout genitore * @param menu entità della classe menu * @param index id del menuItem */ void onItemClick(SwipeMenuView view, SwipeMenu menu, int index); } }
**SwipeMenuView** è la View visualizzata durante lo swipe, vedere il suo costruttore **SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView)**; eseguire l'iterazione sugli Items: menu.getMenuItems(); aggiungere l'item al **SwipeMenuView** utilizzando il metodo addItem.
Nel metodo addItem: ogni item è un LinearLayout.
2.2 SwipeMenuLayout:
Il codice di questa classe è un po' lungo, lo dividiamo in tre parti per osservarlo, e con un'occhiata dovresti capire tutto.
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; //Direction 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; //Set SwipeMenuLayout to SwipeMenuView for judgment whether to open 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 questa parametro è la velocità in direzione X, a sinistra è negativa, a destra è positiva 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); }
Dal metodo init sopra riportato si può vedere che SwipeMenuLayout è composto da due parti, ovvero il itemView dell'utente e il menuView. L'operazione di scorrimento con il dito è completata tramite SimpleOnGestureListener.
/** * Evento di scorrimento, utilizzato per l'interfaccia di chiamata esterna * Questo è un'API esposta all'esterno, e chi chiama questa API è SwipeMenuListView, quindi MotionEvent è l'evento MotionEvent di SwipeMenuListView * @param event * @return */ public boolean onSwipe(MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX(); // Registrare l'asse x del clic 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) { // Quando lo stato è aperto, dis è 0 Log.i("tag", "dis = " + dis); // Questo valore è sempre 0 // DIREZIONE_SINISTRA = 1 || DIREZIONE_DESTRUA = -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 //Come sopra, reimposta la disposizione del menuview, il disegno è molto chiaro 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()); } } /** * Aggiorna stato state = STATE_CLOSE; * Chiudi menu */ public void smoothCloseMenu() { state = STATE_CLOSE; if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mBaseX = -mContentView.getLeft(); //Scivola di mMenuView.getWidth() di distanza, nasconde perfettamente 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, è la distanza di spostamento dis, -(downX-moveX) //l'asse destro di mContentView è -540, mMenuView è 540, l'assoluto di entrambi è uguale, completamente corretto! Haha· } else { mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); } //Chiamare questo metodo fuori dal thread UI per ridisegnare la vista postInvalidate(); } .... }
I principali metodi sono onSwipe e swipe questi due metodi, la logica principale è: onSwipe è un'API esposta all'esterno per essere chiamata da fuori,
nel metodo di gestione degli eventi onTouch di SwipeMenuListView è stato chiamato onSwipe; mentre swipe significa scorrere mContentView e mMenuView di una distanza dis.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //l'ampiezza è espandibile illimitatamente, l'altezza è specificata 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) { //scorrimento a sinistra //rispetto al view padre, con base a sinistra e in alto, nascosto a destra mMenuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + mMenuView.getMeasuredWidth(), mContentView.getMeasuredHeight()); } else { // scorre a destra, nascosto a sinistra mMenuView.layout(-mMenuView.getMeasuredWidth(), 0, 0, mContentView.getMeasuredHeight()); } }
I metodi onMeasure e onLayout, che sono spesso sovrascritti nei View personalizzati, sono rispettivamente per misurare la dimensione del view e per posizionare il view all'interno del layout genitore. In onMeasure, la larghezza è impostata come UNSPECIFIED, il che permette all'elemento di espandersi illimitatamente. In onLayout, dopo che la dimensione del view è stata misurata, il view viene posizionato all'interno del layout genitore. Il codice mostra come il menuView viene nascosto a sinistra (o a destra) in base alla direzione dello swipe.
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; } ... /** * Aggiungi il menu visibile durante lo swipe * Ecco che ogni elemento è un SwipeMenuLayout */ public View getView(int position, View convertView, ViewGroup parent) { SwipeMenuLayout layout = null; if (convertView == null) { View contentView = mAdapter.getView(position, convertView, parent); // view dell'elemento SwipeMenu menu = new SwipeMenu(mContext); // crea SwipeMenu menu.setViewType(getItemViewType(position)); createMenu(menu); // Test, può essere ignorato per ora 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; } // Questo metodo viene sovrascritto al momento della creazione, qui è solo un test, può essere ignorato. public void createMenu(SwipeMenu menu) { // Codice di Test 。。。。。。 } /** * Metodo di ritorno di OnSwipeItemClickListener * Questo metodo viene sovrascritto quando viene creato questo oggetto. */ public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { if (onMenuItemClickListener != null) { onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index); } } 。。。。//omissi dettagli non importanti }
2.4 Classe principale: SwipeMenuListview,
Questo codice è molto lungo, serve pazienza per leggerlo.
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; //direzione public static final int DIRECTION_RIGHT = -1; private int mDirection = 1;//swipe da destra a sinistra per 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; //creazione del menuItem private SwipeMenuCreator mMenuCreator; //evento di clic del menuItem private OnMenuItemClickListener mOnMenuItemClickListener; private OnMenuStateChangeListener mOnMenuStateChangeListener; private Interpolator mCloseInterpolator; //tasso di variazione dell'animazione private Interpolator mOpenInterpolator; //----aggiunto da me--queste due righe sono state aggiunte da me //Se scendi di codice e esegui il demo, noterai che quando un item è già stato trascinato, trascinare un altro item, l'item aperto in precedenza non verrà chiuso. Puoi vedere come funziona in QQ, dove è chiuso, e qui ho fatto un leggero修改。 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) { // Ogni down intercettato imposta lo stato di tocco a TOUCH_STATE_NONE. Solo se si restituisce true si passerà a onTouchEvent, quindi è sufficiente scrivere qui 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: // l'evento DOWN di questa chiamata prevede che l'evento sia già intercettato, quindi le possibilità sono: 1. Il menu è già scivolato fuori, poi è stato cliccato l'area dell'item di sinistra // 2. Il menu è già scivolato fuori, è stato cliccato un altro item // 3. Durante lo scorrimento dell'item, prima DOWN poi MOVE Log.i("tag","Listview nel onTouch ACTION_DOWN. È stato cliccato un altro item"); int oldPos = mTouchPosition; // qui il design non è ragionevole, chiamata diretta di questo evento dopo onInterceptTouchEvent, mTouchPosition è uguale if(mOldTouchPosition == -1){//-1 è il valore originale mOldTouchPosition = mTouchPosition; } mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//list中 // qui è stato modificato, pldPos non è più utilizzato, sostituito con mOldTouchPosition if (mTouchPosition == mOldTouchPosition && mTouchView != null && mTouchView.isOpen()) {}} mTouchState = TOUCH_STATE_X; //direzione X (orizzontale) di scorrimento //chiamata all'interfaccia di evento onSwipe di SwipeMenuLayout mTouchView.onSwipe(ev); Log.i("tag","onTouchEvent ACTION_DOWN della Listview. È stato scorruto o cliccato su un altro item"); return true; } if(mOldTouchPosition != mTouchPosition){ //quando la posizione DOWN è diversa //shouldCloseMenu = true; mOldTouchPosition = mTouchPosition; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //È già stato aperto un menu, in questo caso se si clicca su un altro item //Questo metodo non viene mai eseguito! if (mTouchView != null && mTouchView.isOpen()) { // chiudi swipeMenu mTouchView.smoothCloseMenu(); mTouchView = null; // return super.onTouchEvent(ev); // tentativo di annullare l'evento di tocco MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); // evento annullato, tempo concluso // chiamata di ritorno per 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: //alcuni possono avere un header, quindi subtractire il header prima di determinare mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount(); //se lo swipe non è stato completamente eseguito, richiudilo, a questo punto mTouchView è già stato assegnato e non si può scorrere un altro view non swipabile //può causare mTouchView swip. Quindi usare il posizionamento per determinare se lo swipe è su un 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) { //se è nella direzione X if (mTouchView != null) { mTouchView.onSwipe(ev); //chiamata all'evento di scorrimento } getSelector().setState(new int[]{0}); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev);//Fine dell'evento return true; } 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: //Chiudi il menu Log.i("tag","Azione ACTION_UP dell'evento onTouchEvent"); if (mTouchState == TOUCH_STATE_X) { if (mTouchView != null) { Log.i("tag","Perché l'azione ACTION_UP dell'evento onTouchEvent non si è chiusa"); boolean isBeforeOpen = mTouchView.isOpen(); //Chiamata dell'evento di swipe 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) { //Esegui il ritorno chiamata di fine dello swipe 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(); } } } /** * Puoi guardare il codice sorgente, è una conversione di unità diverse in pixel px, qui è 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); } ... }
La logica più importante di questa classe riguarda il giudizio e la distribuzione degli eventi, quando intercettare gli eventi, e cosa fare con diversi eventi. Se non sei chiaro sulla distribuzione degli eventi, puoi cercare blog correlati su Internet o leggere i miei prossimi blog, dovrebbe essere nei prossimi giorni.
Ecco l'analisi della logica di distribuzione degli eventi di SwipeMenuListView: il nucleo è il trattamento degli eventi di click e di scorrimento degli item di SwipeMenuListView. Quando si scorre, SwipeMenuListView intercetta gli eventi e li gestisce da solo, ricordati che questa logica è chiaramente visibile nel codice. Ecco una diagramma di flusso che ho disegnato:
L'evento di tocco è una sequenza di eventi: ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. Comincia con ACTION_DOWN e termina con ACTION_UP.
Ecco il mio print della procedura: (aggiungi log nel codice)
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: ACTION_UP dell'evento onTouchEvent I/tag: Perché l'ACTION_UP dell'evento onTouchEvent non si chiude 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
Terza parte: i problemi esistenti
1. Se hai eseguito il framework in modalità debug, troverai un problema:
Quando un item di ListView è già stato scollato, supponiamo che sia item1; a questo punto, scorri un altro item, chiamiamolo item2;
In questo caso item1 non si chiuderà, ovviamente item2 non si aprirà.
Questo effetto non è buono, ho già modificato questo problema nel codice. Il codice specifico è stato evidenziato.
2. Ecco questo pezzo di codice: nel ACTION_DOWN di onTouchEvent(MotionEvent ev) di SwipeMenuListView, questo codice non verrà mai eseguito perché onTouchEvent e onInterceptTouchEvent corrispondono a un MotionEvent.
mTouchPosition == oldPos è sempre uguale.
//Questo metodo non verrà mai eseguito! Il desiderio dell'autore è chiudere il menu quando mTouchPosition != oldPos, ma secondo questo codice questi due valori sono sempre uguali, // ovviamente, dato che corrisponde a un MotionEvent, sono uguali if (mTouchView != null && mTouchView.isOpen()) { // chiudi swipeMenu mTouchView.smoothCloseMenu(); // mTouchView = null; // return super.onTouchEvent(ev); // tentativo di annullare l'evento di tocco MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); // evento annullato, tempo concluso // chiamata di ritorno per menu close if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; }
Ho modificato questo problema nel codice. Al momento l'ho già inviato all'autore originale su github.
Grazie per la lettura, spero di essere stato d'aiuto, grazie per il supporto a questo sito!
Dichiarazione: il contenuto di questo articolo è stato raccolto da Internet, il copyright è della proprietà del rispettivo autore, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene il diritto di proprietà, non è stato sottoposto a elaborazione editoriale umana e non assume responsabilità legali correlate. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, questo sito eliminerà immediatamente il contenuto sospetto di violazione del copyright.