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

Detailed analysis of Android SwipeMenuListView framework

On weekends, I specially organized the knowledge materials for Android SwipeMenuListView (Sliding Menu), as follows is the organized content:

SwipeMenuListView (Sliding Menu)

A swipe menu for ListView.--A very good open-source sliding menu project.

Demo

 1. Introduction

After studying custom View and event dispatching for a long time, I wanted to find a project to practice and confirm what I have learned.

I found this project on github: SwipeMenuListView, which is really great, very inspiring for event dispatching and custom View, although there are still some minor flaws, which will be explained later. For those who want to understand how to implement a sliding menu, this article is definitely helpful, analyzing each file in detail from both macro and micro perspectives.

Project address: https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 Version: b00e0fe Its use is very simple, just three steps, you can understand it on github without taking up space, this article only analyzes the principle. Additionally, if you find the code different from mine and difficult to understand, you can check out the one with comments: http://download.csdn.net/detail/jycboy/9667699

First take a look at the two diagrams: get a general understanding

 These are all the classes in the framework.

1.The following diagram is the view hierarchy:

In the above diagram: SwipeMenuLayout is the layout for the item in ListView, divided into two parts, one is the normally displayed contentView, and the other is the sliding out menuView; the sliding out SwipeMenuView inherits from LinearLayout, and when adding views, it is added horizontally, allowing for the addition of multiple views horizontally.

2.The following diagram is the structure of the class diagram:

The above diagram shows the calling relationship between classes, with the main functions of each class noted beside them.

Two, source code analysis

SwipeMenu​, SwipeMenuItem is an entity class, defining properties and setter/getter methods, take a look. The comments in the source code are basically very clear.

2.1 SwipeMenuView8203;: The comments in the code are very clear

/**
 * A horizontal LinearLayout, which is the parent layout of the entire swipemenu
 * Mainly defines the method of adding items and the property settings of items
 * @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; //
    // List of MenuItems
    List<SwipeMenuItem> items = menu.getMenuItems();
    int id = 0;
    //Add View constructed by item to SwipeMenuView
    for (SwipeMenuItem item : items) {
      addItem(item, id++);
    }
  }
  /**
   * Converts MenuItem to UI controls, one item is equivalent to a vertical LinearLayout,
   * SwipeMenuView is a horizontal LinearLayout,
   */
  private void addItem(SwipeMenuItem item, int id) {
    //Layout parameters
    LayoutParams params = new LayoutParams(item.getWidth(),
        LayoutParams.MATCH_PARENT);
    LinearLayout parent = new LinearLayout(getContext());
    //Set menuitem's id, used for distinguishing items in subsequent click events
    parent.setId(id);
    parent.setGravity(Gravity.CENTER);
    parent.setOrientation(LinearLayout.VERTICAL);
    parent.setLayoutParams(params);
    parent.setBackgroundDrawable(item.getBackground());
    //Set listener
    parent.setOnClickListener(this);
    addView(parent); //Add to SwipeMenuView, horizontal
    if (item.getIcon() != null) {
      parent.addView(createIcon(item));
    }
    if (!TextUtils.isEmpty(item.getTitle())) {
      parent.addView(createTitle(item));
    }
  }
  //Create img
  private ImageView createIcon(SwipeMenuItem item) {
    ImageView iv = new ImageView(getContext());
    iv.setImageDrawable(item.getIcon());
    return iv;
  }
  /*Create title based on parameters
   */
  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
  /**
   * Use the transmitted mLayout to determine whether it is open
   * Call the onItemClick click event
   */
  public void onClick(View v) {
    if (onItemClickListener != null && mLayout.isOpen()) {
      onItemClickListener.onItemClick(this, mMenu, v.getId());
    }
  }
  public OnSwipeItemClickListener getOnSwipeItemClickListener() {
    return onItemClickListener;
  }
  /**
   * Set the click event of the item
   * @param onItemClickListener
   */
  public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
  }
  public void setLayout(SwipeMenuLayout mLayout) {
    this.mLayout = mLayout;
  }
  /**
   * The callback interface for click events
   */
  public static interface OnSwipeItemClickListener {
    /**
     * Call onItemClick in the onClick event
     * @param view The parent layout
     * @param menu The menu entity class
     * @param index The id of menuItem
     */
    void onItemClick(SwipeMenuView view, SwipeMenu menu, int index);
  }
}

