自定义View的滑动冲突解决(侧滑菜单和RecycleVIew的侧滑删除冲突)

  我们需要做的是一个侧滑菜单,在侧滑菜单的主页面中放置一个RecycleView,其中的item可以左右滑动删除,把两个功能都做了结果发现在一起时候会出现冲突,子布局不能使用,有布局同向滑动的冲突。

这里的RecycleView的滑动删除参考这位大佬的博客 https://blog.csdn.net/xiexuan2007/article/details/53156916

侧滑菜单参考的那位大佬的博客找不到了~~~~~~

我们先贴一下我们的布局代码,看一下自定义View 的嵌套:

<com.example.archermind.testview.util.SlideMean xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SlideMeanActivity">

    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:background="@color/wheat"
        android:orientation="vertical">
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/rosybrown"
        android:orientation="vertical">
        <include layout="@layout/toolbar" />

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/recy_slide">

        </android.support.v7.widget.RecyclerView>
    </LinearLayout>


</com.example.archermind.testview.util.SlideMean>


<com.example.archermind.testview.util.SlidingButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_delete"
            android:layout_width="80dp"
            android:layout_height="40dp"
            android:layout_toRightOf="@+id/layout_content"
            android:background="@color/red"
            android:gravity="center"
            android:text="删 除"
            android:textColor="#DDFFFFFF"
            android:textSize="20dp" />


        <RelativeLayout
            android:id="@+id/layout_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

            <TextView
                android:id="@+id/tv_name"
                android:layout_width="100dp"
                android:layout_height="40dp"
                android:gravity="center"
                android:text="@string/app_name" />

            <TextView
                android:id="@+id/tv_show"
                android:layout_width="200dp"
                android:layout_height="40dp"
                android:layout_marginLeft="20dp"
                android:layout_toRightOf="@+id/tv_name"
                android:gravity="center"
                android:maxLines="1"
                android:text="@string/app_name" />
        </RelativeLayout>

    </RelativeLayout>
</com.example.archermind.testview.util.SlidingButtonView>

 

    我们自定义View来处理事件,自定义View SlideMean这个View是一个父布局,自定义View SlidingButtonView是一个子布局,我们的SlidingButtonView需要左右滑动来调出来删除按钮,SlideMean view需要左右滑动出现菜单,在滑动的时候系统不知道你这时候点击的事件是应该触发那一个view去执行。

        我们需要在父布局中重写onInterceptTouchEvent的这个方法,这个方法会在OnTouchEvent事件执行之前回调,然后在这里做事件的拦截,onInterceptTouchEvent返回值为true这表示拦截,父布局的OnTouchEvent事件触发执行,如果返回值是false,则表示不拦截,OnTouchEvent不会触发,由子布局去执行触摸事件。

这里大家可以参考一下这位大佬的博客  https://blog.csdn.net/Z_L_P/article/details/53488085,他讲解的两种的处理方法,内部拦截和外部拦截,我做的是外部拦截做分发机制

下面是拦截事件的处理代码,我们在删除按钮菜单有和没有,向左右滑动,有四种情况。无菜单左滑,子布局触发;无菜单右滑,父布局触发;有菜单右滑,子布局触发。

public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent.........ACTION_DOWN");
                mFirstX = mLastX = ev.getX();
                mFirstY = mLastY = ev.getY();
                isIntercept =false;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent.........ACTION_MOVE"+ev.getX()+"-->"+mFirstX+SlidingButtonView.isOpen);
                float deltaX = ev.getX() - mLastX;
                float deltaY = ev.getY() - mLastY;
                mMove = ev.getX() - mFirstX;
                if(mMove > 0 && SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY) ){
                    isIntercept=false;
                    Log.e("--------","右滑动---菜单");
                   // return false;    //不拦截
                }else if(mMove > 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
                    Log.e("--------","右滑动---无菜单");
                    isIntercept = true;   //拦截父布局执行OnTouchEvent
                }else if(mMove < 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
                    Log.e("--------","左滑动---无菜单");
                    isIntercept = false;
                }else if(Math.abs(deltaX) < Math.abs(deltaY)){
                    isIntercept = false;
                }
