Compose回忆童年 - 手拉灯绳-开灯/关灯

news2024/11/17 19:33:43

一、前言

偶然间想到小时候顺着那白色开关垂下来的灯绳,拉一下“咔哒”一声,再拉一下又是“咔哒”一声。当时年龄小感觉新奇总是把灯开了关又关了开的拉着玩,以至于好几次拉坏了开关灯绳。

今天我们在手机上做一个拉不坏的灯绳😄,怀念一下童年🤣。

82年钨丝灯.png

二、材料准备

  1. 在App里面准备一个弹簧绳😄,我们想奢侈一下,躺在床上拉灯绳。
  2. 绳末尾底部的疙瘩,替换成:夜光球 ,毕竟现在2022年了,夜晚总不能黑灯瞎火摸着找灯绳吧,你们觉得呢?
  3. 准备一个82年的钨丝灯泡💡,拉菲也得配个82年的,才有氛围感。

三、安装82年的钨丝灯和灯绳

我们要知道材料所需要占用最大的宽度和最小宽度,可以用BoxWithConstraints做容器,里面放我们的灯泡灯绳以及我们的“夜光球”。

我们知道BoxWidthConstraints可以获得屏幕上Composable最小/最大可用宽度和高度,可以根据可用空间使用它来显示不同的内容。

1. 使用Image来显示我们的钨丝灯:

Image(
   alpha = state.wsdAlpha,
   modifier = Modifier.align(Alignment.TopCenter).size(100.dp),
   painter = painterResource(id = R.drawable.ic_wsd),
   contentDescription = null
)

2. 穿好我们的弹簧灯绳,绘制一条线🧵,我们这里还需要用到Modifier.matchParentSize 填充可用空间

Box(
    modifier = Modifier
        .matchParentSize()
        .ropeLine(ropeHandleState)
)

// 绘制我们的弹簧绳
fun Modifier.ropeLine(
    state: RopeHandleState
): Modifier {
    return drawBehind {
        val bulbPosition = state.ropeHandleOffset
        drawLine(
            Color.DarkGray,
            start = Offset(x= size.width * 0.8F,y = 0f),
            end = bulbPosition,
            ROPE_LINE_WIDTH.toPx()
        )
    }
}

这里我们使用ModifierdrawBehind修饰符,大家可以去看一下Compose性能优化篇,仅在绘制阶段读取“绳子”拉动的距离,因此,Compose 可以完全跳过组合阶段和布局阶段 , 当距离发生变化时,Compose 会直接进入绘制阶段。

3. 我们需要给“夜光球”增加2个功能,“点击开/关灯”,“借力拉绳

点击功能:我们可以通过PointerInputScope#detectTapGestures来实现点击功能。

借力拉伸功能:就是我们的拖拽功能,我们可以通过PointerInputScope#detectDragGestures来实现拖拽的功能。

通过Modifieroffset修饰符,更新x轴y轴的位置。

大家先看看我们的“夜光球”的全部代码,下面我们会介绍RopeHandleState

@Composable
fun LightButton(state: RopeHandleState) {
    val coroutineScope = rememberCoroutineScope()

    Box(Modifier
        .size(BUTTON_RADIUS * 2)
        .offset {
            val position = state.ropeHandleOffset - Offset(BUTTON_RADIUS.toPx(), BUTTON_RADIUS.toPx())
            IntOffset(position.x.roundToInt(), position.y.roundToInt())
        }
        .background(
            // 夜光球的背景
            brush = state.ropeHandleBackground,
            shape = CircleShape
        )
        .shadow(16.dp, shape = CircleShape, clip = false)
        .pointerInput(Unit) {
            detectTapGestures(onTap = {
                // 点击了开关按钮
                state.toggle()
            })
        }
        .pointerInput(Unit) {
            detectDragGestures(
                onDragStart = { state.onDragStart() },
                onDragEnd = { coroutineScope.launch { state.onDragEnd() } },
                onDragCancel = { coroutineScope.launch { state.onDragEnd() } },
                onDrag = { change, dragAmount ->
                    coroutineScope.launch { state.onDrag(change, dragAmount) }
                }
            )
        }
    )
}

四、绳柄状态类RopeHandleState

1. 我们需要把BoxWithConstraints的最大宽度和高度传进来,定义如下构造方法:

// RopeHandleState.kt
class RopeHandleState(size: Size) { ... }

2. 初始化默认绳柄尾巴的“夜光球”,默认位置

