美文网首页
自定义View之QQ侧滑删除

自定义View之QQ侧滑删除

作者: Noblel | 来源:发表于2017-12-19 18:48 被阅读0次

    作者:Noblel
    链接:http://www.jianshu.com/p/b81cae309594
    请尊重原创,谢谢!

    需求来源:
    这是某一个版本的,现在的版本不是这样的了。

    QQ侧滑

    实现思路

    1.分析元素和效果:
    有两块布局,一个是内容,一个是菜单布局,可以左滑而且根据位置自动关闭和打开。

    2.自定义属性:
    为了简单,这里我就没有写自定义属性,套路都一样。不过思路上要记得这一步。

    3.编写SlideView

    3.1. 选择合适的布局,这里直接extends最简单的FrameLayout

    3.2. 定义变量和初始化布局

    原理图
    /**
     * 内容视图
     */
    private View mContentView;
    /**
     * 菜单视图
     */
    private View mMenuView;
    
    /**
     * 布局文件加载完成回调的方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentView = getChildAt(0);
        mMenuView = getChildAt(1);
    }
    

    item_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <com.noblel.slideview.SlideLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="60dp">
    
        <include
            android:id="@+id/item_content"
            layout="@layout/item_content" />
    
        <include
            android:id="@+id/item_menu"
            layout="@layout/item_menu" />
    </com.noblel.slideview.SlideLayout>
    

    item_content我们直接放一个TextView,menu中放三个TextView。

    item_content.xml

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#ffffff"
        android:gravity="center"
        android:textColor="#000000"
        android:textSize="25sp"/>
    

    item_menu.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="300dp"
        android:background="#ffffff"
        android:layout_height="60dp">
    
        <TextView
            android:id="@+id/tv_top"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:background="#C7C7CD"
            android:gravity="center"
            android:padding="5dp"
            android:text="置顶"
            android:textColor="#ffffff"
            android:textSize="25sp" />
    
        <TextView
            android:id="@+id/tv_no_read"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_weight="1"
            android:background="#FF9D01"
            android:gravity="center"
            android:padding="5dp"
            android:text="标为未读"
            android:textColor="#ffffff"
            android:textSize="25sp" />
    
        <TextView
            android:id="@+id/tv_delete"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:background="#FF3A30"
            android:gravity="center"
            android:padding="5dp"
            android:text="删除"
            android:textColor="#ffffff"
            android:textSize="25sp" />
    </LinearLayout>
    

    3.3. 有两部分视图那么我们需要在onLayout中给他们安排座位。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取内容的宽度
        mContentWidth = mContentView.getMeasuredWidth();
        //获取菜单视图的宽度
        mMenuWidth = mMenuView.getMeasuredWidth();
        //获取内容高度
        mContentHeight = mContentView.getMeasuredHeight();
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //指定菜单位置
        mMenuView.layout(mContentWidth, 0, mContentWidth + mMenuWidth, mContentHeight);
    }
    

    3.4. 设置侧滑系列操作

    • 在左滑的时候拦截事件交给自己onTouchEvent处理

       @Override
       public boolean onInterceptTouchEvent(MotionEvent event) {
           boolean intercept = false;
           switch (event.getAction()) {
               case MotionEvent.ACTION_DOWN:
                   //记录按下坐标
                   mDownX = mStartX = event.getX();
                   mDownY = event.getY();
                   if (mOnStateChangeListener != null) {
                       mOnStateChangeListener.onDown(this);
                   }
                   break;
               case MotionEvent.ACTION_MOVE:
                   float endX = event.getX();
                   //重新赋值
                   mStartX = event.getX();
                   //在X轴滑动的距离
                   float DX = Math.abs(endX - mDownX);
                   //大于一定距离才处理
                   intercept = DX > 8;
                   break;
           }
           return intercept;
       }
      
    • 计算滑动偏移量,并将视图移动更新视图

      @Override
      public boolean onTouchEvent(MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_MOVE:
                  float endX = event.getX();
                  float endY = event.getY();
                  //计算偏移量
                  float distanceX = endX - mStartX;
                  //getScrollX()和getX()和getRawX()后面做总结
                  int toScrollX = (int) (getScrollX() - distanceX);
                  if (toScrollX < 0) {
                      toScrollX = 0;
                  } else if (toScrollX > mMenuWidth) {
                      toScrollX = mMenuWidth;
                  }
                  //scrollTo和scrollBy()区别?
                  scrollTo(toScrollX, 0);
                  //重新赋值
                  mStartX = event.getX();
                  //在X轴和Y轴滑动的距离
                  float DX = Math.abs(endX - mDownX);
                  if (DX > 5) {
                     //水平方向滑动响应侧滑,设置反拦截-父控件不拦截,事件交给slideLayout
                      getParent().requestDisallowInterceptTouchEvent(true);
                  }
                  break;
              case MotionEvent.ACTION_UP:
                  int totalScrollX = getScrollX();
                  //手离开时候判断滑动距离小于一半时候关闭菜单
                  if (totalScrollX < mMenuWidth / 2) {
                      closeMenu();
                  } else {
                      openMenu();
                  }
                  break;
          }
          return true;
      }
      
    • 关闭菜单(开启菜单类似)

      /**
       * 关闭菜单
       */
      public void closeMenu() {
         int distanceX = 0 - getScrollX();
         //设置scroller滑动
         mScroller.startScroll(getScrollX(), getScrollY(), distanceX, getScrollY());
         //强制刷新
         invalidate();
         if (mOnStateChangeListener != null) {
             mOnStateChangeListener.onClose(this);
         }
      }
      
    • 设置computeScroll()

      /**
       * View在draw()的过程中调用
       */
      @Override
      public void computeScroll() {
          if (mScroller.computeScrollOffset()) {
              scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
              invalidate();
          }
      }
      

    3.5 设置相关接口

    /**
     * 监听slideLayout状态的改变
     */
    public interface OnStateChangeListener {
        void onClose(SlideLayout layout);
    
        void onDown(SlideLayout layout);
    
        void onOpen(SlideLayout layout);
    }
    

    效果:

    效果

    源码

    QQ侧滑删除源码

    问题思考:

    1. ListView多点触控会出现多个item同时打开菜单
      解决办法: 设置android:splitMotionEvents="false"

    2. 获取mContentWidth在onLayout的mMenuView.layout()之后会有一个item显示不出来
      原因: ListView的复用机制中会多次调用onLayout方法,而在mMenuView.layout()之后值都为0,所以显示不出来。具体需要了解ListView复用机制。

    getScrollX()和getX()和getRawX()区别?

    getX():触摸点相对于其所在视图原点在x轴上的偏移量
    getScrollX():当前视图相对于屏幕原点在x轴上的偏移量,就是实际视图(包括未显示出来的)距离屏幕左边缘的距离。如果在右边则为负数,在左边则为正数。
    getRawX():触摸点相对于屏幕原点在x轴上的偏移量,即getRawX() = getX() + getScrollX()

    scrollTo和scrollBy()区别?

    public void scrollBy(int x, int y) {
        //scrollBy就是调用scrollTo()
        scrollTo(mScrollX + x, mScrollY + y);
    }
    
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }
    

    scrollTo():在当前视图内容偏移至(x , y)坐标处,即显示(可视)区域位于(x , y)坐标处。
    scrollBy(): 在当前视图内容继续偏移(x , y)个单位,显示(可视)区域也跟着偏移(x,y)个单位。

    computeScroll()是做什么的?

    computeScrollOffset()方法会计算出下一个滚动到的位置,通过getCurrX(),getCurrY()这两个方法获得,在没滚到我们设定距离前返回是true,到了我们设定的距离后就返回false,表示计算完成了,滚动也这个时候完成。起始偏移量和移动距离和时间通过startscroll()设置的。他只是个设置的,并会自动绘制,要绘制就通常我们调用viewgroup的invalidate()等相关方法来重绘制。然后viewgroup会调用computeScroll()这个空方法,在这里面不断循环判断computeScrollOffset()和移动scrollTo()来达到我们自动滚动到目标距离。

    相关文章

      网友评论

          本文标题:自定义View之QQ侧滑删除

          本文链接:https://www.haomeiwen.com/subject/xfyiwxtx.html