实现仿QQ滑动侧边栏及滑动删除效果

1.0版本

在这里插入图片描述

侧滑主页面,包含侧边菜单栏和主界面的消息栏显示

package com.example.qxb_810.wigdets;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
/**
* create 2018/10/9
* desc 侧滑界面
*/
public class SlideMainView extends HorizontalScrollView {

    private int mMenuWidth;  // 侧滑菜单宽度
    private int mScreenWidth;   // 设备屏幕宽度

    private boolean measureOnce = false;

    private ViewGroup mMenu;    // 侧滑菜单
    private ViewGroup mContent;     // 主显内容

    public SlideMainView(Context context, AttributeSet attrs) {
        super(context, attrs);
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        // 获取设备屏幕宽度
        mScreenWidth = metrics.widthPixels;
    }

    // 测量控件尺寸
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!measureOnce) {
            // 获取到对应控件
            LinearLayout linearLayout = (LinearLayout) getChildAt(0);

            mMenu = (ViewGroup) linearLayout.getChildAt(0);
            mContent = (ViewGroup) linearLayout.getChildAt(1);
            // 设置控件的宽高  -- 菜单设置宽度为屏幕百分之80, 主显区占满屏幕
            mMenuWidth = mMenu.getLayoutParams().width = (int) (mScreenWidth * 0.8);
            mContent.getLayoutParams().width = mScreenWidth;
            measureOnce = true;
        }
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
        super.onLayout(changed, l, t, r, b);
        smoothScrollTo(mMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏,
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            int x = getScrollX();
            // view左上角为原点 -- 大于mMenuWidth一半是则说明拖拽不到一半,回主显页
            // 如果已经展开菜单且点击区域在主显区域,则将菜单收起, 或者拖拽出的侧边栏不到一半
            // x=0,则说明菜单展开,ev.getX() > mMenuWidth说明点击区域在主显区域, getScrollX() > mMenuWidth / 2则说明拖拽出侧边栏不到一半
            if ((x == 0 && ev.getX() > mMenuWidth) || getScrollX() > mMenuWidth / 2) {
                mContent.setEnabled(true);
                smoothScrollTo(mMenuWidth, 0);
            } else {
                mContent.setEnabled(false);
                smoothScrollTo(0, 0);
            }
            return true;    // return true 事件不发散
        }
        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
    }
}

SlideItemView 编辑删除的消息栏控件

package com.example.qxb_810.wigdets;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.example.qxb_810.qqslidingdemo.R;

/**
 * create 2018/10/8
 * desc 侧滑编辑删除View  ---  练手用
 */

public class SlideItemView extends HorizontalScrollView {
    private boolean once = false;
    private int mScreenWidth;   // 设备屏幕宽度
    private int mItemWidth;  // 模块宽度

    private float mStartPoint = -1;
    public SlideItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        mScreenWidth = metrics.widthPixels;
        mItemWidth = mScreenWidth / 2;

        View editView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_edit, null);
        View contentView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_content, null);
        LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.MATCH_PARENT);
        LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(mScreenWidth, ViewGroup.LayoutParams.MATCH_PARENT);

        LinearLayout.LayoutParams rootParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
        LinearLayout rootView = new LinearLayout(context);

        rootView.addView(contentView, contentParams);
        rootView.addView(editView, editParams);
        rootView.setOrientation(LinearLayout.HORIZONTAL);

        this.addView(rootView, rootParams);

    }

    @Override   // 防止父View拦截滑动事件
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            if (mStartPoint - ev.getX() > mItemWidth /2){
                smoothScrollTo(mItemWidth, 0);
            } else {
                smoothScrollTo(0, 0);
            }
            return true;
        } else if (ev.getAction() == MotionEvent.ACTION_DOWN){
            mStartPoint = -1;
            mStartPoint = ev.getX();
        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);


    }
}




menu_content.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:id="@+id/ll_menu_head"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:background="@color/menuHead"
        android:layout_margin="5dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="菜单头布局"
            android:textSize="50dp" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/ll_menu_item_one"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:background="@color/menuItem"
        android:layout_marginBottom="20dp"
        android:layout_marginTop="20dp"
        android:layout_margin="5dp">
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/colorPrimary"
            android:layout_margin="20dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30dp"
            android:text="one"/>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_menu_item_two"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:background="@color/menuItem"
        android:layout_margin="5dp">
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@color/colorPrimary"
            android:layout_margin="20dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30dp"
            android:text="two"/>
    </LinearLayout>
