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

AndroidのSwipeMenuListViewフレームワークの詳細な解説と分析

週末に特別にAndroid SwipeMenuListView(スライドメニュー)の知識資料を整理しました。以下は整理した内容です:

SwipeMenuListView(スライドメニュー)

ListViewのためのスライドメニュー--非常に良いスライドメニューのオープンソースプロジェクトです。

デモ

 1. 概要

カスタムビューとイベントディスパッチを見ていた間に、自分の学んだことを実践するプロジェクトを見つけたいと思っていました。まさに自分の学んだことを確認するのに役立ちました。

GitHubでこのプロジェクト:SwipeMenuListViewを見つけました。これは非常に良いもので、イベントディスパッチとカスタムビューに非常に啓発的です。多少の小さな欠陥はありますが、後で説明します。スライドメニューの実現方法を知りたい方には、この記事が非常に役立ちます。これは、宏观から微观まで、各ファイルを詳細に分析しています。

プロジェクトのURL:https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 バージョン:b00e0fe その使用法は非常に簡単で、三つのステップだけで済みます。GitHubで見ればわかりますので、本文では原理の分析に限定します。また、コードを見て私と異なる感じがしたり、難しく感じる場合は、コメントを追加したものを見てください:http://download.csdn.net/detail/jycboy/9667699

まず二つの図を見てみましょう:全体の概要を把握するためにです

 これはフレームワーク内のすべてのクラスです。

1.以下の図はビューの階層です:

上記の図では:SwipeMenuLayoutはListView内のitemのレイアウトで、左右に分かれています。一つは通常表示されるcontentView、もう一つはスライドで出てくるmenuViewです;スライドで出てくるSwipeMenuViewはLinearLayoutを継承しており、ビューを追加する際には横に追加され、複数のビューを横に追加できます。

2.以下の図はクラス図の構造です:

上記はクラス間の呼び出し関係であり、クラスの横にクラスの主な機能が記載されています。

二、ソースコードの分析

SwipeMenu​ SwipeMenuItemはエンティティクラスで、属性とsetter、getterメソッドを定義しています。見てください。基本的にはソースコードのコメントが非常に明確です。

2.1 SwipeMenuView8203; 代码中注释的很清楚

/**
 * 横方向のLinearLayoutは、swipemenuの親レイアウト全体です
 * 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;
    //itemを通じてViewを作成し、SwipeMenuViewに追加します
    for (SwipeMenuItem item : items) {
      addItem(item, id++);
    }
  }
  /**
   * MenuItemをUIコントロールに変換し、1つのitemは垂直のLinearLayoutに相当します。
   * SwipeMenuViewは横方向のLinearLayoutです。
   */
  private void addItem(SwipeMenuItem item, int id) {
    //レイアウトパラメータ
    LayoutParams params = new LayoutParams(item.getWidth(),
        LayoutParams.MATCH_PARENT);
    LinearLayout parent = new LinearLayout(getContext());
    //menuitemのidを設定します。後のクリックイベントでitemを区別するために使用されます
    parent.setId(id);
    parent.setGravity(Gravity.CENTER);
    parent.setOrientation(LinearLayout.VERTICAL);
    parent.setLayoutParams(params);
    parent.setBackgroundDrawable(item.getBackground());
    //リスナーを設定します
    parent.setOnClickListener(this);
    addView(parent); //SwipeMenuViewに加えます。横方向の
    if (item.getIcon() != null) {
      parent.addView(createIcon(item));
    }
    if (!TextUtils.isEmpty(item.getTitle())) {
      parent.addView(createTitle(item));
    }
  }
  //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
  /**
   * 伝わってきたmLayoutを使って開いているかどうかを判断します
   * onItemClickクリックイベントを呼び出します
   */
  public void onClick(View v) {
    if (onItemClickListener != null && mLayout.isOpen()) {
      onItemClickListener.onItemClick(this, mMenu, v.getId());
    }
  }
  public OnSwipeItemClickListener getOnSwipeItemClickListener() {
    return onItemClickListener;
  }
  /**
   * itemのクリックイベントを設定します
   * @param onItemClickListener
   */
  public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
  }
  public void setLayout(SwipeMenuLayout mLayout) {
    this.mLayout = mLayout;
  }
  /**
   * クリックイベントのコールバックインターフェース
   */
  public static interface OnSwipeItemClickListener {
    /**
     * onClickクリックイベントでonItemClickを呼び出します
     * @param view 親レイアウト
     * @param menu menuエンティティクラス
     * @param index menuItemのID
     */
    void onItemClick(SwipeMenuView view, SwipeMenu menu, int index);
  }
}