**SwipeMenuView8203; It is the View displayed when sliding, see its constructor SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView)8203; Traverse Items: menu.getMenuItems(); call the addItem method to8203; Add item to SwipeMenuView.

In the addItem method: each item is a LinearLayout8203;.

2.2 SwipeMenuLayout​ :

The code of this class is a bit long, let's divide it into three parts to look at it, just paste the core code, and the rest should be understandable.

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 this parameter is the rate of speed in the x-axis, negative for left and positive for right
      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);
  }

 From the above init method, it can be seen that SwipeMenuLayout consists of two parts, namely the user's item View and menu View. The sliding operation when the finger slides is completed by SimpleOnGestureListener.

/**
   * Swiping event, used for external interfaces
   * This is an exposed API to the outside, and the caller of this API is SwipeMenuListView, so MotionEvent is the MotionEvent of SwipeMenuListView
   * @param event
   * @return
   */
  public boolean onSwipe(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      mDownX = (int) event.getX();//Record the clicked x coordinate
      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) {//When the state is open, dis is 0
        Log.i("tag", "dis = ") + dis);//This value is always 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:
      //Determine the sliding distance to open or close
      //How can we improve the situation where, if an item is already open, sliding another item still executes this method?
      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;
  }
  /**
   * Slide a distance of dis, sliding mContentView and mMenuView by dis distance
   * @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; //Do not slide
    } else if (Math.abs(dis) > mMenuView.getWidth()) {//Greater than its width, dis is mMenuView.getWidth()
      dis = mMenuView.getWidth()*mSwipeDirection;
    }
    //Reset the layout, continuously move left (or right),
    mContentView.layout(-dis, mContentView.getTop(),
        mContentView.getWidth() -dis, getMeasuredHeight());
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1
      //As above, reset the layout of menuview, the drawing is very clear
      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());
    }
  }
  /**
   * Update the state state = STATE_CLOSE;
   * Close the menu
   */
  public void smoothCloseMenu() {
    state = STATE_CLOSE;
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
      mBaseX = -mContentView.getLeft();
      //Slide the distance of mMenuView.getWidth() to hide it perfectly;
      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, which is the distance moved,-downX-moveX)
      //mContentView.getLeft()=-540, mMenuView=540, the absolute values of these two are equal, completely correct! Haha·
    } else {
      mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);
    }
    //Call this method on a non-ui thread to redraw the view
    postInvalidate();
  }
  ....
}

The main methods are onSwipe and swipe, and the main logic is that onSwipe is an API exposed to the outside

In the onTouchEvent handling method of SwipeMenuListView, onSwipe is called; while swipe slides mContentView and mMenuView by a distance of dis8203;.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //The width is infinitely expandable, and the height is specified
    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) {//Swipe to the left
      //Hidden on the right relative to the parent view, with the left and top as the reference points
      mMenuView.layout(getMeasuredWidth(), 0,
          getMeasuredWidth() + mMenuView.getMeasuredWidth(),
          mContentView.getMeasuredHeight());
    } else {  //Swipe to the right, hidden on the left
      mMenuView.layout(-mMenuView.getMeasuredWidth(), 0,
          0, mContentView.getMeasuredHeight());
    }
  }

 The above onMeasure, onLayout methods are commonly overridden methods in custom views. In onMeasure, the size of the view is measured, here the width type is set to UNSPECIFIED, which can be infinitely expanded. onLayout is after the view size is measured, placing the view at a certain position in the parent layout. The code shows that the menuView is hidden on the left (or right) based on the sliding direction.

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;
  }
  。。。。
  /**
   * Add a sliding menu that is displayed
   * Here it can be seen that each Item is a SwipeMenuLayout
   */
  public View getView(int position, View convertView, ViewGroup parent) {}}
    SwipeMenuLayout layout = null;
    if (convertView == null) {
      View contentView = mAdapter.getView(position, convertView, parent);//item's view
      SwipeMenu menu = new SwipeMenu(mContext); //Create SwipeMenu
      menu.setViewType(getItemViewType(position));
      createMenu(menu); //For testing purposes, you can ignore it first
      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;
  }
  //This method is overridden when created, and it is for testing purposes, so it can be ignored.
  public void createMenu(SwipeMenu menu) {
    // Test Code
   。。。。。。
  }
  /**
   * Callback method of OnSwipeItemClickListener
   * This method is overridden when the class is created.
   */
  public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) {
    if (onMenuItemClickListener != null) {
      onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu,
          index);
    }
  }
  。。。。//The unimportant parts are omitted
}