</LinearLayout>

main_show.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center">

    <com.example.qxb_810.wigdets.SlideItemView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/menuItem"
        android:scrollbars="none">

    </com.example.qxb_810.wigdets.SlideItemView>
</LinearLayout>

MainActivity

package com.example.qxb_810.qqslidingdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

主页面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="com.example.qxb_810.qqslidingdemo.MainActivity">

    <com.example.qxb_810.wigdets.SlideMainView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <include layout="@layout/menu_content" />
            <include layout="@layout/main_show" />
        </LinearLayout>
    </com.example.qxb_810.wigdets.SlideMainView>

</LinearLayout>

2.0版本 – 基本完成了QQ滑动侧边栏以及滑动删除效果

不过这个这个版本本来没想写成这样,但是写着写着发现太复杂了,整体架构都偏出了我的想法。不过弄巧成拙,从这个极为混乱的Demo中学到了安卓事件分发。
总结几点关于安卓事件分发机制的内容:
1. getScrollX() 和getRawX()和onTouchEvent中的event.getX()区别:

  • getScrollX – 相对于View的左上角,父布局(屏幕)左上角的坐标点,即View左上角为原点
  • getRawX() 获得触摸点在整个屏幕的 X 轴坐标。在父View铺满屏幕时和getX()获取的值相同
  • getX() – onTouchEvent 方法,其含义是相对于当前View左上角,你手指的坐标位置,即屏幕左上角为原点。

2. ViewGroup 和View的事件分发:详细内容可以参考https://www.cnblogs.com/linjzong/p/4191891.html

  • ViewGroup中包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。
  • 在ViewGroup中,当onInterceptTouchEvent返回True的时候事件会被自身处理,会调用ViewGroup的onTouchEvent事件,而dispatchTouchEvent返回true并不能能阻止继续调用onTouchEvent。
  • ViewGroup 执行顺序 — dispatchTouchEvent --> onInterceptTouchEvent --> onTouchEvent
  • 安卓点击事件传递, 首先由Activity分发,分发给根View,再分发给子View。执行顺序:onTouch > onLongClick > onClick, 前者会影响后者,后者不影响前者。如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发
  • 将事件分发给子View需要两个条件,requestDisallowInterceptTouchEvent()则是设置disallowIntercept的true or false
 //    将事件分发给子View需要一下两个条件,requestDisallowInterceptTouchEvent()则是设置disallowIntercept
 if(disallowIntercept || !onInterceptTouchEvent(ev)){
       将事件分发给子View
    }
  • 在dispatchTouchEvent() 中调用getParent().requestDisallowInterceptTouchEvent(true); 含义:当传入的参数为true时,表示子组件要自己消费这次事件,告诉父组件不要拦截(抢走)这次的事件。
  • 当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
  • 当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
  • 当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
  • onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

接下来项目截图:

在这里插入图片描述

GIF图失效,加几张图片吧

在这里插入图片描述

在这里插入图片描述
解释一下Demo开发:一开始只是想写个自定义滑动Demo — SlideMainView,类似版本1.0 后来写好之后觉着可以写一个类似QQ的滑动删除。随后增加一个侧滑编辑删除SlideItemView。本来想着将SlideItemView加入到SlideMainView中添加一些事件就行。但是后来发现由于事件分发导致很多事件不能触发。正常情况下,当时就觉着这个东西不能这么实现。但就是偏偏不信邪,于是便想着就要这么实现。在实现过程中也学习到了安卓的事件分发。与此同时,项目结构变得极其混乱,为了实现功能无所不用其极,最终诞生了这个Demo。

Demo结构: MainActivity中有一个自定义View — SlideMainView ,这个View其实包括侧边菜单和主显页面。
而主显页面中有一个RecyclerView,RecyclerView的itemView就是侧滑编辑删除菜单View — SlideItemView。
同时为了实现一些事件的触发,需要自定义一个RecyclerView — SlideRecyclerView。与此同时自定义一个帮助类 — MyLinearSnapHelper。

如图所示。。。。