**SwipeMenuView8203;これはスライド時に表示されるViewであり、SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView)のコンストラクタを見てください8203;;Itemsを巡回:menu.getMenuItems();addItemメソッドを呼び出して8203;SwipeMenuViewにitemを追加します。

addItemメソッド内で:各itemはLinearLayoutのものです。8203;。

2.2 SwipeMenuLayout8203;:

このクラスのコードは少し長いので、三部分に分けて見てみましょう。ここに核心のコードを貼り付け、残りは理解しやすいでしょう。

public class SwipeMenuLayout extends FrameLayout {
  private static final int CONTENT_VIEW_ID = 1;
  private static final int MENU_VIEW_ID = 2;
  private static final int STATE_CLOSE = 0;
  private static final int STATE_OPEN = 1;
  //方向
  private int mSwipeDirection;
  private View mContentView;
  private SwipeMenuView mMenuView;
  。。。。。
  public SwipeMenuLayout(View contentView, SwipeMenuView menuView) {
    this(contentView, menuView, null, null);
  }
  public SwipeMenuLayout(View contentView, SwipeMenuView menuView,
      Interpolator closeInterpolator, Interpolator openInterpolator) {
    super(contentView.getContext());
    mCloseInterpolator = closeInterpolator;
    mOpenInterpolator = openInterpolator;
    mContentView = contentView;
    mMenuView = menuView;
    //SwipeMenuLayoutをSwipeMenuViewに設定し、開いているかどうかを判断するために使用します。
    mMenuView.setLayout(this);
    init();
  }
  private void init() {
    setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,)
        LayoutParams.WRAP_CONTENT));
    mGestureListener = new SimpleOnGestureListener() {
      @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とmenuViewの2つの部分で構成されています。指でスライドする操作はSimpleOnGestureListenerによって完了されます。

/**
   * スライドイベント、外部からの呼び出し用のインターフェース
   * これは外部に公開されているAPIであり、このAPIを呼び出すのはSwipeMenuListViewであり、その場合MotionEventはSwipeMenuListViewのMotionEventです
   * @param event
   * @return
   */
  public boolean onSwipe(MotionEvent event) {
    mGestureDetector.onTouchEvent(event);
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      mDownX = (int) event.getX();//クリックしたx座標を記録します
      isFling = false;
      break;
    case MotionEvent.ACTION_MOVE:
      // Log.i("byz", "downX = ") + mDownX}} + ", moveX = " + event.getX());
      int dis = (int) (mDownX - event.getX());
      if (state == STATE_OPEN) {//状態がopenの場合、disは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:
      //スライド距離を判断し、開くか閉じるか
      //ここに、すでに1つのアイテムが開かれている場合、この時他のアイテムをスライドするとこのメソッドが実行されるので、どう改善できるか?
      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は正;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の2つで、主なロジックは:onSwipeは外部から呼び出されるAPIに公開されています。

SwipeMenuListViewのonTouchEventイベント処理メソッドでonSwipeが呼び出され、swipeはmContentViewとmMenuViewを各々dis距離でスライドします。8203;。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //幅は無限に拡張可能で、高さは指定されています
    mMenuView.measure(MeasureSpec.makeMeasureSpec(0,
        MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
        getMeasuredHeight(), MeasureSpec.EXACTLY));
  }
  protected void onLayout(boolean changed, int l, int t, int r, int b) {}}
    mContentView.layout(0, 0, getMeasuredWidth(),
        mContentView.getMeasuredHeight());
    if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//左スライド
      //親Viewに対して、左側と上側を基準にして右側に非表示にします。
      mMenuView.layout(getMeasuredWidth(), 0,
          getMeasuredWidth() + mMenuView.getMeasuredWidth(),
          mContentView.getMeasuredHeight());
    } else {  //右スライドで左側に非表示
      mMenuView.layout(-mMenuView.getMeasuredWidth(), 0,
          0, mContentView.getMeasuredHeight());
    }
  }

 上のonMeasure、onLayoutメソッドは、カスタムViewでよくオーバーライドされるメソッドです。onMeasureではViewの大きさを測定し、ここでは幅のタイプをUNSPECIFIEDに設定して無限に拡張できます。onLayoutはViewの大きさが測定された後に、Viewを親レイアウトのどこに配置するかを決定します。コードからは、スライド方向に応じてmenuViewを左側(または右側)に非表示にするのがわかります。