//                if(Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
//                    Log.e("----------","执行了此事件");
//                    isIntercept=true;
//                }


        }
        return isIntercept;
    }

 

下面放入两个自定义View的代码:

SlideMean的代码,侧滑菜单

package com.example.archermind.testview.util;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.widget.TextView;

public class SlideMean extends ViewGroup {
    private int mTouchSlop;//被判定为滑动的最小距离
    private static final String TAG = "SlidingMenu";
    private Scroller mScroller;//弹性滑动
    public static  boolean isIntercept=false;;
    private VelocityTracker mVelocityTracker;//速度跟踪器
    private int mScrollWidth;
    private float mFirstX;

    private float mFirstY;

    private float mLastX;

    private float mLastY;

    private int menuWidth;//菜单的宽度

    private int contentWidth;//内容的宽度

    private View menu;//菜单

    private View content;//内容
    private float mMove;
    public SlideMean(Context context) {
        this(context, null);
    }

    public SlideMean(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideMean(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mTouchSlop = ViewConfiguration.getTouchSlop();
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //测量所有的子view
        int childCount = getChildCount();
        for(int i = 0; i < childCount; i++){
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.d(TAG, "l = " + l + "....." + "t = " + t + "....." + "r = " + r + "....." + "b = " + b);
        menu = getChildAt(0);
        content = getChildAt(1);
        menuWidth = menu.getMeasuredWidth();
        contentWidth = content.getMeasuredWidth();
        menu.layout(-1 * menuWidth, 0, 0, menu.getMeasuredHeight());
        content.layout(0, 0, contentWidth, content.getMeasuredHeight());
        Log.d(TAG, "content.getMeasuredWidth() = " + content.getMeasuredWidth()+"-----"+menuWidth);
        Log.d(TAG, "content.getMeasuredHeight() = " + content.getMeasuredHeight()+"----"+menu.getMeasuredHeight());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent.........ACTION_DOWN");
                mFirstX = mLastX = ev.getX();
                mFirstY = mLastY = ev.getY();
                isIntercept =false;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent.........ACTION_MOVE"+ev.getX()+"-->"+mFirstX+SlidingButtonView.isOpen);
                float deltaX = ev.getX() - mLastX;
                float deltaY = ev.getY() - mLastY;
                mMove = ev.getX() - mFirstX;
                if(mMove > 0 && SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY) ){
                    isIntercept=false;
                    Log.e("--------","右滑动---菜单");
                   // return false;
                }else if(mMove > 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
                    Log.e("--------","右滑动---无菜单");
                    isIntercept = true;
                }else if(mMove < 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
                    Log.e("--------","左滑动---无菜单");
                    isIntercept = false;
                }else if(Math.abs(deltaX) < Math.abs(deltaY)){
                    isIntercept = false;
                }
//                if(Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
//                    Log.e("----------","执行了此事件");
//                    isIntercept=true;
//                }


        }
        return isIntercept;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        mVelocityTracker.computeCurrentVelocity(1000);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:

                Log.e(TAG, "onTouchEvent.........ACTION_DOWN");
                return true;
            case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "onTouchEvent.........ACTION_MOVE");
                    Log.d(TAG, "getScrollX() = " + getScrollX());
                    //边界判断和处理
                    if (checkBound(event)) return true;
                    //手指按在屏幕上滑动的处理
                    float deltaX = event.getX() - mLastX;
                    scrollBy((int) (-1 * deltaX), 0);
                    mLastX = event.getX();
                    mLastY = event.getY();
                    break;
//                }
            case MotionEvent.ACTION_UP: //根据滚动的距离判断怎么还原
                Log.d(TAG, "onTouchEvent.........ACTION_UP");
                int vx = (int) Math.abs(mVelocityTracker.getXVelocity());
                Log.d(TAG, "vx = " + vx);
                //快速滑动的处理,未必一定是200
                if(vx > 200){
                    if(event.getX() - mFirstX > 0){ //向右快速滑动
                        showMenuComplete();
                        return true;
                    }else{ //向左快速滑动
                        hideMenuComplete();
                        return true;
                    }
                }
                //正常滑动的处理
                if(getScrollX() > -1 * menuWidth && getScrollX() <= -0.5f * menuWidth){ //拉出过半了
                    showMenuComplete();
                    return true;
                }
                if(getScrollX() > -0.5f * menuWidth && getScrollX() <= 0){ //拉出未过半
                    hideMenuComplete();
                    return true;
                }
                break;
        }
        return super.onTouchEvent(event);
    }


    /**
     * 完全隐藏menu
     */
    private void hideMenuComplete() {
        mScroller.startScroll(getScrollX(), 0, 0 - getScrollX(), 0);
        invalidate();
    }

    /**
     * 完全展示menu
     */
    private void showMenuComplete() {
        mScroller.startScroll(getScrollX(), 0, -1 * menuWidth - getScrollX(), 0);
        invalidate();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    /**
     * 边界的判断和处理
     */
    private boolean checkBound(MotionEvent event) {
        float tempX = event.getX() - mFirstX;
        if(tempX > 0){ //说明手指在向右滑动
            if(getScrollX() <= -1 * menuWidth){
                scrollTo(-1 * menuWidth, 0);
                Log.d(TAG, "已经碰到边界了...不能再往右滑动");
                return true;
            }
        }else{ //说明手指在向左滑动
            if(getScrollX() >= 0){
                scrollTo(0, 0);
                Log.d(TAG, "已经碰到边界了...不能再往左滑动");
                return true;
            }
        }
        return false;
    }


}

 

