博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/smile_running

系列文章:

自定义 View(一)仿 QQ 列表 Item 侧拉删除功能

自定义 View(二)自己动手实现下拉刷新、上拉加载功能

自定义 View(三)仿 DrawerLayout 实现侧拉功能

分享我学习过的一个ListView侧滑的Item菜单效果,所谓举一反三,一同百通。在我学会了这个之后,很容易的就实现ListView的上拉加载和下拉刷新的效果,还有我们最常见的侧滑抽屉效果。百变不离其中,只要我们搞懂其中的一个实现方式,那么其他的便信手拈来。

目的是为了,解决ListView与侧滑删除按钮的滑动冲突

我要实现的是这样一个效果,QQ的联系人侧滑菜单,而在没学过自定义View之前毫无头绪。既然这样,我先来看一下实现后的效果吧。

完成的效果图
  • Item布局

现在呢,我们得有一个这样的思路。所谓ListView中Item布局,我们一般是这个样子的:一个父容器,里面一个TextView,ImageView等等内容。比如:

<listview.example.x.slidelistview.SlideLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="100dp"android:orientation="horizontal"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:padding="2dp"><de.hdodenhof.circleimageview.CircleImageViewandroid:id="@+id/item_image"android:layout_width="100dp"android:layout_height="match_parent"android:padding="4dp" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/item_name"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:gravity="center_vertical"android:paddingLeft="8dp"android:textColor="#323232"android:textSize="25sp" /><TextViewandroid:id="@+id/item_message"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:gravity="center_vertical"android:paddingLeft="8dp"android:textColor="#cfcfcf"android:textSize="18sp" /></LinearLayout></LinearLayout><LinearLayoutandroid:layout_width="200dp"android:layout_height="match_parent"android:orientation="horizontal"><TextViewandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@android:color/darker_gray"android:gravity="center"android:text="置顶"android:textColor="@android:color/white"android:textSize="22sp" /><TextViewandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@android:color/holo_red_light"android:gravity="center"android:text="删除"android:textColor="@android:color/white"android:textSize="22sp" /></LinearLayout>
</listview.example.x.slidelistview.SlideLayout>

以上的ListView的Item布局,也就是本次我们使用的。

既然,你要学习侧滑的实现效果,别告诉我,你还不会用ListView。当然也是有可能的,那么在这里给一篇ListView的使用姿势文章,不会的请点击这里:ListView使用技巧、优化和用法拓展,掌握ListView

  • 自定义存放Item父容器

首先,我们存放Item的父容器,这个父容器是有文章的。怎么说呢,大家可以看到上面布局代码中的父容器,其实这就是我自定义的一个继承FrameLayout的类,当然也可以是RelativeLayout。但是别用LinerLayout,因为我们要对子视图进行排列位置,用LinerLayout反正适得其反。

有了这个前提,我们的思路就是:通过onLayout()、onMesure()方法对子视图的测量宽高以及布置它的位置。我们的继承自FrameLayout的类SlideLayout,其中存放两个父容器。一个是ContentView,一个是MenuView。因为MenuView是从右侧滑进来的,我们应该将它布置到屏幕以外,通过滑动显示和隐藏。

我特地画了一张草图,以便更好的理解吧,也许只有我才能看懂~哈哈哈哈

menuView的逻辑理解图,从隐藏到显示整个过程
​​​​​​

好好理解一下吧,好记性不过烂笔头,在本子上多涂涂画画,或者开启画板自己画一画,才能更好的理解。

那么,我做这些有什么用呢?当然了,请看效果图:

  • 效果图

实现了,但是有明显的bug

我们来看看实现的代码类:

/*** @Created by xww.* @Creation time 2018/8/21.*/public class SlideLayout extends FrameLayout {private View mContentView;private View mMenuView;private int mMenuWidth;private int mMenuHeight;private int mContentWidth;private int mContentHeight;private Scroller mScroller;private float startX;private float startY;public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);mScroller = new Scroller(context);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();mContentView = getChildAt(0);mMenuView = getChildAt(1);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mContentWidth = getMeasuredWidth();mContentHeight = getMeasuredHeight();mMenuWidth = mMenuView.getMeasuredWidth();mMenuHeight = mMenuView.getMeasuredHeight();}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);//将menu布局到右侧不可见(屏幕外)mMenuView.layout(mContentWidth, 0, mContentWidth + mMenuWidth, mMenuHeight);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);}@Overridepublic boolean onTouchEvent(MotionEvent event) {final float x = event.getX();final float y = event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:startX = x;startY = y;break;case MotionEvent.ACTION_MOVE:final float dx = (int) (x - startX);final float dy = (int) (startY - y);int disX = (int) (getScrollX() - dx);if (disX <= 0) {disX = 0;} else if (disX >= mMenuWidth) {disX = mMenuWidth;}scrollTo(disX, getScrollY());startX = x;startY = y;break;case MotionEvent.ACTION_UP:if (getScrollX() < mMenuWidth / 2) {closeMenu();} else {openMenu();}break;}return true;}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return super.onInterceptTouchEvent(ev);}@Overridepublic void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());invalidate();}}public final void openMenu() {mScroller.startScroll(getScrollX(), getScrollY(), mMenuWidth - getScrollX(), 0);invalidate();}public final void closeMenu() {mScroller.startScroll(getScrollX(), getScrollY(), 0 - getScrollX(), 0);invalidate();}
}
  • Bug分析

以上只是简单的实现了而且,但是明显有很多bug,比如每个都可以滑出来、滑到一半卡主、与ListView滑动冲突了等等,下面我们一点一点来解决它,为了更好的体验。

·一、解决滑动冲突

大家可能有疑惑,这个滑动冲突是怎么产生的呢?下面,我们来看一下滑动冲突的结果,你就会明白它产生的大致原因了

滑动冲突了

分析一下,我们SlideLayout有左右滑动的动作,ListView有上下滑动的动作。当我在左右滑动的时候不松开而进行上下滑动,那么滑动事件便由SlideLayout传递到了ListView的滑动。可想而知,SlideLayout的滑动事件被父视图给夺走了,所以SlideLayout就犯毛病了,停止了它的滑动行为。

既然是ListView夺走了touch事件,从ListView角度上来说,我(ListView)拦截了SlideLayout的touch事件;从SlideLayout的角度上来说,父(ListView)把我的touch事件给夺走了。

    那么有两种相对的角度,就有两种解决的方法。第一种是继承ListView,设置不拦截;第二种是剥夺ListView对touch的处理权。我就用第二种来实现,既然你可以夺走我的,那么我就可以拿回来。所谓相生相克,一物降一物。

我们思路应该这样,比如手指在左右滑动的距离大于上下滑动的距离,那么判定是SlideLayout的滑动事件,在这种情况下才去剥夺ListView的事件处理器。

