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

Подробный анализ и анализ фреймворка SwipeMenuListView для Android

В выходные специально собрал материалы по знаниям Android SwipeMenuListView (скользящее меню),以下是整理的内容:

SwipeMenuListView (скользящее меню)

Скользящее меню для ListView. -- Это очень guter开源 проект.

Demo

 Введение

Просто долго смотрел на пользовательские View и обработку событий, хотел найти проект для практики.. Это как бы подтверждало мои знания.

Нашел этот проект на GitHub: SwipeMenuListView, это действительно хорошо, он вдохновляет на понимание обработки событий и создания пользовательских View, хотя и есть несколько небольших недостатков, которые будут описаны позже. Если вы хотите понять, как реализуется скользящая меню, эта статья вам поможет, она анализирует каждый файл подробно с макро- и микро-угла.

Адрес проекта: https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 Версия: b00e0fe. Его использование очень просто, нужно всего три шага, и на GitHub можно понять, не занимая места, поэтому в этой статье только анализируется принцип. Кроме того, если вы чувствуете, что код differs от моего, и он вам кажется сложным, вы можете посмотреть на тот, который я добавил комментарии: http://download.csdn.net/detail/jycboy/9667699

Сначала посмотрите на два изображения: чтобы получить общее представление

 Это все классы в рамке.

1. Ниже приведена иерархия视图:

На верхней схеме: SwipeMenuLayout - это макет элементов ListView, разделенный на две части: одна часть - это normally displayed contentView, другая часть - это вылетающая menuView; вылетающая SwipeMenuView наследуется от LinearLayout, при добавлении view, это добавление по горизонтали, можно добавить несколько view по горизонтали.

2. Ниже приведена структура классов:

Верхняя часть показывает отношения между классами, рядом с ними указывается основная функция класса.

Второй раздел: анализ исходного кода

SwipeMenu и SwipeMenuItem - это сущностные классы, определяющие атрибуты и методы setter и getter. Просто посмотрите. В принципе, комментарии в исходном коде очень понятны.

2.1 SwipeMenuView: Комментарии в коде поясняют все

/**
 * Horizontal LinearLayout, это родительский макет всего SwipeMenu
 * Основная функция - определение методов добавления Item и настройки свойств Item
 * @author baoyz
 * @date 2014-8-23
 *
 */
public class SwipeMenuView extends LinearLayout implements OnClickListener {
  private SwipeMenuListView mListView;
  private SwipeMenuLayout mLayout;
  private SwipeMenu mMenu;
  private OnSwipeItemClickListener onItemClickListener;
  private int position;
  public int getPosition() {
    return position;
  }
  public void setPosition(int position) {
    this.position = position;
  }
  public SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView) {
    super(menu.getContext());
    mListView = listView;
    mMenu = menu; //
    // Список MenuItem
    List<SwipeMenuItem> items = menu.getMenuItems();
    int id = 0;
    // Создает View через item и добавляет его в SwipeMenuView
    for (SwipeMenuItem item : items) {
      addItem(item, id++);
    }
  }
  /**
   * Преобразует MenuItem в UI-контрол, один item эквивалентен вертикальному LinearLayout,
   * SwipeMenuView - это horizontal LinearLayout,
   */
  private void addItem(SwipeMenuItem item, int id) {
    //布局参数
    LayoutParams params = new LayoutParams(item.getWidth(),
        LayoutParams.MATCH_PARENT);
    LinearLayout parent = new LinearLayout(getContext());
    // Установка id menuitem, используется для различия 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));
    }
  }
  // Создание изображения
  private ImageView createIcon(SwipeMenuItem item) {
    ImageView iv = new ImageView(getContext());
    iv.setImageDrawable(item.getIcon());
    return iv;
  }
  /* Создание заголовка по параметрам */
   */
  private TextView createTitle(SwipeMenuItem item) {
    TextView tv = new TextView(getContext());
    tv.setText(item.getTitle());
    tv.setGravity(Gravity.CENTER);
    tv.setTextSize(item.getTitleSize());
    tv.setTextColor(item.getTitleColor());
    return tv;
  }
  @Override
  /**
   * Использование переданного mLayout для определения, открыт ли он
   * Вызов события клика onItemClick
   */
  public void onClick(View v) {
    if (onItemClickListener != null && mLayout.isOpen()) {
      onItemClickListener.onItemClick(this, mMenu, v.getId());
    }
  }
  public OnSwipeItemClickListener getOnSwipeItemClickListener() {
    return onItemClickListener;
  }
  /**
   * Установка события клика item
   * @param onItemClickListener
   */
  public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
  }
  public void setLayout(SwipeMenuLayout mLayout) {
    this.mLayout = mLayout;
  }
  /**
   * Интерфейс回调 для события клика
   */
  public static interface OnSwipeItemClickListener {
    /**
     * В событии onClick вызывается onItemClick
     * @param view родительский макет
     * @param menu сущность класса menu
     * @param index идентификатор menuItem
     */
    void onItemClick(SwipeMenuView view, SwipeMenu menu, int index);
  }
}