2.3 SwipeMenuAdapter

public class SwipeMenuAdapter implements WrapperListAdapter,
    OnSwipeItemClickListener {
  private ListAdapter mAdapter;
  private Context mContext;
  private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener;
  public SwipeMenuAdapter(Context context, ListAdapter adapter) {
    mAdapter = adapter;
    mContext = context;
  }
  。。。。
  /**
   * スライド中に表示されるメニューを追加します。
   * ここでは、それぞれのItemがSwipeMenuLayoutであることがわかります。
   */
  public View getView(int position, View convertView, ViewGroup parent) {
    SwipeMenuLayout layout = null;
    if (convertView == null) {
      View contentView = mAdapter.getView(position, convertView, parent);//アイテムのビュー
      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);
    }
  }
  。。。。//重要でない部分は省略しました
}

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;//デフォルトで右から左にスワイプ
  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;
  //----私が追加しました--以下の二行は私が追加したものです。
  //コードデモをダウンロードして実行してみると、アイテムがすでにスライド開いていた場合、別のアイテムをスライドすると、元のアイテムが閉じていないことがあります。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
  //インターセプトイベント、イベントがクリックイベントかスライドイベントかを判別します
  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());
        //空の時にのみ値を設定します。タッチのたびに設定すると、複数のオープン状態が発生します
        if (view instanceof SwipeMenuLayout) {
          //開いている場合、.mTouchViewはSwipeMenuLayoutをインターセプトします
          //2回目が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) {
          //毎回インターセプトされたDOWNはタッチ状態をTOUCH_STATE_NONEに設定します。trueを返すとonTouchEventが実行されますので、ここに書くだけで十分です
          if (mTouchState == TOUCH_STATE_NONE) {
            if (Math.abs(dy) > MAX_Y) {
              mTouchState = TOUCH_STATE_Y;
            } でも、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.このmenuがすでにスライドしています、さらに左側のitemエリアをタップしました
                      //2.menuがすでにスライドしています、他の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方向(横方向)にスライドして開きます
          //SwipeMenuLayoutのonSwipe()イベントインターフェースを呼び出します
          mTouchView.onSwipe(ev);
          Log.i("tag","ListviewのonTouchEvent ACTION_DOWN。スライドまたは別のアイテムをクリックしました");
          return true;
        }
      if(mOldTouchPosition != mTouchPosition){ //DOWN位置が異なる場合
          //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:
        //ヘッダーがある場合がいくつかあります。ヘッダーを引いてから判断します
        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount();
        //もし一瞬だけスライドして完全に表示されない場合は、それを戻します。この時点でmTouchViewは既に値が設定されていますので、他のスライド不可のviewをもう一度スライドすることはできません
        //これによりmTouchView swipが発生します。したがって、スライドするのは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);//イベント終了
          return true;
        } でも、mTouchState == TOUCH_STATE_NONEなら {//DOWNイベント後のMove
          if (Math.abs(dy) > MAX_Y) {
            mTouchState = TOUCH_STATE_Y;
          } でも、dx > MAX_Xなら {
            mTouchState = TOUCH_STATE_X;
            if (mOnSwipeListener != null) {
              mOnSwipeListener.onSwipeStart(mTouchPosition);
            }
          }
        }
        break;
      case MotionEvent.ACTION_UP: //menuを閉じました
        Log.i("tag","onTouchEventイベントのACTION_UP");
        if (mTouchState == TOUCH_STATE_X) {
          if (mTouchView != null) {
            Log.i("tag","onTouchEventイベントのACTION_UP なぜ閉じなかった");
            boolean isBeforeOpen = mTouchView.isOpen();
            //スライドイベントを呼び出します
            mTouchView.onSwipe(ev);
            boolean isAfterOpen = mTouchView.isOpen();
            if (isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener != null) {
              if (isAfterOpen) {
                mOnMenuStateChangeListener.onMenuOpen(mTouchPosition);
              } else {
                mOnMenuStateChangeListener.onMenuClose(mTouchPosition);
              }
            }
            if (!isAfterOpen) {
              mTouchPosition = -1;
              mTouchView = null;
            }
          }
          if (mOnSwipeListener != null) {
            //スライド終了の戻り値を実行します
            mOnSwipeListener.onSwipeEnd(mTouchPosition);
          }
          ev.setAction(MotionEvent.ACTION_CANCEL);
          super.onTouchEvent(ev);
          return true;
        }
        break;
    }
    return super.onTouchEvent(ev);
  }
  public void smoothOpenMenu(int position) {
    if (position >= getFirstVisiblePosition()
        && position <= getLastVisiblePosition()) {
      View view = getChildAt(position - getFirstVisiblePosition());
      if (view instanceof SwipeMenuLayout) {
        mTouchPosition = position;
        if (mTouchView != null && mTouchView.isOpen()) {
          mTouchView.smoothCloseMenu();
        }
        mTouchView = (SwipeMenuLayout) view;
        mTouchView.setSwipeDirection(mDirection);
        mTouchView.smoothOpenMenu();
      }
    }
  }
  /**
   * ソースコードを見てください、異なる単位をピクセルpxに統一する処理です。ここではdpです->px
   * @param dp
   * @return
   */
  private int 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);
  }
  。。。。
}