SlidingButtonView的代码

package com.example.archermind.testview.util;

import android.annotation.SuppressLint;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.TextView;

import com.example.archermind.testview.R;

public class SlidingButtonView extends HorizontalScrollView {
    private TextView mTextView_Delete, textView_update;
    //右侧的删除按钮的宽度
    private int mScrollWidth;

    private IonSlidingButtonListener mIonSlidingButtonListener;

    public static Boolean isOpen = false;
    private Boolean once = false;
    float x1 = 0;
    float x2 = 0;
    float y1 = 0;
    float y2 = 0;
    float Movex,Movey,Downx,Downy;

    public SlidingButtonView(Context context) {
        this(context, null);
    }

    public SlidingButtonView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        this.setOverScrollMode(OVER_SCROLL_NEVER);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!once) {
            mTextView_Delete = (TextView) findViewById(R.id.tv_delete);
            //textView_update = findViewById(R.id.tv_update);
            once = true;
        }

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            this.scrollTo(0, 0);
            //获取水平滚动条可以滑动的范围,即右侧按钮的宽度
            mScrollWidth = mTextView_Delete.getWidth();
            Log.i("asd", "mScrollWidth:" + mScrollWidth);
        }

    }


    @SuppressLint("LongLogTag")
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e("ButtonView->onTouchEvent","Down事件");

            case MotionEvent.ACTION_MOVE:
                Log.e("ButtonView->onTouchEvent","Move事件");
                if (mIonSlidingButtonListener != null) {
                    mIonSlidingButtonListener.onDownOrMove(this);
                }
                float deltaX = ev.getX() - x1;
                scrollBy((int) (-1 * deltaX), 0);
                x1 = ev.getX();
                y1 = ev.getY();
                break;
            case MotionEvent.ACTION_UP:
                 changeScrollx();
            case MotionEvent.ACTION_CANCEL:
                return true;
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //这里设置动画使删除按钮貌似后面冒出来的
      //  Log.e("显示记录", l + "=====" + mScrollWidth);
        // mTextView_Delete.setTranslationX(l - mScrollWidth);
    }

    /**
     * 按滚动条被拖动距离判断关闭或打开菜单
     */
    public void changeScrollx() {
        if (getScrollX() >= (mScrollWidth / 2)) {
            this.smoothScrollTo(mScrollWidth, 0);
            isOpen = true;
            mIonSlidingButtonListener.onMenuIsOpen(this);
            openMenu();
        } else {
            this.smoothScrollTo(0, 0);
            isOpen = false;
        }
    }

    /**
     * 打开菜单
     */
    public void openMenu() {
        if (isOpen) {
            return;
        }
        this.smoothScrollTo(mScrollWidth, 0);
        isOpen = true;
        mIonSlidingButtonListener.onMenuIsOpen(this);
    }

    /**
     * 关闭菜单
     */
    public void closeMenu() {
        if (!isOpen) {
            return;
        }
        this.smoothScrollTo(0, 0);
        isOpen = false;
    }


    public void setSlidingButtonListener(IonSlidingButtonListener listener) {
        mIonSlidingButtonListener = listener;
    }

    public interface IonSlidingButtonListener {
        //删除菜单打开
        void onMenuIsOpen(View view);
        void onDownOrMove(SlidingButtonView slidingButtonView);
        //删除菜单关闭或者该处被移除
    }

}