**SwipeMenuView - это View, которая отображается при скольжении, см. его конструктор SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView); итерация Items: menu.getMenuItems(); добавление item в SwipeMenuView с помощью метода addItem.

В методе addItem: каждый item - это LinearLayout.

2.2 SwipeMenuLayout​:

This class code is a bit long, let's divide it into three parts to see, 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 этот параметр является скоростью в направлении оси X, влево отрицательная, вправо положительная
      public boolean onFling(MotionEvent e1, MotionEvent e2,
          float velocityX, float velocityY) {
        // TODO
        if (Math.abs(e1.getX() - e2.getX()) > MIN_FLING
            && velocityX < MAX_VELOCITYX) {
          isFling = true;
        }
        Log.i("tag","isFling="+isFling+" e1.getX()="+e1.getX()+" e2.getX()="+e2.getX()+
            " velocityX="+velocityX+" MAX_VELOCITYX="+MAX_VELOCITYX);
        // Log.i("byz", MAX_VELOCITYX + ", velocityX = " + velocityX);
        return super.onFling(e1, e2, velocityX, velocityY);
      }
    });
    mGestureDetector = new GestureDetectorCompat(getContext(),
        mGestureListener);
    ...
    LayoutParams contentParams = new LayoutParams(
        LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    mContentView.setLayoutParams(contentParams);
    if (mContentView.getId() < 1) {
      //noinspection ResourceType
      mContentView.setId(CONTENT_VIEW_ID);
    }
    //noinspection ResourceType
    mMenuView.setId(MENU_VIEW_ID);
    mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,)
        LayoutParams.WRAP_CONTENT));
    addView(mContentView);
    addView(mMenuView);
  }

 Из метода init можно看出, что SwipeMenuLayout состоит из двух частей: itemView пользователя и menu View. Операции по скольжению пальцем выполняются через SimpleOnGestureListener.

