目    录(本篇字数:3000)

介绍

Item布局

自定义存放Item父容器

Bug分析

·一、解决滑动冲突

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

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

博文续篇

ListView添加下拉刷新、上拉加载,其实很简单


  • 介绍

今天,我来分享我学习过的一个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到底做了什么事情,可以看看郭霖大神的文章Android事件分发机制完全解析,带你从源码的角度彻底理解(上)。那我们就知道了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侧滑菜单的效果完全的实现了,是不是很赞呢?哈哈哈哈。你以为就结束了吗?开玩笑。。。哈哈哈哈哈哈哈!

博文续篇

ListView添加下拉刷新、上拉加载,其实很简单

©原文链接:https://blog.csdn.net/smile_Running/article/details/81916502

@作者博客:_Xu2WeI

@更多博文:查看作者的更多博文

转载于:https://www.cnblogs.com/xww0826/p/10359495.html

Android高仿QQ消息列表、侧拉删除菜单按钮效果相关推荐

  1. Android开发之高仿QQ消息侧拉删除

    Android开发之高仿QQ消息侧拉删除 QQ消息的侧滑删除效果之炫酷,想必大家都见过吧,本人作为一名安卓开发人员,遇到如此炫酷的效果,怎能不研究一番呢,现本人已实现其基本功能,现将代码贴出,望各位大 ...

  2. Android开发学习之仿手机QQ消息列表侧滑删除效果

    今天想和大家分享的是手机QQ消息列表侧滑删除效果,这种效果在IOS中被封装为一个列表控件,而手机QQ则是将这个功能移植到了Android上,换言之,这并非是手机QQ的独创.尽管如此,用户体验依然得到了 ...

  3. Android 编程下代码之(QQ消息列表滑动删除)

       这份代码写出来有些时候了,一直没共享,现在把它共享给大家.简单列一下代码中你可以学到的知识点: 自定义控件的实现方式: 事件的拦截分发消费机制: QQ会话列表滑动删除原理: 最后附上源码链接:Q ...

  4. android qq底部图片选择器,Android 高仿QQ图片选择器

    当做一款APP,需要选择本地图片时,首先考虑的无疑是系统相册,但是Android手机五花八门,再者手机像素的提升,大图无法返回等异常因数,导致适配机型比较困难,微信.QQ都相继的在自己的APP里集成了 ...

  5. android qq红点,Android高仿QQ小红点功能

    先给大家展示下效果图: 绘制贝塞尔曲线: 主要是当在一定范围内拖拽时算出固定圆和拖拽圆的外切直线以及对应的切点,就可以通过path.quadTo()来绘制二阶贝塞尔曲线了~ 整体思路: 1.当小红点静 ...

  6. android+qq底部界面,Android 高仿QQ 界面滑动效果

    Android高仿QQ界面滑动效果 点击或者滑动切换画面,用ViewPager实现, 首先是布局文件: android:layout_width="match_parent" an ...

  7. android 仿qq好友动态,Android UI仿QQ好友列表分组悬浮效果

    本文实例为大家分享了Android UI仿QQ好友列表分组悬浮效果的具体代码,供大家参考,具体内容如下 楼主是在平板上測试的.图片略微有点大,大家看看效果就好 接下来贴源代码: PinnedHeade ...

  8. Android 高仿QQ5.2双向側滑菜单DrawerLayout实现源代码

    Android 高仿QQ5.2双向側滑菜单DrawerLayout实现源代码 左右側滑效果图 1.主页的实现 直接将DrawerLayout作为根布局,然后其内部第一个View为内容区域,第二个Vie ...

  9. Android高仿QQ及微信底部菜单的多种实现方式【附源码地址】

    第一种方式:侧滑菜单+底部导航,已经实现聊天,表情,图片,位置,语音等信息的发送. 看效果: 下载地址:https://github.com/HuTianQi/QQ 第二种方式:Fragment+Po ...

最新文章

  1. php安装问题_PHP安装十大经典问题
  2. Transformer t2t vit
  3. 分区报无效的参数_西门子70系列变频器55KW上电就报F002故障维修
  4. Spring IoC 源码导读
  5. libevent源码学习-----阅读心得
  6. 吓人!普京最新Deepfake视频来了,MIT现场伪造实时采访
  7. nssstring 转换大小写
  8. beanutils获取带参数get方法
  9. 《Scikit-Learn与TensorFlow机器学习实用指南》第16章 强化学习
  10. 调研3家学校,分析10万数据,发现有了大数据再也不用“清考”
  11. java batik 字体文件_用 Apache batik 1.10 把svg代码转成png图片,文字丢失???
  12. 猿创征文|国产数据库之OceanBase详解安装和使用
  13. Typora的历史版本下载地址
  14. 使用Excel和Tableau分析淘宝母婴产品上新策略
  15. [转]拍照怎么搜题?(上)
  16. web service方法进行全文检索_软件架构分层方法论
  17. 实时翻译器-实时自动翻译器
  18. 操作系统学习——分时操作系统
  19. Strusts2简单入门教程
  20. java中的<<符号是什么意思

热门文章

  1. 免费ARP(Gratuitousnbsp;ARP)简析
  2. 灌溉控制器 节水灌溉自动控制器
  3. Tomcat配置环境搭建
  4. linux下使用daemontools的supervise让不稳定程序死掉自动马上重启
  5. 最详细的MYSQL学习视频
  6. Apollo自动驾驶入门-地图、定位、感知、预测、规划、控制
  7. 独家 | 跨链通信:区块链技术发展的新趋势
  8. 物联网项目开发必须要注意的几点
  9. 第六大晶圆代工厂商2021净利润大增593.3%
  10. CloudSim云计算仿真平台软件