先上效果图:

GIF图有点模糊,源码已上传Github:Android仿QQ侧滑菜单

####整体思路:

自定义ItemView的根布局(SwipeMenuLayout extends LinearLayout),复写onTouchEvent来处理滑动事件,注意这里的滑动是View里面内容的滑动而不是View的滑动,View里内容的滑动主要是通过scrollTo、scrollBy来实现,然后自定义SwipeRecycleView,复写其中的onInterceptTouchEvent和onTouchEvent来处理滑动冲突。

####实现过程:

先来看每个ItemView的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<org.ninetripods.mq.study.recycle.swipe_menu.SwipeMenuLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/swipe_menu"android:layout_width="match_parent"android:layout_height="70dp"android:layout_centerInParent="true"android:background="@color/white"android:orientation="horizontal"app:content_id="@+id/ll_layout"app:right_id="@+id/ll_right_menu"><LinearLayoutandroid:id="@+id/ll_layout"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"><TextViewandroid:id="@+id/tv_content"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginLeft="20dp"android:gravity="center_vertical"android:text="HelloWorld"android:textSize="16sp" /><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="right"android:layout_marginLeft="20dp"android:layout_marginRight="20dp"android:gravity="center_vertical|end"android:text="左滑←←←"android:textSize="16sp" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_right_menu"android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"><TextViewandroid:id="@+id/tv_to_top"android:layout_width="90dp"android:layout_height="match_parent"android:background="@color/gray_holo_light"android:gravity="center"android:text="置顶"android:textColor="@color/white"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_to_unread"android:layout_width="90dp"android:layout_height="match_parent"android:background="@color/yellow"android:gravity="center"android:text="标为未读"android:textColor="@color/white"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_to_delete"android:layout_width="90dp"android:layout_height="match_parent"android:background="@color/red_f"android:gravity="center"android:text="删除"android:textColor="@color/white"android:textSize="16sp" /></LinearLayout>
</org.ninetripods.mq.study.recycle.swipe_menu.SwipeMenuLayout>
复制代码

android:id="@+id/ll_layout" 的LinearLayout宽度设置的match_parent,所以右边的三个菜单按钮默认我们是看不到的,根布局是SwipeMenuLayout,是个自定义ViewGroup,主要的滑动事件也是在这里面完成的。

RecycleView的布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><includeandroid:id="@+id/toolbar"layout="@layout/m_toolbar" /><org.ninetripods.mq.study.recycle.swipe_menu.SwipeRecycleViewandroid:id="@+id/swipe_recycleview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_below="@id/toolbar" />
</RelativeLayout>
复制代码

我们用到的SwipeRecycleView也是自定义RecycleView,主要是处理一些和SwipeMenuLayout的滑动冲突。

######先分析SwipeMenuLayout代码:

public static final int STATE_CLOSED = 0;//关闭状态
public static final int STATE_OPEN = 1;//打开状态
public static final int STATE_MOVING_LEFT = 2;//左滑将要打开状态
public static final int STATE_MOVING_RIGHT = 3;//右滑将要关闭状态
复制代码

首先定义了SwipeMenuLayout的四种状态: STATE_CLOSED 关闭状态 STATE_OPEN 打开状态 STATE_MOVING_LEFT 左滑将要打开状态 STATE_MOVING_RIGHT 右滑将要关闭状态

接着通过自定义属性来获得右侧菜单根布局的id,然后通过findViewById()来得到根布局的View,进而获得其宽度值。

//获取右边菜单id
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwipeMenuLayout);
mRightId = typedArray.getResourceId(R.styleable.SwipeMenuLayout_right_id, 0);
typedArray.recycle();
复制代码

相应的attr.xml文件:

<declare-styleable name="SwipeMenuLayout"><!-- format="reference"意为参考某一资源ID --><attr name="content_id" format="reference" /><attr name="right_id" format="reference" /></declare-styleable>
复制代码
@Overrideprotected void onFinishInflate() {super.onFinishInflate();if (mRightId != 0) {rightMenuView = findViewById(mRightId);}}
复制代码

接着来看onTouchEvent,先看ACTION_DOWN事件和ACTION_MOVE事件:

@Override
public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = (int) event.getX();mDownY = (int) event.getY();mLastX = (int) event.getX();break;case MotionEvent.ACTION_MOVE:int dx = (int) (mDownX - event.getX());int dy = (int) (mDownY - event.getY());//如果Y轴偏移量大于X轴偏移量 不再滑动if (Math.abs(dy) > Math.abs(dx)) return false;int deltaX = (int) (mLastX - event.getX());if (deltaX > 0) {//向左滑动currentState = STATE_MOVING_LEFT;if (deltaX >= menuWidth || getScrollX() + deltaX >= menuWidth) {//右边缘检测scrollTo(menuWidth, 0);currentState = STATE_OPEN;break;}} else if (deltaX < 0) {//向右滑动currentState = STATE_MOVING_RIGHT;if (deltaX + getScrollX() <= 0) {//左边缘检测scrollTo(0, 0);currentState = STATE_CLOSED;break;}}scrollBy(deltaX, 0);mLastX = (int) event.getX();break;}return super.onTouchEvent(event);
}
复制代码

在ACTION_MOVE事件中通过点击所在坐标和上一次滑动记录的坐标之差来判断左右滑动,并进行左边缘和右边缘检测,如果还未到左右内容的边界,则通过scrollBy来实现滑动。 接着看ACTION_UP和ACTION_CANCEL事件:

        case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (currentState == STATE_MOVING_LEFT) {//左滑打开mScroller.startScroll(getScrollX(), 0, menuWidth - getScrollX(), 0, 300);invalidate();} else if (currentState == STATE_MOVING_RIGHT || currentState == STATE_OPEN) {//右滑关闭smoothToCloseMenu();}//如果小于滑动距离并且菜单是关闭状态 此时Item可以有点击事件int deltx = (int) (mDownX - event.getX());return !(Math.abs(deltx) < mScaledTouchSlop && isMenuClosed()) || super.onTouchEvent(event);}return super.onTouchEvent(event);
复制代码

这里主要是当松开手时执行ACTION_UP事件,如果不处理,则会变成菜单显示一部分然后卡在那里了,这当然是不行的,这里通过OverScroller.startScroll()来实现惯性滑动,然而当我们调用startScroll()之后还是不会实现惯性滑动的,这里还需要调用invalidate()去重绘,重绘时会执行computeScroll()方法:

@Override
public void computeScroll() {if (mScroller.computeScrollOffset()) {// Get current x and y positionsint currX = mScroller.getCurrX();int currY = mScroller.getCurrY();scrollTo(currX, currY);postInvalidate();}if (isMenuOpen()) {currentState = STATE_OPEN;} else if (isMenuClosed()) {currentState = STATE_CLOSED;}
}
复制代码

在computeScroll()方法中,我们通过Scroller.getCurrX()和scrollTo()来滑动到指定坐标位置,然后调用postInvalidate()又去重绘,不断循环,直到滑动到边界为止。

######再分析下SwipeRecycleView:

SwipeRecycleView是SwipeMenuLayout的父View,事件分发时,先到达的SwipeRecycleView,

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {boolean isIntercepted = super.onInterceptTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mLastX = (int) event.getX();mLastY = (int) event.getY();mDownX = (int) event.getX();mDownY = (int) event.getY();isIntercepted = false;//根据MotionEvent的X Y值得到子ViewView view = findChildViewUnder(mLastX, mLastY);if (view == null) return false;//点击的子View所在的位置final int touchPos = getChildAdapterPosition(view);if (touchPos != mLastTouchPosition && mLastMenuLayout != null&& mLastMenuLayout.currentState != SwipeMenuLayout.STATE_CLOSED) {if (mLastMenuLayout.isMenuOpen()) {//如果之前的菜单栏处于打开状态,则关闭它mLastMenuLayout.smoothToCloseMenu();}isIntercepted = true;} else {//根据点击位置获得相应的子ViewViewHolder holder = findViewHolderForAdapterPosition(touchPos);if (holder != null) {View childView = holder.itemView;if (childView != null && childView instanceof SwipeMenuLayout) {mLastMenuLayout = (SwipeMenuLayout) childView;mLastTouchPosition = touchPos;}}}break;case MotionEvent.ACTION_MOVE:case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:int dx = (int) (mDownX - event.getX());int dy = (int) (mDownY - event.getY());if (Math.abs(dx) > mScaleTouchSlop && Math.abs(dx) > Math.abs(dy)|| (mLastMenuLayout != null && mLastMenuLayout.currentState != SwipeMenuLayout.STATE_CLOSED)) {//如果X轴偏移量大于Y轴偏移量 或者上一个打开的菜单还没有关闭 则禁止RecycleView滑动 RecycleView不去拦截事件return false;}break;}return isIntercepted;
}
复制代码

通过findChildViewUnder()找到ItemView,进而通过getChildAdapterPosition(view)来获得点击位置,如果是第一次点击,则会通过findViewHolderForAdapterPosition()找到对应的ViewHolder 并获得子View;如果不是第一次点击,和上次点击不是同一个item并且前一个ItemView的菜单处于打开状态,那么此时调用smoothToCloseMenu()关闭菜单。在ACTION_MOVE、ACTION_UP、ACTION_CANCEL事件中,如果X轴偏移量大于Y轴偏移量 或者上一个打开的菜单还没有关闭 则禁止SwipeRecycleView滑动,SwipeRecycleView不去拦截事件,相应的将事件传到SwipeMenuLayout中去。

@Overridepublic boolean onTouchEvent(MotionEvent e) {switch (e.getAction()) {case MotionEvent.ACTION_DOWN://若某个Item的菜单还没有关闭,则RecycleView不能滑动if (!mLastMenuLayout.isMenuClosed()) {return false;}break;case MotionEvent.ACTION_MOVE:case MotionEvent.ACTION_UP:if (mLastMenuLayout != null && mLastMenuLayout.isMenuOpen()) {mLastMenuLayout.smoothToCloseMenu();}break;}return super.onTouchEvent(e);}
复制代码

在onTouchEvent的ACTION_DOWN事件中,如果某个Item的菜单还没有关闭,则SwipeRecycleView不能滑动,在ACTION_MOVE、ACTION_UP事件中,如果前一个ItemView的菜单是打开状态,则先关闭它。

####踩过的坑:

说起踩坑尼玛真是一把鼻涕一把泪,因为水平有限遇到了很多坑,当时要不是赶紧看了一下银行卡的余额不足,我差一点就把电脑砸了去买新的了~当时的心情是下面这样的:

1、当在某个ItemView (SwipeMenuLayout) 保持按下操作,然后手势从SwipeMenuLayout控件内部转移到外部,然后菜单滑到一半就卡在那里了,在那里卡住了~那里卡住了~卡住了~住了~了~,当时有点不知所措,后来通过Debug发现SwipeMenuLayout的ACTION_UP已经不会执行了,想想也是,你都滑动外面了,人家凭啥还执行ACTION_UP方法,后来通过google发现SwipeMenuLayout不执行ACTION_UP但是会执行ACTION_CANCEL,ACTION_CANCEL是当前滑动手势被打断时调用,比如在某个控件保持按下操作,然后手势从控件内部转移到外部,此时控件手势事件被打断,会触发ACTION_CANCEL,解决方法也就出来了,即ACTION_UP和ACTION_CANCEL都根据判断条件去执行惯性滑动的逻辑。

2、假如某个ItemView (SwipeMenuLayout) 的右侧菜单栏处于打开状态,此时去上下滑动SwipeRecycleView,发现菜单栏关闭了,但同时SwipeRecycleView也跟着上下滑动了,这里的解决方法是在SwipeRecycleView的onTouchEvent中去判断:

@Overridepublic boolean onTouchEvent(MotionEvent e) {switch (e.getAction()) {case MotionEvent.ACTION_DOWN://若某个Item的菜单还没有关闭,则RecycleView不能滑动if (!mLastMenuLayout.isMenuClosed()) {return false;}................省略其他..................}return super.onTouchEvent(e);}
复制代码

通过判断,若某个Item的菜单还没有关闭,直接返回false,那么SwipeRecycleView就不会再消费此次事件,即SwipeRecycleView不会上下滑动了。

####后记: 本文主要运用的是View滑动的相关知识,如scrollTo、scrollBy、OverScroller等,水平有限,如果发现文章有误,还请不吝赐教,不胜感激~最后再贴下源码地址: Android仿QQ侧滑菜单,如果对您有帮助,给个star吧,感谢老铁~

下一篇:Android高仿QQ小红点

转载于:https://juejin.im/post/5a33e7cbf265da43310de175

Android仿QQ侧滑菜单相关推荐

  1. Android 仿QQ侧滑菜单

    前言 集成方式 兼容超强的BaseRecyclerViewAdapterHelper 方法及属性介绍 THANKS 侧滑的雏形 测绘布局 onLayout onMeasure MotionEvent事 ...

  2. Android高仿QQ侧滑菜单

    文章目录 效果图 整体思路 实现过程 先分析SwipeMenuLayout 再分析下SwipeRecycleView 踩过的坑 后记 效果图 GIF图有点模糊,源码已上传Github:Android仿 ...

  3. 仿QQ侧滑菜单(二)

    在(一)https://blog.csdn.net/qq_36551426/article/details/80427352中讲了一下DrawerLayout的简单概念,但是这并不足以让我们去做一个完 ...

  4. iOS仿QQ侧滑菜单、登录按钮动画、仿斗鱼直播APP、城市选择器、自动布局等源码

    iOS精选源码 QQ侧滑菜单,右滑菜单,QQ展开菜单,QQ好友分组 image 登录按钮 image 3分钟快捷创建高性能轮播图 ScrollView嵌套ScrolloView(UITableView ...

  5. iOS仿QQ侧滑菜单、登录按钮动画、仿斗鱼直播APP、城市选择器、自动布局等源码...

    iOS精选源码 QQ侧滑菜单,右滑菜单,QQ展开菜单,QQ好友分组 登录按钮 3分钟快捷创建高性能轮播图 ScrollView嵌套ScrolloView(UITableView .UICollecti ...

  6. Android自定义View之仿QQ侧滑菜单实现

    最近,由于正在做的一个应用中要用到侧滑菜单,所以通过查资料看视频,学习了一下自定义View,实现一个类似于QQ的侧滑菜单,顺便还将其封装为自定义组件,可以实现类似QQ的侧滑菜单和抽屉式侧滑菜单两种菜单 ...

  7. 鹅厂系列一 : 仿QQ侧滑菜单

    --不会的东西你不尝试的去做,你永远都不会做 好了,跟随潮流,还是先看下效果,不然可能都没人想看下去了(不会看到效果后不想看了吧O(∩_∩)O~) 额,图片资源来自QQ_374.APK,里面四五千个图 ...

  8. 名片夹android布局代码,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分Android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: SlidingLayout继承于 HorizontalScrollView /** * Cr ...

  9. android仿qq布局,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: slidinglayout继承于 horizontalscrollview /** * cr ...

最新文章

  1. 【MATLAB】符号数学计算(四):符号表达式操作
  2. 初步了解学习将传统单机应用改造成Dubbo服务的过程
  3. SQL Server调优系列基础篇(联合运算符总结)
  4. 关于nodejs中npm命令没有反应的解决方法
  5. Ormlite数据库
  6. 微软提供的数据访问组件SqlHelper
  7. MySQL 开发实践
  8. C语言航空订票系统课程设计
  9. 笔记本电脑开机键盘失效
  10. 会计常用的Excel函数公式大全(共21个)
  11. 什么是线程安全?如何保证线程安全?
  12. html的strong标签是什么意思,Strong标签和B标签怎么用?区别有哪些
  13. 支付宝批量转帐工具使用说明书
  14. RTP协议解析及H264/H265 音视频RTP打包分析
  15. TCP粘包的原因及解决办法
  16. GDKOI-PJ-2021 Day1总结
  17. Android4.2 开发者选项在哪里 Developer options
  18. unity塔防游戏怪物转向_怪兽塔防手机版下载-怪兽塔防游戏下载v1.4 安卓版
  19. 四 基于TCP的服务器端/客户端
  20. 读文献 THE HSIC BOTTLENECK: DEEP LEARNING WITHOUT BACK-PROPAGATION 阅读心得

热门文章

  1. Linux TC(Traffic Control)框架原理解析
  2. 给UIButton添加背景图片
  3. CoreData多线程
  4. Hadoop 资源列表
  5. OpenGL中的着色模式GL_SMOOTH与GL_FLAT
  6. 实模式与保护模式详解二:地址映射
  7. python文件中内容转换为字典
  8. 侧记戴尔的第三代虚拟化价值观
  9. echo -e 参数
  10. linux下批量添加新用户