2.4 Core class: SwipeMenuListview,

This code is very long, and it requires patience to read.

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; //Direction
  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;
  //Create menuItem's
  private SwipeMenuCreator mMenuCreator;
  //menuItem's item click event
  private OnMenuItemClickListener mOnMenuItemClickListener;
  private OnMenuStateChangeListener mOnMenuStateChangeListener;
  private Interpolator mCloseInterpolator; //Animation rate
  private Interpolator mOpenInterpolator;
  //----added in myself--The following two lines are added by myself,
  //If you run the demo code down, you will find that when one item has been swiped open, if you swipe another item, the originally opened item does not close. You can see this in QQ's side swipe, which is closed. I have slightly modified it here.
  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();
  }
  //Initialize variables
  private void init() {
    MAX_X = dp2px(MAX_X);
    MAX_Y = dp2px(MAX_Y);
    mTouchState = TOUCH_STATE_NONE;
  }
  @Override
  /**
   * A wrapping of the parameter adapter into a SwipeMenuAdapter was performed.
   */
  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);
        }
        //Click the item in the list again to close the menu
        if (mTouchView != null && !flag) {
          mTouchView.smoothCloseMenu();
        }
      }
    });
  }
  ......
  @Override
  //Intercept events, judge whether the event is a click event or a sliding event
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    //Handle the intercept at the intercept point, and also allow swiping at the point where the click event is set for sliding, without affecting the original click event
    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; //Set the state to none every time Down occurs
        //Return the position of the item
        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
        //Get the view corresponding to the clicked item, which is SwipeMenuLayout
        View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
        //Only assign when empty to avoid assigning every time a touch occurs, which would result in multiple open states
        if (view instanceof SwipeMenuLayout) {
          //If there is an opened one, intercept. .mTouchView is SwipeMenuLayout
          //If it is the same mTouchView twice, update mTouchView; if it is not the same view, intercept and return true
          if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {
            Log.i("tag","onInterceptTouchEvent ACTION_DOWN in Listview.");
            return true;
          }
          mTouchView = (SwipeMenuLayout) view;
          mTouchView.setSwipeDirection(mDirection);//The default is left=1
        }
        //If touched on another view, intercept this event
        if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) {
          handled = true;
        }
        if (mTouchView != null) {
          mTouchView.onSwipe(ev);
        }
        return handled;
      case MotionEvent.ACTION_MOVE: //Intercept event when moving, handle it in 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) {}}
          //Each intercepted down sets the touch state to TOUCH_STATE_NONE. Only by returning true will onTouchEvent be executed, so it is enough to write it here
          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: //The premise of this DOWN event is that the event has already been intercepted, so the possible situation is:1. The menu has already been slid out, and then the left item area was clicked
                      //2. The menu has already been slid out, another item was clicked
                      //3. When sliding an item, first DOWN then MOVE
        Log.i("tag","Listview's onTouchEvent ACTION_DOWN. Did another item get clicked");
        int oldPos = mTouchPosition; //The design here is not reasonable, this event is called directly after onInterceptTouchEvent, mTouchPosition is the same
        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());//in the list
        //Here is the modification, pldPos is no longer used, changed to mOldTouchPosition
        if (mTouchPosition == mOldTouchPosition && mTouchView != null
            && mTouchView.isOpen()) {
          mTouchState = TOUCH_STATE_X; //swiped horizontally (side to side)
          //Call the onSwipe() event interface of SwipeMenuLayout
          mTouchView.onSwipe(ev);
          Log.i("tag","Listview's onTouchEvent ACTION_DOWN. The list was swiped or another item was clicked");
          return true;
        }
      if(mOldTouchPosition != mTouchPosition){ //when the DOWN position is different
          //shouldCloseMenu = true;
          mOldTouchPosition = mTouchPosition;
        }
        View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
        //A menu has already been opened. At this time, if another item is clicked
        //This method will never be executed!
        if (mTouchView != null && mTouchView.isOpen()) {
          //Close swipe menu
          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); //Cancel event, time ends
          //Perform menu close callback
          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:
        //Some may have headers, so subtract the header before judgment
        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount();
        //If the swipe is not fully displayed, it should be retracted. At this point, mTouchView has been assigned, and another view that cannot be swiped should not be swiped
        //This may cause mTouchView swip. Therefore, it is necessary to use position judgment to determine whether a view is being swiped
        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) { //In the X direction
          if (mTouchView != null) {
            mTouchView.onSwipe(ev); //Invoke the slide event
          }
          getSelector().setState(new int[]{0});
          ev.setAction(MotionEvent.ACTION_CANCEL);
          super.onTouchEvent(ev);//Event end
          return true;
        } else if (mTouchState == TOUCH_STATE_NONE) {//Move event after DOWN
          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: //The menu was closed
        Log.i("tag","ACTION_UP event of onTouchEvent");
        if (mTouchState == TOUCH_STATE_X) {
          if (mTouchView != null) {
            Log.i("tag","Why didn't the ACTION_UP event close");
            boolean isBeforeOpen = mTouchView.isOpen();
            //Invoke the slide event
            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) {
            //Perform the callback for the end of the slide
            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();
      }
    }
  }
  /**
   * You can go inside to see the source code, which is to convert different units to pixels px, here it is dp->px
   * @param dp
   * @return
   */
  private int dp2
    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);
  }
  。。。。
}