在这里插入图片描述

MainActivity.java

package com.example.qxb_810.qqslidingdemo;

import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.example.qxb_810.wigdets.MyLinearSnapHelper;
import com.example.qxb_810.wigdets.SlideItemView;
import com.example.qxb_810.wigdets.SlideMainView;

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

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.ll_menu_head)
    LinearLayout llMenuHead;
    @BindView(R.id.ll_menu_item_one)
    LinearLayout llMenuItemOne;
    @BindView(R.id.ll_menu_item_two)
    LinearLayout llMenuItemTwo;
    @BindView(R.id.rv_slide)
    SlideRecyclerView rvSlide;
    @BindView(R.id.smv_content)
    SlideMainView smvContent;

    private List<String> mItemlist = new ArrayList<>();
    private SlideAdapter mSlideAdapter;
    private int mCurrOpenPosition = -1; // 当前滑动打开编辑页的item position

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        // 随便添加数据
        mItemlist.add("0");
        mItemlist.add("1");
        mItemlist.add("2");
        mItemlist.add("3");
        mItemlist.add("4");
        mItemlist.add("5");
        mItemlist.add("6");
        mItemlist.add("7");
        mItemlist.add("8");
        mItemlist.add("9");
        mItemlist.add("10");
        mItemlist.add("11");
        mItemlist.add("12");
        mItemlist.add("13");
        mItemlist.add("14");
        mItemlist.add("15");
        mItemlist.add("16");
        mItemlist.add("17");
        mItemlist.add("18");
        mItemlist.add("19");
        mItemlist.add("20");

        mSlideAdapter = new SlideAdapter(this, mItemlist);
        rvSlide.addItemDecoration(new SpacesItemDecoration(10));
        rvSlide.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        new MyLinearSnapHelper().attachToRecyclerView(rvSlide); // 使滑动RecyclerView结束时第一个item完整显示
        rvSlide.setAdapter(mSlideAdapter);
        initEvent();
    }

    /**
     * 初始化事件
     */
    private void initEvent() {
        // 设置侧边栏点击事件
        llMenuItemOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getBaseContext(), "llMenuItemOne", Toast.LENGTH_SHORT).show();
            }
        });
        llMenuItemTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getBaseContext(), "llMenuItemTwo", Toast.LENGTH_SHORT).show();
            }
        });

        // 添加RecyclerView Touch 事件, 用于刷新主显页
        rvSlide.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    mSlideAdapter.notifyDataSetChanged();
                }
                return false;
            }
        });

    }

    /**
     * 消息栏适配器
     */
    public class SlideAdapter extends RecyclerView.Adapter<SlideAdapter.SlideViewHolder> {

        private Context context;
        private List<String> list;

        public SlideAdapter(Context context, List<String> list) {
            this.context = context;
            this.list = list;
        }

        @NonNull
        @Override
        public SlideViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(context).inflate(R.layout.recyclerview_slide_item, parent, false);
            return new SlideViewHolder(v);
        }

        @Override
        public void onBindViewHolder(@NonNull SlideViewHolder holder, int position) {
            // 将主显页的item全部设置为未滑开的的状态
            holder.sivItem.setContent(list.get(position), null, null);
            holder.sivItem.scrollTo(0, 0);
            holder.sivItem.setOpen(false);
            SlideItemView.isOneOpen = false;
            mCurrOpenPosition = -1;
        }


        @Override
        public int getItemCount() {
            return list.size();
        }

        class SlideViewHolder extends RecyclerView.ViewHolder {
            @BindView(R.id.siv_item)
            SlideItemView sivItem;

            public SlideViewHolder(View itemView) {
                super(itemView);
                ButterKnife.bind(this, itemView);
                // 设置消息回调
                sivItem.setmListenter(new SlideItemClickListener() {
                    @Override
                    public void onEdit() {  // 点击编辑时
                        Toast.makeText(context, "onEdit", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onDeltete() {   // 点击删除时
                        notifyItemRemoved(getPosition());
                        list.remove(getPosition());
                        mCurrOpenPosition = -1;
                        Toast.makeText(context, "onDeltete", Toast.LENGTH_SHORT).show();

                    }

                    @Override
                    public void onContent() {
                    }

                    @Override
                    public void onMove() {          // 当item滑动除编辑删除页的时候触发
                        // 记录当前打开的序号
                        mCurrOpenPosition = getPosition();
                        rvSlide.setmCurrOpenPosition(mCurrOpenPosition);
                        //        Toast.makeText(context, "onMove +  " + mCurrOpenPosition, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onResume() {    // 当item关闭编辑删除页时触发
                        mCurrOpenPosition = -1;
                        rvSlide.setmCurrOpenPosition(mCurrOpenPosition);
                    }
                });


            }
        }
    }


    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
        private int space;

        public SpacesItemDecoration(int space) {
            this.space = space;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view,
                                   RecyclerView parent, RecyclerView.State state) {
            outRect.left = space;
            outRect.right = space;
            outRect.bottom = space;

            if (parent.getChildPosition(view) == 0)
                outRect.top = space;
        }
    }

    public interface SlideItemClickListener {
        void onEdit();      // 点击编辑时调用

        void onDeltete();   // 点击onDelete()时调用

        void onContent();   // 点击onContent时调用

        void onMove();      // 滑出编辑删除时调用

        void onResume();    // item恢复时使用
    }

}


