仿微信消息列表

2024-03-21 10:20
文章标签 列表 消息 仿微信

本文主要是介绍仿微信消息列表,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

仿微信消息列表

文章目录

  • 仿微信消息列表
  • 前言
  • 一、典型的事件类型
  • 二、Scroller
  • 三、View的滑动
    • 1.scrollTo/scrollBy
    • 2.修改布局参数
    • 3.动画
  • 四、使用步骤
    • 1.布局文件
    • 2.自定义View-ScrollerLinearLayout
  • 五、问题
  • 总结


前言

最近自己在利用空闲时间开发一个APP,目的是为了巩固所学的知识并扩展新知,加强对代码的理解扩展能力。消息模块是参照微信做的,一开始并没有准备做滑动删除的功能,觉得删除嘛,后面加个长按的监听不就行了,但是对于有些强迫症的我来说,是不大满意这种解决方法的,但由于我对自定义view的了解还是比较少,而且之前也没有做过,所以就作罢。上周看了任玉刚老师的《Android开发艺术探索》中的View事件体系章节,提起了兴趣,就想着试一试吧,反正弄不成功也没关系。最后弄成了,但还是有些小瑕疵(在五、问题中),希望大佬能够指教一二。话不多说,放上一张动图演示下:
在这里插入图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、典型的事件类型

在附上源码之前,想先向大家介绍下事件类型,在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种:

  • ACTION_DOWN ---- 手指刚接触屏幕
  • ACTION_MOVE ---- 手指在屏幕上移动
  • ACTION_UP ---- 手指刚离开屏幕

正常情况下、一次手指触摸屏幕的行为会触发一系列点击事件:

  • 点击屏幕后松开,事件序列为DOWN -> UP
  • 点击屏幕滑动后松开,事件序列为DOWN -> MOVE -> … -> MOVE -> UP

二、Scroller

Scroller - 弹性滑动对象,用于实现View的弹性滑动。
当使用View的scrollTo/scrollBy方法来实现滑动时,其过程是在瞬间完成的,这个过程没有过渡效果,用户体验感较差,这个时候就可以使用Scroller来实现有过渡效果的滑动,其过程不是瞬间完成的,而是在一定时间间隔内完成的。

三、View的滑动

Android手机由于屏幕较小,为了给用户呈现更多的内容,就需要使用滑动来显示和隐藏一些内容,不管滑动效果多么绚丽,它们都是由不同的滑动外加特效实现的。View的滑动可以通过三种方式实现:

  • scrollTo/scrollBy:操作简单,适合对View内容的滑动。
  • 修改布局参数:操作稍微复杂,适合有交互的View。
  • 动画:操作简单,适合没有交互的View和实现复杂的动画效果。

1.scrollTo/scrollBy

为了实现View的滑动,View提供了专门的方法来实现这一功能,也就是scrollTo/scrollBy。是基于所传参数的绝对滑动。

2.修改布局参数

即改变LayoutParams,比如想把一个布局向右平移100px,只需要将该布局LayoutParams中的marginLeft参数值增加100px即可。或者在该布局左边放入一个默认宽度为0px的空View,当需要向右平移时,重新设置空View的宽度就OK了,

3.动画

动画和Scroller一样具有过渡效果,View动画是对View的影像做操作,并不能真正改变View的位置,单击新位置无法触发onClick事件,在这篇文章中并没有使用到,所以不再赘叙了。

四、使用步骤

1.布局文件

<?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"xmlns:tools="http://schemas.android.com/tools"xmlns:widget="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><com.example.myapplication.view.ScrollerLinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><RelativeLayoutandroid:id="@+id/friend_item"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingHorizontal="16dp"android:paddingVertical="10dp"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><com.makeramen.roundedimageview.RoundedImageViewandroid:id="@+id/friend_icon"android:layout_width="45dp"android:layout_height="45dp"android:src="@mipmap/touxiang"app:riv_corner_radius="5dp" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center"android:layout_marginLeft="12dp"android:gravity="center_vertical"android:orientation="vertical"><TextViewandroid:id="@+id/friend_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:singleLine="true"android:textColor="@color/black"android:textSize="15dp"tools:text="好友名" /><TextViewandroid:id="@+id/friend_last_mess"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="3dp"android:layout_marginEnd="18dp"android:singleLine="true"android:textColor="@color/color_dbdbdb"android:textSize="12dp"tools:text="最后一条信息内容" /></LinearLayout></LinearLayout><TextViewandroid:id="@+id/last_mess_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"android:layout_marginTop="5dp"android:singleLine="true"android:textColor="@color/color_dbdbdb"android:textSize="11dp"tools:text="时间" /></RelativeLayout><LinearLayoutandroid:layout_width="240dp"android:layout_height="match_parent"android:orientation="horizontal"><Buttonandroid:id="@+id/unread_item"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"android:clickable="true"android:background="@color/color_theme"android:gravity="center"android:text="标为未读"android:textColor="@color/color_FFFFFF" /><Buttonandroid:id="@+id/top_item"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"android:clickable="true"android:background="@color/color_orange"android:gravity="center"android:text="置顶"android:textColor="@color/color_FFFFFF" /><Buttonandroid:id="@+id/delete_item"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"android:clickable="true"android:background="@color/color_red"android:gravity="center"android:text="删除"android:textColor="@color/color_FFFFFF" /></LinearLayout></com.example.myapplication.view.ScrollerLinearLayout><Viewandroid:layout_width="match_parent"android:layout_height="1px"android:layout_alignParentBottom="true"android:layout_marginLeft="60dp"android:layout_marginRight="3dp"android:background="@color/color_e7e7e7" /></LinearLayout>