/**
   * Событие скольжения, используемое для внешнего вызова интерфейса
   * Это API, которое открыто для внешнего использования, и это API вызывается SwipeMenuListView, поэтому MotionEvent - это MotionEvent SwipeMenuListView
   * @param event
   * @return
   */
  public boolean onSwipe(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      mDownX = (int) event.getX();//记下点击的x坐标
      isFling = false;
      break;
    case MotionEvent.ACTION_MOVE:
      // Log.i("byz", "downX = " + mDownX + ", moveX = " + event.getX());
      int dis = (int) (mDownX - event.getX());
      if (state == STATE_OPEN) {//当状态是open时,dis就是0
        Log.i("tag", "dis = " + dis);//这个值一直是0
        // DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1
        dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1
        Log.i("tag", "dis = " + dis + ", mSwipeDirection = " + mSwipeDirection);
      }
      Log.i("tag", "ACTION_MOVE downX = " + mDownX + ", moveX = " + event.getX() + ", dis="+dis);
      swipe(dis);
      break;
    case MotionEvent.ACTION_UP:
      // определяем расстояние скольжения, открывать или закрывать
      // здесь, если уже открыт один элемент, то при перемещении другого элемента, все равно выполняется этот метод, как улучшить?
      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());
        // открыть
        smoothOpenMenu();
      } else {
        // закрыть
        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 является положительным; right является отрицательным
    if (Math.signum(dis) != mSwipeDirection) { // left = 1; right = -1
      dis = 0; // не скользить
    } else if (Math.abs(dis) > mMenuView.getWidth()) { // больше его ширины, dis = mMenuView.getWidth(),
      dis = mMenuView.getWidth() * mSwipeDirection;
    }
    // перенастройка макета, постоянное смещение влево (или вправо),
    mContentView.layout(-dis, mContentView.getTop(),
        mContentView.getWidth() - dis, getMeasuredHeight());
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1
      //тоже заново установить положение menuview, рисование очень ясно
      mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(),
          mContentView.getWidth() + mMenuView.getWidth() - dis,
          mMenuView.getBottom());
    } else {
      mMenuView.layout(-mMenuView.getWidth() - dis, mMenuView.getTop(),
          - dis, mMenuView.getBottom());
    }
  }
  /**
   * Обновить состояние state = STATE_CLOSE;
   * Закрыть menu
   */
  public void smoothCloseMenu() {
    state = STATE_CLOSE;
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
      mBaseX = -mContentView.getLeft();
      //прокрутка на расстояние mMenuView.getWidth(),正好隐藏掉
      mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);
    } else {
      mBaseX = mMenuView.getRight();
      mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);
    }
    postInvalidate();
  }
  public void smoothOpenMenu() {
    if(!mSwipEnable){
      return ;
    }
    state = STATE_OPEN;
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
      mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);
      Log.i("tag", "mContentView.getLeft()="+mContentView.getLeft()+", mMenuView="+mMenuView.getWidth());//-451, это расстояние смещения dis, -(downX-moveX)
      //mContentView.getLeft()=-540, mMenuView=540 , эти абсолютные значения равны, это совершенно правильно! Ха-ха·
    } else {
      mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);
    }
    //Этот метод вызывается в не-ui потоке, чтобы отобразить вид.
    postInvalidate();
  }
  ....
}

Основные методы - это onSwipe и swipe, основная логика: onSwipe - это API, доступный для внешнего вызова,

В методе обработки событий onTouchEvent класса SwipeMenuListView вызывается onSwipe; а swipe это движение mContentView и mMenuView на расстояние dis.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //Ширина безгранично расширяема, высота задана
    mMenuView.measure(MeasureSpec.makeMeasureSpec(0,
        MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
        getMeasuredHeight(), MeasureSpec.EXACTLY));
  }
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mContentView.layout(0, 0, getMeasuredWidth(),
        mContentView.getMeasuredHeight());
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { // Левый свайп
      //Относительно родительского view, с левой и верхней сторонами как базой, скрыто справа
      mMenuView.layout(getMeasuredWidth(), 0,
          getMeasuredWidth() + mMenuView.getMeasuredWidth(),
          mContentView.getMeasuredHeight());
    } else {  // Прокрутка вправо, скрытие слева}}
      mMenuView.layout(-mMenuView.getMeasuredWidth(), 0,
          0, mContentView.getMeasuredHeight());
    }
  }

 Методы onMeasure и onLayout, приведенные выше, это часто перезаписываемые методы в пользовательских View, в onMeasure измеряется размер view, здесь тип ширины установлен в UNSPECIFIED, что позволяет ему расширяться бесконечно. Метод onLayout выполняется после измерения размера view и определяет, где поместить view в родительский макет. Из кода可以看出, в зависимости от направления скролла, menuView скрыт слева (или справа).

2.3 SwipeMenuAdapter