MainActivity 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="com.example.qxb_810.qqslidingdemo.MainActivity">

    <com.example.qxb_810.wigdets.SlideMainView
        android:id="@+id/smv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <include layout="@layout/menu_content" />
            <include layout="@layout/main_show" />
        </LinearLayout>
    </com.example.qxb_810.wigdets.SlideMainView>

</LinearLayout>

SlideMainView.java

package com.example.qxb_810.wigdets;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

import com.example.qxb_810.qqslidingdemo.MainActivity;


/**
 * create 2018/10/9
 * desc 侧滑界面  -- 这个界面包括侧滑的菜单页和主显内容页
 */
public class SlideMainView extends HorizontalScrollView {

    private int mMenuWidth;  // 侧滑菜单宽度
    private int mScreenWidth;   // 设备屏幕宽度

    private boolean measureOnce = false;

    private ViewGroup mMenu;    // 侧滑菜单
    private ViewGroup mContent;     // 主显内容
    private float mStartPoint = -1;

    public boolean isMenuShow = false;      // 菜单是否显示  --- 菜单是否被拖拽出来

    public SlideMainView(Context context, AttributeSet attrs) {
        super(context, attrs);
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        // 获取设备屏幕宽度
        mScreenWidth = metrics.widthPixels;
    }

    @Override   // ViewGroup onInterceptTouchEvent返回true,自己捕获事件
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 当菜单展开,且点击区域不在菜单区域时由自身处理该事件,调用自身onTouch
        // isMenuShow 标识菜单展开, ev.getX() > mMenuWidth标识点击区域不是菜单区域,及点击的是部分显示的主显区域
        // 此时调用本身的onTouchEvent事件
        return  isMenuShow && ev.getX() > mMenuWidth ? true : super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 如果没拖拽出菜单
        if (!isMenuShow) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                mStartPoint = -1;
                mStartPoint = ev.getX();
            } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
                // 判断如果是在拖拽主显区域的item,则将事件分发给子View
                if (mStartPoint - ev.getX() > 0) {  // 大于零时是往右拖拽,在菜单栏隐藏时往右拖拽则将事件分发给子View
                    requestDisallowInterceptTouchEvent(true);   // true 则告诉View自己并不关心此次事件,分发给子View
                }
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    // 测量控件尺寸
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!measureOnce) {
            // 获取到对应控件
            LinearLayout linearLayout = (LinearLayout) getChildAt(0);

            mMenu = (ViewGroup) linearLayout.getChildAt(0);
            mContent = (ViewGroup) linearLayout.getChildAt(1);
            // 设置控件的宽高  -- 菜单设置宽度为屏幕百分之80, 主显区占满屏幕
            mMenuWidth = mMenu.getLayoutParams().width = (int) (mScreenWidth * 0.8);
            mContent.getLayoutParams().width = mScreenWidth;
            measureOnce = true;
        }
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
        super.onLayout(changed, l, t, r, b);
        smoothScrollTo(mMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏,
        isMenuShow = false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            // view左上角为原点 -- 大于mMenuWidth一半是则说明拖拽不到一半,回主显页
            // 如果已经展开菜单且点击区域在主显区域,则将菜单收起, 或者拖拽出的侧边栏不到一半
            // isOpen = false,则说明菜单展开,ev.getX() > mMenuWidth说明点击区域在主显区域, getScrollX() > mMenuWidth / 2则说明拖拽出侧边栏不到一半
            if ((isMenuShow && ev.getX() > mMenuWidth) || getScrollX() > mMenuWidth / 2) {
                smoothScrollTo(mMenuWidth, 0);
                isMenuShow = false;
            } else {
                smoothScrollTo(0, 0);
                isMenuShow = true;
            }
            return true;    // return true 事件不发散
        }

        return super.onTouchEvent(ev);
    }

}