提示:ScrollerLinearLayout布局最多包含两个子布局(默认是这样,后面可能还会修改成自定义),一个是展示在用户面前充满屏幕宽度的布局,一个是待展开的布局,在该xml布局中,ScrollerLinearLayout布局包含了一个RelativeLayout和一个LinearLayoutLinearLayout中包含了三个按钮,分别是删除、置顶、标为未读。

2.自定义View-ScrollerLinearLayout

代码如下:

/*** @Copyright : China Telecom Quantum Technology Co.,Ltd* @ProjectName : My Application* @Package : com.example.myapplication.view* @ClassName : ScrollerLinearLayout* @Description : 文件描述* @Author : yulu* @CreateDate : 2023/8/17 17:05* @UpdateUser : yulu* @UpdateDate : 2023/8/17 17:05* @UpdateRemark : 更新说明*/
class ScrollerLinearLayout @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) :LinearLayout(context, attrs, defStyleAttr) {private val mScroller = Scroller(context)  // 用于实现View的弹性滑动private val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlopprivate var mVelocityTracker: VelocityTracker? = null   // 速度追踪private var intercept = false   // 拦截状态 初始值为不拦截private var lastX: Float = 0fprivate var lastY: Float = 0f  // 用来记录手指按下的初始坐标var expandWidth = 720   // View待展开的布局宽度 需要手动设置 3*dpprivate var expandState = false   // View的展开状态private val displayWidth =context.applicationContext.resources.displayMetrics.widthPixels  // 屏幕宽度private var state = trueoverride fun onTouchEvent(event: MotionEvent): Boolean {Log.e(TAG, "onTouchEvent $event")when (event.action) {MotionEvent.ACTION_DOWN -> {if (!expandState) {state = false}}else -> {state = true}}return state}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "onInterceptTouchEvent Result : ${onInterceptTouchEvent(ev)}")Log.e(TAG, "dispatchTouchEvent : $ev")mVelocityTracker = VelocityTracker.obtain()mVelocityTracker!!.addMovement(ev)return super.dispatchTouchEvent(ev)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "onInterceptTouchEvent $ev")when (ev?.action) {MotionEvent.ACTION_DOWN -> {lastX = ev.rawXlastY = ev.rawY// 处于展开状态且点击的位置不在扩展布局中 拦截点击事件intercept = expandState && ev.x < (displayWidth - expandWidth)}MotionEvent.ACTION_MOVE -> {// 当滑动的距离超过10 拦截点击事件intercept = lastX - ev.x > 10moveWithFinger(ev)}MotionEvent.ACTION_UP -> {// 判断滑动距离是否超过布局的1/2chargeToRightPlace(ev)intercept = false}MotionEvent.ACTION_CANCEL -> {chargeToRightPlace(ev)intercept = false}else -> intercept = false}return intercept}/*** 将布局修正到正确的位置*/private fun chargeToRightPlace(ev: MotionEvent) {val eventX = ev.x - lastXLog.e(TAG, "该事件滑动的水平距离 $eventX")if (eventX < -(expandWidth / 4)) {smoothScrollTo(expandWidth, 0)expandState = trueinvalidate()} else {expandState = falsesmoothScrollTo(0, 0)invalidate()}// 回收内存mVelocityTracker?.apply {clear()recycle()}//清除状态lastX = 0finvalidate()}/*** 跟随手指移动*/private fun moveWithFinger(event: MotionEvent) {//获得手指在水平方向上的坐标变化// 需要滑动的像素val mX = lastX - event.xif (mX > 0 && mX < expandWidth) {scrollTo(mX.toInt(), 0)}// 获取当前水平方向的滑动速度mVelocityTracker!!.computeCurrentVelocity(500)val xVelocity = mVelocityTracker!!.xVelocity.toInt()invalidate()}/*** 缓慢滚动到指定位置*/private fun smoothScrollTo(destX: Int, destY: Int) {val delta = destX - scrollX// 在多少ms内滑向destXmScroller.startScroll(scrollX, 0, delta, 0, 600)invalidate()translationY = 0f}// 流畅地滑动override fun computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(mScroller.currX, mScroller.currY);postInvalidate()}}override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {expandWidth = childViewWidth()invalidate()super.onLayout(changed, l, t, r, b)}/*** 最多只允许有两个子布局*/private fun childViewWidth(): Int {Log.e(TAG, "childCount ${this.childCount}")return if (this.childCount > 1) {val expandChild = this.getChildAt(1) as LinearLayoutif (expandChild.measuredWidth != 0){expandWidth = expandChild.measuredWidth}Log.e(TAG, "expandWidth $expandWidth")expandWidth} else0}companion object {const val TAG = "ScrollerLinearLayout_YOLO"}
}