このクラスの中で最も重要なロジックは、イベントの判定と分散に関するものです。いつイベントをインターセプトするか、異なるイベントに対応する操作は何かです。イベント分散についてよくわからない場合は、オンラインのブログを検索したり、私の次のブログをチェックしてください。これは、数日以内のことです。

ここでSwipeMenuListViewのイベント分散ロジックを分析します:核心はSwipeMenuListView内のアイテムのクリックイベントとスライドイベントの処理です。スライド中にSwipeMenuListViewはイベントをインターセプトし、自分で処理します。このロジックはコードを見れば一目瞭然です。以下は、私が描いたイベント分散フローチャートです:

タッチイベントはイベントシーケンス:ACTION_DOWNです->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. ACTION_DOWNで始まり、ACTION_UPで終わります。

以下は私の印刷されたプロセスです:(コードにlogを追加して自分で確認)

I/タグ: ListviewのonInterceptTouchEvent ACTION_DOWN。view=class com.baoyz.swipemenulistview.SwipeMenuLayout
I/タグ: onInterceptTouchEvent ACTION_DOWN handled=false
I/タグ: SwipeMenuLayout onTouchEvent
I/タグ: ListviewのonTouchEvent ACTION_DOWN。他のitemをクリックしたかどうか
I/タグ: oldPos=1 mTouchPosition=1
I/タグ: ACTION_MOVE downX = 987, moveX = 906.69666, dis=80
I/タグ: ACTION_MOVE downX = 987, moveX = 855.5785, dis=131
I/タグ: ACTION_MOVE downX = 987, moveX = 797.6258, dis=189
I/タグ: ACTION_MOVE downX = 987, moveX = 735.9639, dis=251
I/タグ: ACTION_MOVE downX = 987, moveX = 666.5104, dis=320
I/タグ: ACTION_MOVE downX = 987, moveX = 589.0626, dis=397
I/タグ: ACTION_MOVE downX = 987, moveX = 509.15567, dis=477
I/タグ: ACTION_MOVE downX = 987, moveX = 431.7224, dis=555
I/タグ: ACTION_MOVE downX = 987, moveX = 361.2613, dis=625
I/タグ: ACTION_MOVE downX = 987, moveX = 319.70398, dis=667
I/タグ: onTouchEventイベントのACTION_UP
I/タグ: onTouchEventイベントのACTION_UP なぜ閉じないのか
I/タグ: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500
I/タグ: ACTION_UP downX = 987, moveX = 319.70398
I/タグ: mContentView.getLeft()=-540, mMenuView=540

 三、問題点

1.フレームワークを実行してみると、問題が発生することがあります:

  ListViewのitemがすでにスライドさせられている場合、例えばitem1;他のitemをスライドさせる場合、それをitem2;

  このような場合、item1閉じることもありません、item2もちろん、開くこともありません。

  この効果は良くありません。私はコードでこの問題を修正しました。具体的なコードは、以下のように示されています。

2.以下のコードが以下の通りです: SwipeMenuListViewのonTouchEvent(MotionEvent ev)のACTION_DOWNで、このコードは決して実行されません、なぜなら&8203;onTouchEventとonInterceptTouchEvent&8203;对应的一个MotionEvent。

mTouchPosition == oldPos&8203;永远相等。

//このメソッドは決して実行されません!著者の意図はmTouchPosition != oldPosの時にCloseMenuをすることですが、このコードではこの2つの値が常に相等であるためです。
        //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#wまでお知らせください。3codebox.com(メールを送信する際には、#を@に変更してください。報告を行い、関連する証拠を提供してください。一旦確認がつき、このサイトは侵害疑いのコンテンツをすぐに削除します。)

おすすめ