public class SwipeMenuAdapter implements WrapperListAdapter,
    OnSwipeItemClickListener {
  private ListAdapter mAdapter;
  private Context mContext;
  private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener;
  public SwipeMenuAdapter(Context context, ListAdapter adapter) {
    mAdapter = adapter;
    mContext = context;
  }
  ...
  /**
   * Добавление меню для отображения при скролле
   * Здесь可以看出 каждый элемент - это SwipeMenuLayout
   */
  public View getView(int position, View convertView, ViewGroup parent) {
    SwipeMenuLayout layout = null;
    if (convertView == null) {
      View contentView = mAdapter.getView(position, convertView, parent); // View элемента item
      SwipeMenu menu = new SwipeMenu(mContext); // Создание SwipeMenu
      menu.setViewType(getItemViewType(position));
      createMenu(menu); // тестовый, можно не обращать внимания
      SwipeMenuView menuView = new SwipeMenuView(menu,
          (SwipeMenuListView) parent);
      menuView.setOnSwipeItemClickListener(this);
      SwipeMenuListView listView = (SwipeMenuListView) parent;
      layout = new SwipeMenuLayout(contentView, menuView,
          listView.getCloseInterpolator(),
          listView.getOpenInterpolator());
      layout.setPosition(position);
    } else {
      layout = (SwipeMenuLayout) convertView;
      layout.closeMenu();
      layout.setPosition(position);
      View view = mAdapter.getView(position, layout.getContentView(),
          parent);
    }
    if (mAdapter instanceof BaseSwipListAdapter) {
      boolean swipEnable = (((BaseSwipListAdapter) mAdapter).getSwipEnableByPosition(position));
      layout.setSwipEnable(swipEnable);
    }
    return layout;
  }
  // Этот метод перезаписан при создании, здесь это тест, можно не обращать внимания.
  public void createMenu(SwipeMenu menu) {
    // Test Code
   。。。。。。
  }
  /**
   * Возвратный метод OnSwipeItemClickListener
   * Этот метод перезаписан при создании класса.
   */
  public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) {
    if (onMenuItemClickListener != null) {
      onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu,
          index);
    }
  }
  。。。。//исключены неimportantные
}

2.4 Основной класс: SwipeMenuListview,

Этот код очень длинный, при чтении нужно быть терпеливым.

