国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android SwipeMenuListView框架詳解分析

2019-12-12 04:53:00
字體:
來源:轉載
供稿:網友

周末 特地把Android SwipeMenuListView(滑動菜單)的知識資料整理一番,以下是整理內容:

SwipeMenuListView(滑動菜單)

A swipe menu for ListView.--一個非常好的滑動菜單開源項目。

Demo

 一、簡介

看了挺長時間的自定義View和事件分發,想找一個項目練習下。。正好印證自己所學。

在github上找到了這個項目:SwipeMenuListView這的真不錯,對事件分發和自定義View都很有啟發性,雖然還有點小瑕疵,后面說明。想了解滑動菜單怎么實現的同學,這篇文章絕對對你有幫助,從宏觀微觀角度詳細分析了每個文件。

項目地址:https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 版本:b00e0fe 它的使用很簡單只需要三步,在github上就可以看懂就不占用篇幅啦,本文只分析原理。另外如果你看代碼感覺和我不一樣,看著困難的話,可以看我加了注釋的:http://download.csdn.net/detail/jycboy/9667699

先看兩個圖:有一個大體的了解

 這是框架中所有的類。

1.下面的圖是視圖層次:

上面的圖中:SwipeMenuLayout是ListView中item的布局,分左右兩部分,一部分是正常顯示的contentView,一部分是滑出來的menuView;滑出來的SwipeMenuView繼承自LinearLayout,添加view時,就是橫向添加,可以橫向添加多個。

2.下面的圖是類圖結構:

上面是類之間的調用關系,類旁邊注明了類的主要作用。

二、源碼分析

SwipeMenu​、SwipeMenuItem是實體類,定義了屬性和setter、getter方法,看下就行。基本上源碼的注釋很清楚。

2.1 SwipeMenuView​: 代碼中注釋的很清楚