SlideItemView

package com.example.qxb_810.wigdets;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.example.qxb_810.qqslidingdemo.MainActivity;
import com.example.qxb_810.qqslidingdemo.R;


/**
 * create 2018/10/8
 * desc 侧滑编辑删除View  --- 主显区域的item
 */

public class SlideItemView extends HorizontalScrollView implements View.OnClickListener {

    private int mScreenWidth;   // 设备屏幕宽度
    private int mItemWidth;  // 模块宽度
    private float mStartPoint = -1;
    private boolean isOpen = false;
    public static boolean isOneOpen = false;    // 用于标识RecyclerView中的item是否有滑出编辑删除页

    private View mEditView;
    private View mContentView;
    private TextView mTvEdit;       // 编辑tv
    private TextView mTvDelete;     // 删除tv
    private TextView mTvContent;    // content tv

    private MainActivity.SlideItemClickListener mListenter;

    public void setmListenter(MainActivity.SlideItemClickListener mListenter) {
        this.mListenter = mListenter;
    }

    public void setOpen(boolean open) {
        isOpen = open;
    }


    /**
     * 设置属性值
     *
     * @param content content值
     * @param edit    edit值
     * @param delete  删除值
     */
    public void setContent(String content, String edit, String delete) {
        if (content != null) {
            mTvContent.setText(content);
        }
        if (edit != null) {
            mTvEdit.setText(edit);
        }
        if (delete != null) {
            mTvDelete.setText(delete);
        }
    }

    public SlideItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        mScreenWidth = metrics.widthPixels;
        // 设置编辑删除区域的快读为屏幕宽度的一半
        mItemWidth = mScreenWidth / 2;
        // 动态添加编辑删除部分和content部分
        mEditView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_edit, null);
        mContentView = LayoutInflater.from(context).inflate(R.layout.sideslip_item_content, null);
        mTvEdit = mEditView.findViewById(R.id.tv_edit);
        mTvDelete = mEditView.findViewById(R.id.tv_delete);
        mTvContent = mContentView.findViewById(R.id.tv_content);
        // 设置相应的点击事件
        mTvEdit.setOnClickListener(this);
        mTvDelete.setOnClickListener(this);
        mTvContent.setOnClickListener(this);

        // 动态添加view
        LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.MATCH_PARENT);
        LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(mScreenWidth, ViewGroup.LayoutParams.MATCH_PARENT);

        LinearLayout.LayoutParams rootParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
        LinearLayout rootView = new LinearLayout(context);

        rootView.addView(mContentView, contentParams);
        rootView.addView(mEditView, editParams);
        rootView.setOrientation(LinearLayout.HORIZONTAL);

        this.addView(rootView, rootParams);
    }

    /*
    将事件分发给子View需要一下两个条件,requestDisallowInterceptTouchEvent()则是设置disallowIntercept
    if(disallowIntercept || !onInterceptTouchEvent(ev)){
       将事件分发给子View
    }
    */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (isOpen) {
            getParent().requestDisallowInterceptTouchEvent(true);   // 设置父类的 disallowIntercept 为true,即父类不拦截
        }
        // disallowIntercept这个标志表示是否禁用事件拦截功能,默认情况都是false,当isOpen为false时,disallowIntercept为默认值false,
        // 父View中super.onInterceptTouchEvent(ev)返回为false 则不将事件分发给子View当前View也收不到事件。
        // 即isOpen为false时事件由父View处理。
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //  检测拖拽,如果拖拽长度大于 编辑删除区域一半时则会默认拖拽出来,否则复位。
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            if (mStartPoint - ev.getX() > mItemWidth / 2) {
                smoothScrollTo(mItemWidth, 0);
                isOpen = true;
                isOneOpen = true;
                mListenter.onMove();
            } else {
                smoothScrollTo(0, 0);
                isOpen = false;
                isOneOpen = false;
                mListenter.onResume();
            }
            return true;
        } else if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 记录落点,这里会触发一次onContent回调,触发玩的,没什么用
            mStartPoint = -1;
            mStartPoint = ev.getX();
            mListenter.onContent();
        }
        return super.onTouchEvent(ev);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_edit:
                mListenter.onEdit();
                break;
            case R.id.tv_delete:
                mListenter.onDeltete();
                break;
            case R.id.tv_content:
                mListenter.onContent();
                break;
            default:
                break;
        }
    }
}