public class SwipeMenuListView extends ListView {
  private static final int TOUCH_STATE_NONE = 0;
  private static final int TOUCH_STATE_X = 1;
  private static final int TOUCH_STATE_Y = 2;
  public static final int DIRECTION_LEFT = 1; //направление
  public static final int DIRECTION_RIGHT = -1;
  private int mDirection = 1;//по умолчанию swipe справа налево
  private int MAX_Y = 5;
  private int MAX_X = 3;
  private float mDownX;
  private float mDownY;
  private int mTouchState;
  private int mTouchPosition;
  private SwipeMenuLayout mTouchView;
  private OnSwipeListener mOnSwipeListener;
  //создание menuItem
  private SwipeMenuCreator mMenuCreator;
  //событие клика по menuItem
  private OnMenuItemClickListener mOnMenuItemClickListener;
  private OnMenuStateChangeListener mOnMenuStateChangeListener;
  private Interpolator mCloseInterpolator; //коэффициент изменения анимации
  private Interpolator mOpenInterpolator;
  //----добавлено мною--ниже приведены строки, добавленные мной
  //Если вы запустите этот код demo, вы обнаружите, что когда один элемент уже открыт, если скольжете к другому элементу, то открытый элемент не закрывается. Это можно увидеть в QQ, где侧滑是关闭的, здесь я немного изменил это.
  private int mOldTouchPosition = -1;
  private boolean shouldCloseMenu;
  //--------
  public SwipeMenuListView(Context context) {
    super(context);
    init();
  }
  public SwipeMenuListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  }
  public SwipeMenuListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }
  //初始化变量
  private void init() {
    MAX_X = dp2px(MAX_X);
    MAX_Y = dp2px(MAX_Y);
    mTouchState = TOUCH_STATE_NONE;
  }
  @Override
  /**
   * 对参数adapter进行了一次包装,包装成SwipeMenuAdapter
   */
  public void setAdapter(ListAdapter adapter) {
    super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) {
      @Override
      public void createMenu(SwipeMenu menu) {
        if (mMenuCreator != null) {
          mMenuCreator.create(menu);
        }
      }
      @Override
      public void onItemClick(SwipeMenuView view, SwipeMenu menu,
                  int index) {
        boolean flag = false;
        if (mOnMenuItemClickListener != null) {
          flag = mOnMenuItemClickListener.onMenuItemClick(
              view.getPosition(), menu, index);
        }
        // При повторном нажатии на item списка закрывать menu
        if (mTouchView != null && !flag) {
          mTouchView.smoothCloseMenu();
        }
      }
    });
  }
  。。。。。
  @Override
  // И拦截 события, определить, является ли это событие нажатием или событием swipe
  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 устанавливает состояние в состояние без состояния
        // Возвратить position item
        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
        // Получить view, соответствующий нажатому item, это 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: // При движении拦截 события, обрабатывается в onTouch
        float dy = Math.abs((ev.getY() - mDownY));
        float dx = Math.abs((ev.getX() - mDownX));
        if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) {
          // каждый раз, когда拦截down, устанавливается состояние касания TOUCH_STATE_NONE, только если возвращается true, будет вызван onTouchEvent, поэтому достаточно написать здесь
          if (mTouchState == TOUCH_STATE_NONE) {
            if (Math.abs(dy) > MAX_Y) {
              mTouchState = TOUCH_STATE_Y;
            } else if (dx > MAX_X) {
              mTouchState = TOUCH_STATE_X;
              if (mOnSwipeListener != null) {
                mOnSwipeListener.onSwipeStart(mTouchPosition);
              }
            }
          }
          return true;
        }
    }
    return super.onInterceptTouchEvent(ev);
  }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
      return super.onTouchEvent(ev);
    int action = ev.getAction();
    switch (action) {
      case MotionEvent.ACTION_DOWN: // этот DOWN-событие的前提 уже拦截илось событие, поэтому возможны следующие ситуации: 1. Меню уже выведено, затем был сделан клик по области левого item
                      // 2. Меню уже выведено, был сделан клик по другому item
                      // 3. При скольжении item сначала DOWN, затем MOVE
        Log.i("tag","Listview в onTouchEvent ACTION_DOWN. Была ли нажата другая item");
        int oldPos = mTouchPosition; // здесь проект не очень рационален, после onInterceptTouchEvent напрямую вызывается этот事件, mTouchPosition одинаковый
        if(mOldTouchPosition == -1){// -1 это исходное значение
          mOldTouchPosition = mTouchPosition;
        }
        mDownX = ev.getX();
        mDownY = ev.getY();
        mTouchState = TOUCH_STATE_NONE;
        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); // в списке
        // здесь было изменено, pldPos не используется, вместо этого используется mOldTouchPosition
        if (mTouchPosition == mOldTouchPosition && mTouchView != null
            && mTouchView.isOpen()) {}}
          mTouchState = TOUCH_STATE_X; //скольжение в направлении X (горизонтально)
          //вызов интерфейса события onSwipe SwipeMenuLayout
          mTouchView.onSwipe(ev);
          Log.i("tag","onTouchEvent ACTION_DOWN в Listview. Скольжение или клик по другому элементу");
          return true;
        }
      if(mOldTouchPosition != mTouchPosition){ //when the DOWN position is different
          //shouldCloseMenu = true;
          mOldTouchPosition = mTouchPosition;
        }
        View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
        //Уже открыто одно меню, и если в этот момент был кликнут другой элемент
        //Этот метод никогда не достигается!
        if (mTouchView != null && mTouchView.isOpen()) {
          // закройте swipeMenu
          mTouchView.smoothCloseMenu();
          mTouchView = null;
          // return super.onTouchEvent(ev);
          // попробуйте отменить событие касания
          MotionEvent cancelEvent = MotionEvent.obtain(ev);
          cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
          onTouchEvent(cancelEvent); // отмените событие, время завершено
          // выполните вызов обратно для menu close
          if (mOnMenuStateChangeListener != null) {
            mOnMenuStateChangeListener.onMenuClose(oldPos);
          }
          return true;
        }
        if (view instanceof SwipeMenuLayout) {
          mTouchView = (SwipeMenuLayout) view;
          mTouchView.setSwipeDirection(mDirection);
        }
        if (mTouchView != null) {
          mTouchView.onSwipe(ev);
        }
        break;
      case MotionEvent.ACTION_MOVE:
        //у некоторых может быть header,所以要 вычесть header, чтобы определить
        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount();
        //если свайп был выполнен не полностью, то его нужно вернуть, в этот момент mTouchView уже установлено, и нельзя свайпить другой view, который не поддерживает свайп
        //может вызвать swip у mTouchView. Поэтому используется положение для определения, является ли свайп одним из view
        if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) {
          break;
        }
        float dy = Math.abs((ev.getY() - mDownY));
        float dx = Math.abs((ev.getX() - mDownX));
        if (mTouchState == TOUCH_STATE_X) { //если в направлении X
          if (mTouchView != null) {
            mTouchView.onSwipe(ev); //вызов события скольжения
          }
          getSelector().setState(new int[]{0});
          ev.setAction(MotionEvent.ACTION_CANCEL);
          super.onTouchEvent(ev);//End of event
          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: //Closed menu
        Log.i("tag","ACTION_UP event of onTouchEvent");
        if (mTouchState == TOUCH_STATE_X) {
          if (mTouchView != null) {
            Log.i("tag","Why ACTION_UP event of onTouchEvent did not close");
            boolean isBeforeOpen = mTouchView.isOpen();
            //Вызов события скольжения
            mTouchView.onSwipe(ev);
            boolean isAfterOpen = mTouchView.isOpen();
            if (isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener != null) {
              if (isAfterOpen) {
                mOnMenuStateChangeListener.onMenuOpen(mTouchPosition);
              } else {
                mOnMenuStateChangeListener.onMenuClose(mTouchPosition);
              }
            }
            if (!isAfterOpen) {
              mTouchPosition = -1;
              mTouchView = null;
            }
          }
          if (mOnSwipeListener != null) {
            //Выполнение обратного вызова завершения скольжения
            mOnSwipeListener.onSwipeEnd(mTouchPosition);
          }
          ev.setAction(MotionEvent.ACTION_CANCEL);
          super.onTouchEvent(ev);
          return true;
        }
        break;
    }
    return super.onTouchEvent(ev);
  }
  public void smoothOpenMenu(int position) {
    if (position >= getFirstVisiblePosition()
        && position <= getLastVisiblePosition()) {
      View view = getChildAt(position - getFirstVisiblePosition());
      if (view instanceof SwipeMenuLayout) {
        mTouchPosition = position;
        if (mTouchView != null && mTouchView.isOpen()) {
          mTouchView.smoothCloseMenu();
        }
        mTouchView = (SwipeMenuLayout) view;
        mTouchView.setSwipeDirection(mDirection);
        mTouchView.smoothOpenMenu();
      }
    }
  }
  /**
   * можно перейти к исходному коду, чтобы увидеть, как преобразуются различные единицы в пиксели px, здесь это dp -> px
   * @param dp
   * @return
   */
  private int dp2px(int dp) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
        getContext().getResources().getDisplayMetrics());
  }
  public static interface OnMenuItemClickListener {
    boolean onMenuItemClick(int position, SwipeMenu menu, int index);
  }
  public static interface OnSwipeListener {
    void onSwipeStart(int position);
    void onSwipeEnd(int position);
  }
  public static interface OnMenuStateChangeListener {
    void onMenuOpen(int position);
    void onMenuClose(int position);
  }
  ...
}