// RopeHandleState.kt
private val initStartX = size.width * 0.8F
private val initStartY = size.height * 0.5F

3. 定义一个state记录当前开关状态

// RopeHandleState.kt
private var isOpen by mutableStateOf(false)

4. 记录是否已经开始拖拽,以及拖拽位置动画Animatable

// RopeHandleState.kt
private var isDragStart by mutableStateOf(false)
private val dragAnimatable = Animatable(Offset(0F, 0F), Offset.VectorConverter)

为什么要初始化isDragStart呢?

用过82年钨丝灯的应该知道,以前的拉绳开关,拉到一定距离自己就打开灯了,松手后,再拉到一定距离灯就关了。

5. 返回当前绳子拉动的x轴和y轴位置

val ropeHandleOffset by derivedStateOf {
    dragAnimatable.value.exactPositionIn()
}
private fun Offset.exactPositionIn(): Offset {
    // 拖动的距离 + 初始化位置
    return this + Offset(initStartX, initStartY)
}

6. 定义onDragStart、onDrag、onDragEnd

fun onDragStart() {
    isDragStart = true
}

suspend fun onDragEnd() {
    isDragStart = false
    dragAnimatable.animateTo(
        targetValue = Offset.Zero,
        animationSpec = spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)
    )
}

suspend fun onDrag(change: PointerInputChange, dragAmount: Offset) {
     change.consume()
     val targetValue = dragAnimatable.value + dragAmount
     dragAnimatable.snapTo(targetValue)
     if(isDragStart) {
        if(targetValue.y >= 250F) {
           isDragStart = false
           // 更新开关状态,这里只修改灯泡的可见性,因为没有UI素材,一切从简!!
           toggle()
        }
     }
}

到这里,我们就是使用Animatable写这篇文章的起因之一,我们可以看到AnimatablesnapTo方法:

将当前值设置为目标值,没有任何动画。这个方法将取消任何正在进行的动画并将 Animatable.value 和 Animatable.targetValue 更新为 targetValue 后返回。

Animateable#animateTo我们这里指定了animationSpecspring弹簧动画,因为onDragEnd松手后触发执行的,所以targetValue直接设置为:Offset.Zero即可。

后续扩展:打开可以给它加个“手机震动”、“播放一段音乐”等等,大家自由发挥咯。

最后,看看我们的效果吧:

2022-08-29 22_12_44.gif

点击下载体验钨丝灯Demo: https://www.pgyer.com/0hpA

别忘了:点赞❤️+收藏❤️+评论❤️+关注❤️+分享❤️,😘😘😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/97814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

毕业四年,我当初是如何走上编程这条路的!

感概万千,毕业已达4年之久。 想起在大学时期学习编程的事情,感觉很有意义,在此记录回顾一下。 希望自己初心未变,勇往向前 现状与过去 20210706 目前的我是在天津一家公司做前端开发,主要用Python。 从毕业实习到…

入门:容器工作机制概述

我们先来看看Docker的整体架构: 实际上分为三个部分: Docker 客户端:也就是我们之前使用的docker命令,都是在客户端上执行的,操作会发送到服务端上处理。 Docker 服务端:服务端就是启动容器的主体了&#x…

Java项目:SSM网上超市购物商城管理系统

作者主页:源码空间站2022 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为前后台,分为普通用户、管理员两种角色。前台普通用户登录,后台管理员登录; 管理员角色包含以下功…

毕业设计 单片机温湿度环境检测仪 - stm32 物联网 嵌入式

文章目录0 前言1 简介2 主要器件3 实现效果4 设计原理5 部分核心代码6 最后0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长…

Dynamic RSA System 设计与实现

一、背景 在实现了静态的加解密工具后,感觉不够灵活,想设计一个动态生成 RSA KeyPair 的中间系统,暂且称为 Dynamic RSA System,以达到自动化维护信安高墙的效果。 加解密和签名校验工具_余衫马的博客-CSDN博客_校验和工具RSA加…

Zero-sho原先的升级版:hourglass网络:U-Net

ZMFF: Zero-shot multi-focus image fusion (ZMFF: Zero-shot 多聚焦图像融合) (本论文的先导片:ZERO-SHOT MULTI-FOCUS IMAGE FUSION) 这是我们之前的扩展工作。在ZMFF,我们做一些改动和改进相比原来的框架。首先&…

数学英语不好,新手学编程难吗?适合学Python吗?

英语不好,上学时考试从来没有超越40分。 数学也不可,很多的东西都还给老师了。 我还能学习编程吗??? 刚开始学习的时分,这个问题深深的困扰着我。以致于我其时报培训班之前犹疑了很长很长时刻。 由于在我…