SlideRecyclerView.java

package com.example.qxb_810.qqslidingdemo;

import android.content.Context;
import android.graphics.Matrix;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.example.qxb_810.wigdets.SlideItemView;


/**
 * create 2018/10/12 16:36
 * desc 自定义RecyclerView,解决分发事件问题
 */

public class SlideRecyclerView extends RecyclerView {

    private int mCurrOpenPosition = -1;
    private int mItemHeight = -1;

    public void setmCurrOpenPosition(int mCurrOpenPosition) {
        this.mCurrOpenPosition = mCurrOpenPosition;
    }

    public SlideRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        // 计算出当前单击的item序号,判断点击的是否是当前滑开的item
        LinearLayoutManager manager = ((LinearLayoutManager) getLayoutManager());
        if (mItemHeight == -1) {
            mItemHeight = manager.getChildAt(0).getHeight() + 10;    // +10是item每个都设置了10像素的间距
        }
        int firstPosition = manager.findFirstVisibleItemPosition() + 1;
        int position = (int) (e.getY() / mItemHeight) + firstPosition - 1;
        // 如果有item被滑开且当前点击的item不是被滑开的item,则事件不会往下分发,由RecyclerView来处理该次事件 注: 会调用MainActivity中的RecyclerView设置的OnTouchListener事件
        return SlideItemView.isOneOpen && mCurrOpenPosition != position ? true : super.onInterceptTouchEvent(e);
    }


//    @Override
//    public void onScrollStateChanged(int state) {
//        super.onScrollStateChanged(state);
//        // OnScrollListener.SCROLL_STATE_FLING; //屏幕处于甩动状态
//        // OnScrollListener.SCROLL_STATE_IDLE; //停止滑动状态
//        // OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;// 手指接触状态
//        if (state == SCROLL_STATE_IDLE) {
//
//            // 整个滚动事件结束,只触发一次            顺序: 4
//            Log.e("123", 3 + "");
//            LinearLayoutManager manager = ((LinearLayoutManager) getLayoutManager());
//
//            int firstPosition = manager.findFirstVisibleItemPosition();
//            int completelyPosition = manager.findFirstCompletelyVisibleItemPosition();
//            int lastPosition = manager.findFirstVisibleItemPosition();
//
//            if (firstPosition != completelyPosition) {
//                smoothScrollToPosition(firstPosition);
//            }
//        }
//
//    }
}



recyclerview_slide_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center">

     <com.example.qxb_810.wigdets.SlideItemView
         android:id="@+id/siv_item"
         android:layout_width="match_parent"
         android:layout_height="100dp"
         android:background="@color/menuItem"
         android:scrollbars="none">
     </com.example.qxb_810.wigdets.SlideItemView>
</LinearLayout>

sideslip_item_content.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"

    android:gravity="center"
    android:background="@color/menuHead">
    <TextView

        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="content"
        android:textSize="50dp"
        />
</LinearLayout>

sideslip_item_edit.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_edit"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:textSize="30dp"
        android:text="编辑"
        android:background="@color/edit"
        android:layout_weight="1"
        android:gravity="center"
        android:padding="10dp" />
    <TextView
        android:id="@+id/tv_delete"
        android:layout_width="80dp"
        android:layout_height="match_parent"
        android:textSize="30dp"
        android:background="@color/delete"
        android:padding="10dp"
        android:gravity="center"
        android:layout_weight="1"
        android:text="删除"/>