Самый важный логик в этом классе - это判断和分发事件, когда блокировать события, какие действия выполняются для различных событий. Если вы не понимаете процесс分发 событий, вы можете найти相关信息 в блогах в Интернете или посмотреть мои последующие блоги, это должно быть в ближайшие дни.

Здесь анализируется логика分发 событий SwipeMenuListView: ядро - это обработка событий клика и скольжения item в SwipeMenuListView. При скольжении SwipeMenuListView блокирует события и обрабатывает их самостоятельно, эта логика очевидна из кода. Ниже приведена моя диаграмма процесса分发 событий:

Тouched event - это последовательность событий: ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. Начинается с ACTION_DOWN и заканчивается ACTION_UP.

Ниже приведен мой напечатанный процесс: (добавляю log в код)

I/tag: В Listview onTouchEvent ACTION_DOWN. view=class com.baoyz.swipemenulistview.SwipeMenuLayout
I/tag: onInterceptTouchEvent ACTION_DOWN обработано=false
I/tag: SwipeMenuLayout onTouchEvent
I/tag: В Listview onTouchEvent ACTION_DOWN. Нажал ли на другой элемент?
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 в onTouchEvent
I/tag: Почему ACTION_UP не закрывается в onTouchEvent
I/tag: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500
I/tag: ACTION_UP downX = 987, moveX = 319.70398
I/tag: mContentView.getLeft()=-540, mMenuView=540

 Третий раздел: проблемы,存在的