看一下我们实现的代码:在onTouchEvent();的ACTION_MOVE事件中修改代码,添加如下部分

            case MotionEvent.ACTION_MOVE:final float dx = (int) (x - startX);final float dy = (int) (startY - y);int disX = (int) (getScrollX() - dx);if (disX <= 0) {disX = 0;} else if (disX >= mMenuWidth) {disX = mMenuWidth;}scrollTo(disX, getScrollY());final float moveX = Math.abs(x - downX);final float moveY = Math.abs(y - downY);if (moveX > moveY && moveX > 10f) {//剥夺ListView对touch事件的处理权getParent().requestDisallowInterceptTouchEvent(true);}startX = x;startY = y;break;

通过以上的修改,我们达到了目的,看看效果吧。

解决滑动冲突bug

二、解决Item点击事件的冲突

我们的Item内可能是有点击事件的,所以呢,又掉入了一个坑。为什么这样说呢?看下面的图你就明白一切了。我给名字设置了点击事件,发生了什么?

在姓名区域无法滑动,其他区域可以滑动

分析一下,产生这种异常的原因。为什么在姓名那里却无法移动呢?因为什么呢?我们就知道了onClickListener其实把touch事件给消费了,这就导致了SlideLayout始终处理不了touch事件,因为被Item中的子元素TextView给消费了。

如果不太懂,你也许可以先看这样的一个简单例子:理解View的事件分发、拦截和消费,处理事件冲突的必备技能

    既然,TextView是它(SlideLayout)的儿子,作为父亲,那肯定要管管啊,不能让儿子乱来吧。那么,下面我们就管一管,怎么管呢?当然是拦截了,父亲把touch事件给拦截下来。但是,总不能所有情况都拦截吧,那儿子不就废了吗?所以呢,我们给定这样一个条件,就是当手指确实在左右滑动,而非单纯的点击时,这时父亲才拦截儿子的touch事件。也许有点难以想象,不急,我们看一下代码就简单多了。

    @Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {boolean intercept = false;final float x = event.getX();final float y = event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:downX =  x;downY = y;break;case MotionEvent.ACTION_MOVE:final float moveX = Math.abs(x - downX);if (moveX > 10f) {//对儿子touch事件进行拦截intercept = true;}break;case MotionEvent.ACTION_UP:break;}return intercept;}

通过这样,我们的儿子就听话多了,那么来看看是这样的一种效果。

解决点击事件与滑动的bug

三、限制只能有一个menu被打开

这一路,我的bug层出不穷,解决了这个又出了新的bug,真的是一步一个坑。当然,这是幸运的事情,当你爬完了这些坑之后,给你带来的确实另一番天地。不禁扯了一下,言归正传,我们看看出现的Bug情况吧。

多个Item可以被拉出

我们来分析一下出现这种情况的根本原因,那就是每个Item都是ListView中独立的一个个体,我们的Count数多少也就是Item数的多少,所以我们要对每个Item的侧滑Menu进行监听,我们自定义一个接口用于保存每一个Item的状态,有两种:Open和Close。但我们还需要一个点击的监听,用于判断是否与当前Item一致。

这部分就比较简单了,也好理解,简单的看一下代码吧。首先,在SlideLayout中定义一个监听接口

    private onSlideChangeListenr onSlideChangeListenr;public interface onSlideChangeListenr {void onMenuOpen(SlideLayout slideLayout);void onMenuClose(SlideLayout slideLayout);void onClick(SlideLayout slideLayout);}public void setOnSlideChangeListenr(SlideLayout.onSlideChangeListenr onSlideChangeListenr) {this.onSlideChangeListenr = onSlideChangeListenr;}

其次,在三个地方分别设置监听

    @Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {boolean intercept = false;final float x = event.getX();final float y = event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:downX = x;downY = y;if (onSlideChangeListenr != null) {onSlideChangeListenr.onClick(this);}break;case MotionEvent.ACTION_MOVE:final float moveX = Math.abs(x - downX);if (moveX > 10f) {//对儿子touch事件进行拦截intercept = true;}break;case MotionEvent.ACTION_UP:break;}return intercept;}public final void openMenu() {mScroller.startScroll(getScrollX(), getScrollY(), mMenuWidth - getScrollX(), 0);invalidate();if (onSlideChangeListenr != null) {onSlideChangeListenr.onMenuOpen(this);}}public final void closeMenu() {mScroller.startScroll(getScrollX(), getScrollY(), 0 - getScrollX(), 0);invalidate();if (onSlideChangeListenr != null) {onSlideChangeListenr.onMenuClose(this);}}

最后,在我们适配器中设置监听状态的改变做出相应的处理。

        mSlideLayout = (SlideLayout) convertView;mSlideLayout.setOnSlideChangeListenr(new SlideLayout.onSlideChangeListenr() {@Overridepublic void onMenuOpen(SlideLayout slideLayout) {mSlideLayout = slideLayout;}@Overridepublic void onMenuClose(SlideLayout slideLayout) {if (mSlideLayout != null) {mSlideLayout = null;}}@Overridepublic void onClick(SlideLayout slideLayout) {if (mSlideLayout != null ) {mSlideLayout.closeMenu();}}});

接下来,我们运行一下项目,成功的解决了问题。

到此,一个仿QQ侧滑菜单的效果完全的实现了,是不是很赞呢?哈哈哈哈。你以为就结束了吗?开玩笑。。。哈哈哈哈哈哈哈!

自定义 View(一)仿 QQ 列表 Item 侧拉删除功能相关推荐

  1. Android自定义View之仿QQ运动步数进度效果

    文章目录 前言 先看效果图 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6e4ddec17933496ea4830fa08d8ffbe5.png?x-oss-pr ...

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

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

  3. 自定义View之仿QQ运动步数进度效果

    前言 今天接着上一篇来写关于自定义View方面的东西,我是近期在学习整理这方面的知识点,所以把相关的笔记都放到这个Android自定义View的专栏里了,方便自己下次忘记的时候能回来翻翻,今天的内容是 ...

  4. android 自定义view实现仿QQ运动步数进度效果

    最近公司在策划一个新的项目,原型还没出来,再说这公司人都要走没了,估计又要找工作了,所以必须要学习,争取每个写个关于自定义view方面的,这样几个月积累下来,也能学习到东西,今天就带来简单的效果,就是 ...

  5. 自定义 View 之仿 QQ 步数变动动画效果

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  6. Android自定义View实现仿QQ实现运动步数效果

    效果图: 1.attrs.xml中 <declare-styleable name="QQStepView"><attr name="outerColo ...

  7. 04.自定义View(SlidingView仿QQ侧滑)

    感谢红橙Darren博主 布局文件中 <?xml version="1.0" encoding="utf-8"?> <LinearLayout ...

  8. Android仿qq聊天记录长按删除功能效果

    最近项目在做IM即时通讯开发,在删除聊天列表的时候跟删除聊天详细信息的时候,产品经理想要跟ios一样,在当前选中行上方弹出一个删除窗口.于是先从网上找demo,找了一个发现是Dialog做的,我感觉没 ...

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

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

最新文章

  1. 卷积神经网络--CNN
  2. 二分图 ---- 树的二分图性质 2020icpc 济南 J Tree Constructer(构造)
  3. 太多人关注,太少人理解,这就是“量子计算”
  4. 各代程序设计语言拓扑
  5. 汇编语言典型例子详解_数据分析常用的7大思维方法详解
  6. Mysql5.6.21源码安装
  7. 力扣53. 最大子序和
  8. 最小生成树(模板 prim)
  9. 【渝粤教育】电大中专职业生涯规划作业 题库
  10. Mac 电脑如何对文件进行批量重命名?
  11. 3至六年级计算机知识,小学三至六年级下册信息技术教学计划范文
  12. XCT学习笔记_X射线
  13. Android 计算网络速度文件下载剩余时间<<最优方案>>
  14. 纯php实现中秋博饼游戏(1):绘制骰子图案
  15. windows-提权常用技巧总结
  16. tomcat安全加固手册
  17. 软件工程(第三版) 期末复习
  18. 2021哈工大深入理解计算机系统Lab5(linklab)
  19. 上映半个月,微博热搜近10次,长歌行的魅力到底在哪 ?
  20. HEVC官方软件HM源代码简单分析-解码器TAppDecoder

热门文章

  1. SOLIDWORKS-D:\SW\SolidWorks_Flexnet_Server/启动server_install
  2. 简单的二维码生成接口,自动生成二维码,返回图片地址
  3. OTSU图像分割算法(python实现)
  4. BRAIN脑电研究:使用快速球方法评估阿尔茨海默病识别记忆
  5. selenium之使用xlrd模块读取ecel文件,使用pytest参数化实现DDT
  6. 找不到web的服务器ip地址,请进来看看,web服务器不能绑定ip地址的问题。谢谢!...
  7. 5911. 【NOIP2018模拟10.18】Travel
  8. 一分钟入门Java面向对象 为什么要创建对象?
  9. 回溯法求解旅行商问题
  10. 高中数学必修2知识点总结:第四章圆与方程