</LinearLayout>

MyLinearSnapHelper.java
这个类是拷贝大神的 。具体博客地址: https://blog.csdn.net/li15225271052/article/details/54747359
主要是用来在RecyclerView滑动结束时第一个item完整显示

package com.example.qxb_810.wigdets;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

public class MyLinearSnapHelper extends LinearSnapHelper {

    private OrientationHelper mVerticalHelper, mHorizontalHelper;

    public MyLinearSnapHelper() {
    }

    //这个是放入的被设置的recycleview'的
    @Override
    public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
            throws IllegalStateException {
        super.attachToRecyclerView(recyclerView);
    }

    /**
     * 计算到targetView要移动的距离  这个也是关键  这个方法是在我们判断处理之后(也就是findSnapView方法)  所以distanceToStart的方法中直接计算此时recycleview的离最屏幕开始处的距离减去内边距就是需要移动的距离
     */
    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                              @NonNull View targetView) {
        //这里是个数组0返回的是横向的距离  1返回的是纵向的距离  默认的话你将每次都返回到第一个view
        int[] out = new int[2];
        //这里判断你设置的recycleview的布局管理器方向是横向还是纵向
        if (layoutManager.canScrollHorizontally()) {
            //然后将距离开始的距离存放在内
            out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }

        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }
    //这里是获取开始显示的view方法
    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {

        if (layoutManager instanceof LinearLayoutManager) {

            if (layoutManager.canScrollHorizontally()) {
                return getStartView(layoutManager, getHorizontalHelper(layoutManager));
            } else {
                return getStartView(layoutManager, getVerticalHelper(layoutManager));
            }
        }

        return super.findSnapView(layoutManager);
    }

    //这里是 计算距离开始的距离   由于calculateDistanceToFinalSnap的方法是在findSnapView之后所以这里计算的是recycleview的离最屏幕开始处的距离减去内边距就是需要移动的距离
    private int distanceToStart(View targetView, OrientationHelper helper) {
        Log.i("StartSnapHelper", "disanceToStart: "+helper.getDecoratedStart(targetView)+"========="+helper.getStartAfterPadding());
        return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();
    }
    //这里是我们自定义的右对齐的显示规则  也就是这里就是我实现自动最左对齐的关键  当然如果有其他的奇葩右对齐 --  (什么奇葩客户没见过)实现原理一样的
    private View getStartView(RecyclerView.LayoutManager layoutManager,
                              OrientationHelper helper) {
        //这里返回null表示不做任何操作保持当前的位置

        if (layoutManager instanceof LinearLayoutManager) {
            Log.i("StartSnapHelper", "getStartView: "+1);
            int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();

            boolean isLastItem = ((LinearLayoutManager) layoutManager)
                    .findLastCompletelyVisibleItemPosition()
                    == layoutManager.getItemCount() - 1;
            //当发现显示了最后一个item的时候不执行任何操作
            if (firstChild == RecyclerView.NO_POSITION || isLastItem) {
                Log.i("StartSnapHelper", "getStartView: "+2);
                return null;
            }

            View child = layoutManager.findViewByPosition(firstChild);
            Log.i("StartSnapHelper11", "getStartView: "+helper.getDecoratedEnd(child)+ "------------"+helper.getDecoratedMeasurement(child));
            //当发现当前第一个view最后到结束位置到屏幕距离大于item的一半的时候自动复位  或者用getDecoratedStart()判断条件反过来而已
            if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2
                    && helper.getDecoratedEnd(child) > 0) {
                Log.i("StartSnapHelper", "getStartView: "+3);
                return child;
            } else {
                //如果发现最后停留的位置小于item自身的一半  则再次判断是不是最后一个view 如果是将不执行任何操作
                if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()
                        == layoutManager.getItemCount() - 1) {
                    Log.i("StartSnapHelper", "getStartView: "+4);
                    return null;
                } else {
                    //如果不是返回下一个view
                    Log.i("StartSnapHelper", "getStartView: "+5);
                    return layoutManager.findViewByPosition(firstChild + 1);
                }
            }
        }

        return super.findSnapView(layoutManager);
    }

    private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mVerticalHelper == null) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
        }
        return mVerticalHelper;
    }

    private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        return mHorizontalHelper;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值