/** * 橫向的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集合    List<SwipeMenuItem> items = menu.getMenuItems();    int id = 0;    //通過item構造出View添加到SwipeMenuView中    for (SwipeMenuItem item : items) {      addItem(item, id++);    }  }  /**   * 將 MenuItem 轉換成 UI控件,一個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);  }}

**SwipeMenuView​就是滑動時顯示的View,看他的構造函數SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView)​;遍歷Items:menu.getMenuItems();調用addItem方法向​SwipeMenuView中添加item。

在addItem方法中:每一個item都是一個LinearLayout​。

2.2 SwipeMenuLayout​:

這個類代碼有點長,我們分成三部分看,只粘貼核心代碼,剩下的看一下應該就懂啦。

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由兩部分組成,分別是用戶的 item View 和 menu View 。手指的時候滑動的操作是通過 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:      //判斷滑動距離,是打開還是關閉      //在這里,如果已經有一個item打開了,此時滑動另外的一個item,還是執行這個方法,怎么改進?      if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) &&          Math.signum(mDownX - event.getX()) == mSwipeDirection) {        Log.i("tag", "ACTION_UP downX = " + mDownX + ", moveX = " + event.getX());        // open        smoothOpenMenu();      } else {        // close        smoothCloseMenu();        return false;      }      break;    }    return true;  }   public boolean isOpen() {    return state == STATE_OPEN;  }  /**   * 滑動dis的距離,把mContentView和mMenuView都滑動dis距離   * @param dis   */  private void swipe(int dis) {    if(!mSwipEnable){      return ;    }    //left is positive;right is negative    if (Math.signum(dis) != mSwipeDirection) {//left=1;right =-1      dis = 0; //不滑動    } else if (Math.abs(dis) > mMenuView.getWidth()) {//大于它的寬度,dis就是mMenuView.getWidth()      dis = mMenuView.getWidth()*mSwipeDirection;    }    //重新設置布局,不斷左移(或者右移),    mContentView.layout(-dis, mContentView.getTop(),        mContentView.getWidth() -dis, getMeasuredHeight());     if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1      //同上重新設置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 thread中調用這個方法,使視圖重繪    postInvalidate();  }  。。。}

上面主要的方法是onSwipe和swipe這兩個方法,主要邏輯是:onSwipe是暴漏給外面調用的API,

在SwipeMenuListView的onTouchEvent事件處理方法中調用了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;  }  。。。。  /**   * 添加滑動時的顯示的菜單   * 在這里可以看出每一個Item都是一個SwipeMenuLayout   */  public View getView(int position, View convertView, ViewGroup parent) {    SwipeMenuLayout layout = null;    if (convertView == null) {      View contentView = mAdapter.getView(position, convertView, parent);//item的view      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;//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;  //創建menuItem的  private SwipeMenuCreator mMenuCreator;  //menuItem的item點擊事件  private OnMenuItemClickListener mOnMenuItemClickListener;  private OnMenuStateChangeListener mOnMenuStateChangeListener;  private Interpolator mCloseInterpolator; //動畫變化率  private Interpolator mOpenInterpolator;   //----added in myself--下面這兩行是我自己加的,  //你如果下下來代碼demo運行下你會發現,當一個item已經滑開時,滑動另外的item,此時原來打開的item沒有關閉,可以看下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);        }        //再次點擊list中的item關閉menu        if (mTouchView != null && !flag) {          mTouchView.smoothCloseMenu();        }      }    });  }  。。。。。  @Override  //攔截事件,判斷事件是點擊事件還是滑動事件  public boolean onInterceptTouchEvent(MotionEvent ev) {    //在攔截處處理,在滑動設置了點擊事件的地方也能swip,點擊時又不能影響原來的點擊事件    int action = ev.getAction();    switch (action) {      case MotionEvent.ACTION_DOWN:        mDownX = ev.getX();        mDownY = ev.getY();        boolean handled = super.onInterceptTouchEvent(ev);        mTouchState = TOUCH_STATE_NONE; //每次Down都把狀態變為無狀態        //返回item的position        mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());         //得到那個點擊的item對應的view,就是SwipeMenuLayout        View view = getChildAt(mTouchPosition - getFirstVisiblePosition());        //只在空的時候賦值 以免每次觸摸都賦值,會有多個open狀態        if (view instanceof SwipeMenuLayout) {          //如果有打開了 就攔截.mTouchView是SwipeMenuLayout          //如果兩次是一個mTouchView,更新mTouchView;如果不是一個view,就攔截返回true          if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {            Log.i("tag","Listview中的onInterceptTouchEvent ACTION_DOWN。");            return true;          }          mTouchView = (SwipeMenuLayout) view;          mTouchView.setSwipeDirection(mDirection);//默認是left=1        }        //如果摸在另外一個view,攔截此事件        if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) {          handled = true;        }         if (mTouchView != null) {          mTouchView.onSwipe(ev);        }        return handled;      case MotionEvent.ACTION_MOVE: //MOVE時攔截事件,在onTouch中進行處理        float dy = Math.abs((ev.getY() - mDownY));        float dx = Math.abs((ev.getX() - mDownX));        if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) {          //每次攔截的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.該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 is the original value          mOldTouchPosition = mTouchPosition;        }        mDownX = ev.getX();        mDownY = ev.getY();        mTouchState = TOUCH_STATE_NONE;         mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());//list中        //這里改了,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。滑動了或點擊了另一個item");          return true;        }      if(mOldTouchPosition != mTouchPosition){ //when the DOWN position is different          //shouldCloseMenu = true;          mOldTouchPosition = mTouchPosition;        }        View view = getChildAt(mTouchPosition - getFirstVisiblePosition());        //已經有一個menu滑開了,此時如果點擊了另一個item        //這個方法永遠執行不到!        if (mTouchView != null && mTouchView.isOpen()) {          //關閉swipeMenu          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); //取消事件,時間結束           //進行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已經賦值,再滑動另外一個不可以swip的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;        } else if (mTouchState == TOUCH_STATE_NONE) {//DOWN事件后的Move          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: //關閉了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 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的事件分發邏輯:核心就是SwipeMenuListView中item的點擊事件和滑動事件的處理。當滑動時SwipeMenuListView攔截事件,自己處理,記住這個邏輯看代碼就一目了然了。下面是我畫的一個事件分發流程圖:

觸摸事件是一個事件序列:ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. 以ACTION_DOWN開始,以ACTION_UP結束。

下邊是我的一個打印的流程:(自己在代碼中加log)

I/tag: Listview中的onInterceptTouchEvent ACTION_DOWN。view=class com.baoyz.swipemenulistview.SwipeMenuLayoutI/tag: onInterceptTouchEvent ACTION_DOWN handled=falseI/tag: SwipeMenuLayout onTouchEventI/tag: Listview中的onTouchEvent ACTION_DOWN。是否點擊了另一個itemI/tag: oldPos=1 mTouchPosition=1I/tag: ACTION_MOVE downX = 987, moveX = 906.69666, dis=80I/tag: ACTION_MOVE downX = 987, moveX = 855.5785, dis=131I/tag: ACTION_MOVE downX = 987, moveX = 797.6258, dis=189I/tag: ACTION_MOVE downX = 987, moveX = 735.9639, dis=251I/tag: ACTION_MOVE downX = 987, moveX = 666.5104, dis=320I/tag: ACTION_MOVE downX = 987, moveX = 589.0626, dis=397I/tag: ACTION_MOVE downX = 987, moveX = 509.15567, dis=477I/tag: ACTION_MOVE downX = 987, moveX = 431.7224, dis=555I/tag: ACTION_MOVE downX = 987, moveX = 361.2613, dis=625I/tag: ACTION_MOVE downX = 987, moveX = 319.70398, dis=667I/tag: onTouchEvent事件的ACTION_UPI/tag: onTouchEvent事件的ACTION_UP 為什么沒有關閉I/tag: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500I/tag: ACTION_UP downX = 987, moveX = 319.70398I/tag: mContentView.getLeft()=-540, mMenuView=540

 三、存在的問題

1.如果你下下來框架運行了,你會發現一個問題:

  當ListView的一個item已經滑開,假設為item1;此時滑動另外一個的item,叫它item2;

  這種情況下item1不會關閉,item2當然也不會打開。

  這種效果并不好,我在代碼中已經修改了這個問題。具體代碼,我已經標明。

2.就是下面的這段代碼:在SwipeMenuListView的onTouchEvent(MotionEvent ev)的ACTION_DOWN中,這段代碼永遠不會執行到,因為​onTouchEvent和onInterceptTouchEvent​對應的一個MotionEvent。

mTouchPosition ==oldPos​永遠相等。

//這個方法永遠執行不到!作者的愿意是當mTouchPosition != oldPos時CloseMenu,但是按照這個代碼這兩個值是永遠相等的,        //因為對應的是一個MotionEvent當然就相等啦        if (mTouchView != null && mTouchView.isOpen()) {          //關閉swipeMenu          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); //取消事件,時間結束           //進行menu close的回掉          if (mOnMenuStateChangeListener != null) {            mOnMenuStateChangeListener.onMenuClose(oldPos);          }          return true;        }

在代碼中我已經修改了這個問題。目前已經在github上提交給原作者啦。

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 新安县| 铜川市| 兴安县| 岑巩县| 金平| 青州市| 石首市| 泽库县| 廉江市| 施甸县| 井冈山市| 会同县| 吉水县| 上犹县| 丹阳市| 调兵山市| 临夏县| 青河县| 阳山县| 汽车| 萨嘎县| 莱州市| 武城县| 莫力| 吕梁市| 民权县| 大新县| 礼泉县| 三台县| 和政县| 水城县| 乐昌市| 石城县| 德化县| 高陵县| 景东| 桓仁| 淳化县| 仁化县| 新建县| 调兵山市|