自定义View的思路比较简单,就是在ACTION_DOWN时记录初始的横坐标,在ACTION_MOVE中判断是否需要拦截该事件,当滑动的距离超过10,拦截该点击事件,并且View跟随手指移动。在ACTION_UPACTION_CANCEL中将布局修正到正确的位置,主要是根据滑动的距离来判断是否要展开并记录展开的状态。在ACTION_DOWN中判断是否处于展开状态,如果在展开状态且点击的位置不在扩展布局中,拦截点击事件。

提示:拦截点击事件是为了防止不必要的点击。

五、问题

自定义布局中的expandWidth参数在childViewWidth()方法和onLayout()方法中都赋值了一次,在onLayout()方法中查看日志expandWidth是有值的,可是在moveWithFinger()方法中打日志查看得到的expandWidth参数值仍然是0,导致无法正常滑动。去到其他的页面再返回到消息界面就可以正常滑动了,再次查看日志也有值了。

提示:这个问题不知道如何解决,所以需要手动设置expandWidth的值。


总结

初步的和自定义View认识了,小试牛刀,自己还是很满意这个学习成果的。希望在接下来的学习中不要因为没有接触过而放弃学习,勇于迈出第一步。文章若出现错误,欢迎各位批评指正,写文不易,转载请注明出处谢谢。

这篇关于仿微信消息列表的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/832415

相关文章

Python中列表的高级索引技巧分享

《Python中列表的高级索引技巧分享》列表是Python中最常用的数据结构之一,它允许你存储多个元素,并且可以通过索引来访问这些元素,本文将带你深入了解Python列表的高级索引技巧,希望对... 目录1.基本索引2.切片3.负数索引切片4.步长5.多维列表6.列表解析7.切片赋值8.删除元素9.反转列表

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

Spring+MyBatis+jeasyui 功能树列表

java代码@EnablePaging@RequestMapping(value = "/queryFunctionList.html")@ResponseBodypublic Map<String, Object> queryFunctionList() {String parentId = "";List<FunctionDisplay> tables = query(parent

ActiveMQ—消息特性(延迟和定时消息投递)

ActiveMQ消息特性:延迟和定时消息投递(Delay and Schedule Message Delivery) 转自:http://blog.csdn.net/kimmking/article/details/8443872 有时候我们不希望消息马上被broker投递出去,而是想要消息60秒以后发给消费者,或者我们想让消息没隔一定时间投递一次,一共投递指定的次数。。。 类似

Java消息队列:RabbitMQ与Kafka的集成与应用

Java消息队列:RabbitMQ与Kafka的集成与应用 大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿! 在现代的分布式系统中,消息队列是实现系统间通信、解耦和提高可扩展性的重要组件。RabbitMQ和Kafka是两个广泛使用的消息队列系统,它们各有特点和优势。本文将介绍如何在Java应用中集成RabbitMQ和Kafka,并展示它们的应用场景。 消息队

Exchange 服务器地址列表的配置方法与注意事项

Exchange Server 是微软推出的一款企业级邮件服务器软件,广泛应用于企业内部邮件系统的搭建与管理。配置 Exchange 服务器地址列表是其中一个关键环节。本文将详细介绍 Exchange 服务器地址列表的配置方法与注意事项,帮助系统管理员顺利完成这一任务。 内容目录 1. 引言 2. 准备工作 3. 配置地址列表 3.1 创建地址列表 3.2 使用 Exchange

Kafka 分布式消息系统详细介绍

Kafka 分布式消息系统 一、Kafka 概述1.1 Kafka 定义1.2 Kafka 设计目标1.3 Kafka 特点 二、Kafka 架构设计2.1 基本架构2.2 Topic 和 Partition2.3 消费者和消费者组2.4 Replica 副本 三、Kafka 分布式集群搭建3.1 下载解压3.1.1 上传解压 3.2 修改 Kafka 配置文件3.2.1 修改zookeep

Android 友盟消息推送集成遇到的问题

友盟消息推送遇到的问题 集成友盟消息推送,步骤根据提供的技术文档接入便可。可是当你集成到项目中去的时候,可能并不是一帆风顺就搞定,因为你项目里面是可能集成了其他的sdk(比如支付宝,微信,七鱼等等三方的sdk)。那么这个时候,再加上友盟的消息推送sdk集成可能就会出现问题。 问题清单 友盟消息推送sdk和支付宝sdk冲突问题 后台配置了消息推送,也显示发送成功,但是手机没有收到消息通知

Python--列表简介

列表是什么 列表让你能够在⼀个地方存储成组的信息,其中既可以只包含几个元素,也可以包含数百万个元素。列表是新手可直接使用的最强大的Python 功能之⼀。 列表(list)是一种可变的序列类型,用于存储一系列有序的元素。这些元素可以是任何类型,包括整数、浮点数、字符串、其他列表(即嵌套列表)等。列表是动态的,可以在运行时增加或删除元素。 用方括号([ ])表示列表,用逗号分隔其中的元素。