美文网首页
自定义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