1. Если вы запустите этот фреймворк, вы обнаружите проблему:

  Когда один item ListView уже был сдвинут, предположим, item1; в этот момент сдвиньте другой item, назовем его item2;

  В этом случае item1 не закроется, а item2, естественно, не откроется.

  Этот эффект не очень хорош, я уже исправил эту проблему в коде. Конкретный код я уже отметил.

2. Это следующий код: в ACTION_DOWN onTouchEvent(MotionEvent ev) SwipeMenuListView, этот код никогда не будет выполнен, потому что onTouchEvent и onInterceptTouchEvent соответствуют одному MotionEvent.

mTouchPosition == oldPos всегда равно.

//Этот метод никогда не будет выполнен! Хотение автора - закрывать меню, когда mTouchPosition != oldPos, но по этому коду эти значения всегда равны.
        // поскольку это corresponds to a MotionEvent, они, конечно, равны
        if (mTouchView != null && mTouchView.isOpen()) {
          // закройте swipeMenu
          mTouchView.smoothCloseMenu();
          //mTouchView = null;
          // return super.onTouchEvent(ev);
          // попробуйте отменить событие касания
          MotionEvent cancelEvent = MotionEvent.obtain(ev);
          cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
          onTouchEvent(cancelEvent); // отмените событие, время завершено
          // выполните вызов обратно для menu close
          if (mOnMenuStateChangeListener != null) {
            mOnMenuStateChangeListener.onMenuClose(oldPos);
          }
          return true;
        }

Я уже исправил эту проблему в коде. В настоящее время я отправил это обратно автору на GitHub.

Спасибо за чтение, надеюсь, это поможет вам, спасибо за поддержку нашего сайта!

Заявление: содержимое статьи взято из Интернета, авторские права принадлежат соответствующему автору, контент предоставлен пользователями Интернета, самостоятельно загружен, сайт не обладает правами собственности, не был отредактирован вручную, не несет ответственности за соответствующие юридические последствия. Если вы обнаружите подозрительное нарушение авторских прав, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (при отправке письма замените # на @) для подачи жалоб и предоставления соответствующих доказательств. Если будет подтверждено, что контент涉嫌 нарушение авторских прав, сайт незамедлительно удалит涉嫌侵权的内容.

Рекомендуем вам