English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Analisi dettagliata del framework SwipeMenuListView per Android

周末,特意把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.

Ti potrebbe interessare