The most important logic in this class is about event judgment and dispatching, when to intercept the event, and what operations correspond to different events. If you are not clear about event dispatching, you can find related blogs on the Internet, or you can read my subsequent blogs, which should be within the next couple of days.

Here, we analyze the event dispatching logic of SwipeMenuListView: the core is the handling of item click and slide events in SwipeMenuListView. When sliding, SwipeMenuListView intercepts the event, processes it itself, and remembers this logic, as it is clear from the code. Below is a flowchart of the event dispatching process that I drew:

A touch event is a sequence of events: ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. Starts with ACTION_DOWN and ends with ACTION_UP.

Below is my printed process: (Add log in the code yourself)

I/tag: ACTION_DOWN in onTouchEvent of Listview. view=class com.baoyz.swipemenulistview.SwipeMenuLayout
I/tag: ACTION_DOWN handled=false in onInterceptTouchEvent
I/tag: onTouchEvent of SwipeMenuLayout
I/tag: ACTION_DOWN in onTouchEvent of Listview. Did you click another 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 of onTouchEvent event
I/tag: Why the ACTION_UP of onTouchEvent event did not close
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

 3. Existing problems

1.If you run the framework down, you will find a problem:

  When a ListView item has been slid open, assuming it is item1;At this time, slide another item, called item2;

  In this case, item1It will not close, item2Of course, it will not open.

  This effect is not good, and I have modified this problem in the code. The specific code is marked clearly.

2.It is the following code: In the ACTION_DOWN of onTouchEvent(MotionEvent ev) in SwipeMenuListView, this code will never be executed because&8203;onTouchEvent and onInterceptTouchEvent&8203;Corresponding MotionEvent.

mTouchPosition == oldPos&8203;Always equal.

//This method will never be executed! The author's intention is to close the menu when mTouchPosition != oldPos, but according to this code, these two values are always equal.
        //Because it corresponds to a MotionEvent, of course, it is equal
        if (mTouchView != null && mTouchView.isOpen()) {
          //Close swipe menu
          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); //Cancel event, time ends
          //Perform menu close callback
          if (mOnMenuStateChangeListener != null) {
            mOnMenuStateChangeListener.onMenuClose(oldPos);
          }
          return true;
        }

I have modified this problem in the code. It has been submitted to the original author on github.

Thank you for reading and hope it can help everyone. Thank you for your support of this site!

Declaration: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#w3Please report by email to codebox.com (replace # with @ when sending an email) and provide relevant evidence. Once verified, this site will immediately delete the content suspected of infringement.

You may also like