[C语言数据结构]万字长文带你学习八大排序

📟作者主页:慢热的陕西人 🌴专栏链接:数据结构 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客讲解八大排序,及其优化 文章目录排序Ⅰ插入排序&…

配置SSH Keys到github,实现本地操作远程仓库的权限

文章目录第1步:创建SSH Key第2步:检查是否纯正.ssh目录第3步:复制id_rsa.pub的内容第4步:在你的github页面上配置SSH key第5步:验证是否可连接第1步:创建SSH Key 在用户根目录下,打开命令&…

非零基础自学Golang 第10章 错误处理 10.5 Go语言错误的应用 10.6 小结 10.7 知识拓展

非零基础自学Golang 文章目录非零基础自学Golang第10章 错误处理10.5 Go语言错误的应用10.6 小结10.7 知识拓展10.7.1 error接口的应用第10章 错误处理 10.5 Go语言错误的应用 10.4.2小节中的panic()和recover()虽然能模拟其他语言的异常机制,但是并不建议在Go语言…

广州特耐苏-广州风淋通道构造及特点

风淋通道构造及风淋通道特点-广州特耐苏-广州风淋通道-广州风淋通道规格-广州风淋通道置量-广州风淋通道价格-特耐苏拥有*的数控加工设备,技术力量雄厚,专业生产自动化/风淋室货淋室、 >>采用更适合洁净室原理的圆弧转角; >>系统自动控制运…

密码技术扫盲:非对称加密

个人博客 密码技术扫盲:对称加密🎯 密码技术扫盲:非对称加密密码技术扫盲:认证 在上一篇对称加密的介绍中,我们了解到对明文的加密需要使用到密钥,而解密时也必须用到同一把密钥,也就是说发送…

假如编程是魔法之零基础看得懂的Python入门教程

一、前言 几个月前编写了一份python语言入门的博文,近期重新审阅了一遍发现编写的质量太过随意,可能对于一部分人并不是非常友好,故此重新编写Python语言的零基础教程。 本篇教程将会尽量把一些专业术语给读者讲解清楚,并且让读者…

如何成为一个优秀的Python工程师?

众所周知,Python因其优雅而简洁的语言优势而备受程序员的青睐和追捧。随着人工智能、大数据技术的落地,Python工程师也成为了目前薪资待遇高,发展前景好的热门岗位。虽然,Python入门简单,对初学者友好,但是…

52 如何 尽可能的减少 自定义ClassLoader 造成的影响

前言 // 呵呵 很快又该总结 2022 了, 希望这一年也能总结出很多收获 接着 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载 可以先看一下 这一篇文章, 明确一下 上下文 这里 主要说的是 如果我们的场景中存在自定义的 classloader 的情况…

Flask + echarts 轻松解决 nginx 日志可视化

最近,线上的业务系统不太稳定,需要分析下访问情况,能拿到的数据只有 nginx 服务器的访问日志,不过难不倒我,用合适的工具,分分钟做出图形化展示,看看怎么做的吧 思路 nginx 访问日志&#xff…

9 CPP结构体注意事项

注意: 1 结构名是标识符 2 结构体的成员可以是任意数据类型 3 定义结构体描述的代码可以放在程序的人和地方,一般放在main函数的上面或头文件中。 4 结构体成员可以用C的类(如string),但是不提倡。 5 在C中&#…

java:AES加密和解密

java:AES加密和解密 1 前言 对称加密,即单秘钥加密,指加密和解密的过程中,使用相同的秘钥,相比于非对称加密,因仅有一把钥匙,故而速度更快,更适合解密大文件(常见于如视…

ESP32的arduino IDE代码使用flash download tool进行烧录

ESP32的arduino IDE代码使用flash download tool进行烧录前言arduino代码烧录arduino下载了一些什么文件flash download tool工具烧录总结前言 最近遇到用户在使用 arduino IDE开发环境编写了ESP32的代码,希望提供编写好的程序给用户烧录,但是又不希望让…

Kotlin + SpringBoot + JPA 服务端开发

Kotlin SpringBoot JPA 服务端开发 本篇主要介绍一下 kotlin springboot的服务端开发环境搭建 1.概述 Kotlin 是一个基于JVM的编程语言, 是IDEA开发工具 jetbrains 公司开发的语言,也被google选为android开发的首选语言, 因为它是完全兼容Java的 所以也可以做后端开发 比如…