recycleview还使用了Adapteri将删除菜单的判断写到了这里,大家也可以直接在自定义View中进行处理,这样可以减少一定的耦合度。附上Adapter代码:

package com.example.archermind.testview.util;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.example.archermind.testview.R;

import java.util.ArrayList;
import java.util.List;

public class SlideAdapter extends RecyclerView.Adapter<SlideAdapter.MyViewHolder>  implements SlidingButtonView.IonSlidingButtonListener {
    private List<String> list_data = new ArrayList<>();
    private SlidingButtonView mMenu = null;
    SlidingButtonView.IonSlidingButtonListener mIonSlidingButtonListener;
    private IonSlidingViewClickListener mIDeleteBtnClickListener;
    private Context mContext;
    public SlideAdapter(List<String> list, Context context){
        list_data = list;
        mContext = context;
        mIDeleteBtnClickListener = (IonSlidingViewClickListener) context;
    }
    @NonNull
    @Override
    public SlideAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        final View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.spsn_item, viewGroup, false);
        SlideAdapter.MyViewHolder holder = new SlideAdapter.MyViewHolder(v);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull final SlideAdapter.MyViewHolder myViewHolder, int i) {
        myViewHolder.tv_name.setText(list_data.get(i));
        myViewHolder.tv_show.setText("zwn:"+list_data.get(i));
        myViewHolder.layout_content.getLayoutParams().width = Utils.getScreenWidth(mContext);
        myViewHolder.tv_delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               int n =  myViewHolder.getLayoutPosition();
               mIDeleteBtnClickListener.onDeleteBtnCilck(v, n);
                Log.e("---=","delete");
            }
        });
    }

    @Override
    public int getItemCount() {
        return list_data.size();
    }
   public  class MyViewHolder extends RecyclerView.ViewHolder{
        private TextView tv_name,tv_show,tv_delete;
        private ViewGroup layout_content;
       public MyViewHolder(@NonNull View itemView) {
           super(itemView);
           tv_name = itemView.findViewById(R.id.tv_name);
           tv_show = itemView.findViewById(R.id.tv_show);
           tv_delete = itemView.findViewById(R.id.tv_delete);
           layout_content = (ViewGroup) itemView.findViewById(R.id.layout_content);
           ((SlidingButtonView) itemView).setSlidingButtonListener(SlideAdapter.this);
       }
   }

    /**
     * 删除菜单打开信息接收
     */
    @Override
    public void onMenuIsOpen(View view) {
        mMenu = (SlidingButtonView) view;
    }


    /**
     * 滑动或者点击了Item监听
     * @param slidingButtonView
     */
    @Override
    public void onDownOrMove(SlidingButtonView slidingButtonView) {
        if(menuIsOpen()){
            if(mMenu != slidingButtonView){
                closeMenu();
            }
        }

    }

    /**
     * 关闭菜单
     */
    public void closeMenu() {
        mMenu.closeMenu();
        mMenu = null;

    }
    /**
     * 判断是否有菜单打开
     */
    public Boolean menuIsOpen() {
        if(mMenu != null){
            Log.i("asd","mMenu不为null");
            return true;
        }
        Log.i("asd","mMenu为null");
        return false;
    }
    public interface IonSlidingViewClickListener {
        void onDeleteBtnCilck(View view,int position);
    }

    public void removeData(int position){
        list_data.remove(position);
        notifyItemRemoved(position);

    }

}
工具类中的获取屏幕宽度的方法
public static int getScreenWidth(Context context)
{
    WindowManager wm = (WindowManager) context
            .getSystemService(Context.WINDOW_SERVICE );
    DisplayMetrics outMetrics = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics( outMetrics);
    return outMetrics .widthPixels ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值