Android中使用有趣的指示器和过渡自定义 Compose Pager

news2024/11/20 21:31:08

paper-logo

Android使用有趣的指示器和过渡自定义 Compose Pager

google最近在compose中新增了Pager控件,HorizontalPagerVerticalPager
pager-effect

页面之间的转换

该文档涵盖了访问页面从“对齐”位置滚动到多远的基础知识。我们可以使用这些信息来创建页面之间的过渡效果。

例如,如果我们想在页面之间创建一个简单的淡入淡出效果,我们可以将修饰符应用于graphicsLayer我们的页面可组合项以调整其alpha和translationX

val pagerState = rememberPagerState()
HorizontalPager(
    pageCount = 10,
    modifier = modifier.fillMaxSize(),
    state = pagerState
) { page ->
    Box(Modifier
        .graphicsLayer {
            val pageOffset = pagerState.calculateCurrentOffsetForPage(page)
            // translate the contents by the size of the page, to prevent the pages from sliding in from left or right and stays in the center
            translationX = pageOffset * size.width
            // apply an alpha to fade the current page in and the old page out
            alpha = 1 - pageOffset.absoluteValue
        }
        .fillMaxSize()) {
        Image(
            painter = rememberAsyncImagePainter(model = rememberRandomSampleImageUrl
                (width = 1200)),
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxSize()
                .padding(16.dp)
                .clip(RoundedCornerShape(16.dp)),
        )
    }
}

// extension method for current page offset
@OptIn(ExperimentalFoundationApi::class)
fun PagerState.calculateCurrentOffsetForPage(page: Int): Float {
    return (currentPage - page) + currentPageOffsetFraction
}

然后我们可以将这个graphicsLayer修饰符提取到一个可重用的修饰符中,我们可以在其他HorizontalPager实例上使用它:

fun Modifier.pagerFadeTransition(page: Int, pagerState: PagerState) = 
    graphicsLayer {
        val pageOffset = pagerState.calculateCurrentOffsetForPage(page)
        translationX = pageOffset * size.width
        alpha = 1- pageOffset.absoluteValue
    }

pager-effect
这太棒了,我们可以实现我们之前在视图中能够实现的相同效果ViewPager

其他有趣的过渡效果

您可以使用 ViewPager 实现的一些更常见的效果,也可以使用 Compose 中的 Pager 实现,例如:

立方体深度效果

立方体深度效果

立方体深度效果
立方体深度效果

指尖陀螺效果
指尖陀螺效果

更多特效请查看github repo代码

https://github.com/riggaroo/compose-playtime#custom-pager-transformations

Compose 的美妙之处在于我们PagerState也可以访问页面内部的内容。因此我们可以使用此信息来执行有趣的效果,例如驱动动画、隐藏/缩放内容或根据页面的滚动状态显示内容。

下面实现音乐播放界面效果
[video width=“1600” height=“1200” mp4=“http://www.lovecodeboy.top/wp-content/uploads/2023/04/dribbble.mp4”][/video]

首先,我们创建此屏幕组件的静态版本,方法是创建HorizontalPager内部 aBoxstaticImageText可组合项来表示每个页面。

@Preview
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DribbbleInspirationPager() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xFFECECEC))
    ) {
        val pagerState = rememberPagerState()
        HorizontalPager(
            pageCount = 10,
            pageSpacing = 16.dp,
            beyondBoundsPageCount = 2,
            state = pagerState,
            modifier = Modifier.fillMaxSize()
        ) { page ->
            Box(modifier = Modifier.fillMaxSize()) {
                // Contains Image and Text composables
                SongInformationCard(
                    modifier = Modifier
                        .padding(32.dp)
                        .align(Alignment.Center),
                    pagerState = pagerState,
                    page = page
                )
            }

        }
    }
}

现在我们有了歌曲的静态版本,我们可以进一步分析设计,看看可组合项的哪些部分是动画的。您可能会注意到的第一件事是卡片内的图像会随着它是否为当前选定的项目而变大。滚动的下一个变化是卡片的尺寸扩大,并在展开时显示“拖动以收听”文本。要实现这两个元素,我们可以使用相同的值pagerState.currentPageOffsetFraction,并pagerState.currentPage在页面可组合项内容的不同部分使用这些值。

要调整可组合项内的图像,我们使用Modifier.graphicsLayer { }on Image

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SongInformationCard(
    pagerState: PagerState,
    page: Int,
    modifier: Modifier = Modifier
) {
    Card(
        modifier = /*..*/    
    ) {
        Column(modifier = /*..*/) {
            val pageOffset = pagerState.calculateCurrentOffsetForPage(page)
            Image(
                modifier = Modifier
                    /* other modifiers */
                    .graphicsLayer {
                        // get a scale value between 1 and 1.75f, 1.75 will be when its resting,
                        // 1f is the smallest it'll be when not the focused page
                        val scale = lerp(1f, 1.75f, pageOffset)
                        // apply the scale equally to both X and Y, to not distort the image
                        scaleX = scale
                        scaleY = scale
                    },
                //..
            )
            SongDetails()
        }
    }
}

通过获取 pagerState.currentPage 并减去当前歌曲所在的页数,我们可以知道歌曲距离 pager(当前选中的页)中心的偏移量。接着,我们将这个值与pagerState.currentPageOffsetFraction相加,现在我们知道页面从其对齐位置滚动的比例。然后,我们可以将 pageOffset 在 1f 和 1.75f 之间进行缩放。这个值会被用来应用 scaleXscaleY,以便不会对图像进行扭曲。当页面没有被选中时,缩放值为 1.75f,当页面被选中时,缩放值为 1f。

结果如下:

下一步是展开卡片以显示和隐藏卡片中的“拖动以收听”部分。使用相同的 pageOffset 值,我们可以对 Column 组合进行高度的动画变化,同时也可以对透明度进行动画效果的变化。

@Composable
private fun DragToListen(pageOffset: Float) {
    Box(
        modifier = Modifier
            .height(150.dp * (1 - pageOffset))
            .fillMaxWidth()
            .graphicsLayer {
                alpha = 1 - pageOffset
            }
    ) {
        Column(
            modifier = Modifier.align(Alignment.BottomCenter),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Icon(
                Icons.Rounded.MusicNote, contentDescription = "",
                modifier = Modifier
                    .padding(8.dp)
                    .size(36.dp)
            )
            Text("DRAG TO LISTEN")
            Spacer(modifier = Modifier.size(4.dp))
            DragArea()
        }
    }
}


@Composable
private fun DragArea() {
    Box {
        Canvas(
            modifier = Modifier
                .padding(0.dp)
                .fillMaxWidth()
                .height(60.dp)
                .clip(RoundedCornerShape(bottomEnd = 32.dp, bottomStart = 32.dp))
        ) {
            val sizeGap = 16.dp.toPx()
            val numberDotsHorizontal = size.width / sizeGap + 1
            val numberDotsVertical = size.height / sizeGap + 1
            repeat(numberDotsHorizontal.roundToInt()) { horizontal ->
                repeat(numberDotsVertical.roundToInt()) { vertical ->
                    drawCircle(
                        Color.LightGray.copy(alpha = 0.5f), radius = 2.dp.toPx
                            (), center =
                        Offset(horizontal * sizeGap + sizeGap, vertical * sizeGap + sizeGap)
                    )
                }
            }
        }
        Icon(
            Icons.Rounded.ExpandMore, "down",
            modifier = Modifier
                .size(height = 24.dp, width = 48.dp)
                .align(Alignment.Center)
                .background(Color.White)
        )
    }
}

运行这个代码,我们可以看到每张卡片的高度现在也受到页面被拖动的程度的驱动:
太棒了 - 我们已经实现了与原始设计一样的页面动画效果!完整的源代码可以在这里找到。

https://github.com/riggaroo/compose-playtime/blob/main/app/src/main/java/dev/riggaroo/composeplaytime/pager/DribbbleInspirationPager.kt

页面指示器

现在我们已经了解了如何访问PagerState并使用其转换内容,另一个常见的使用Pager的用例是添加一个指示器来显示当前所在的页面在页面列表中的位置。使用Compose和PagerState,获取这些信息非常简单。

要创建一个基本的页面指示器,我们可以像文档建议的那样,为每个页面绘制一个圆圈。但是,我们还可以创建自己的自定义页面指示器,例如在屏幕底部分段的线条,我们可以更改绘制逻辑以绘制线条而不是圆圈,并将宽度均匀地划分为若干段。

从下面这个页面效果得到灵感
pager-effect

我们要做的第一件事是在 一个HorizontalPager内部创建一个Box,并将我们的圆形指示器移动到框的底部,与分页器内的内容重叠:

@Preview
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LineIndicatorExample() {
    Box(modifier  = Modifier.fillMaxSize()) {
        val pageCount = 5
        val pagerState = rememberPagerState()
        HorizontalPager(pageCount = pageCount,
            beyondBoundsPageCount = 2,
            state = pagerState) {
            PagerSampleItem(page = it)
        }
        Row(
            Modifier
                .height(50.dp)
                .fillMaxWidth()
                .align(Alignment.BottomCenter),
            horizontalArrangement = Arrangement.Center
        ) {
            repeat(pageCount) { iteration ->
                val color = if (pagerState.currentPage == iteration) Color.White else Color.White.copy(alpha = 0.5f)
                Box(
                    modifier = Modifier
                        .padding(4.dp)
                        .clip(CircleShape)
                        .background(color)
                        .size(16.dp)

                )
            }
        }
    }
}

indicator-effect
接下来,我们将更改圆形指示器,以绘制一条线条,如果选中与未选中,则颜色和大小不同。我们最初给每行一个权重1f

@Preview
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LineIndicatorExample() {
    Box(modifier  = Modifier.fillMaxSize()) {
        val pageCount = 5
        val pagerState = rememberPagerState()
        HorizontalPager(pageCount = pageCount,
            beyondBoundsPageCount = 2,
            state = pagerState) {
            PagerSampleItem(page = it,
                modifier = Modifier.pagerFadeTransition(it, pagerState = pagerState))
        }
        Row(
            Modifier
                .height(24.dp)
                .padding(start = 4.dp)
                .fillMaxWidth()
                .align(Alignment.BottomCenter),
            horizontalArrangement = Arrangement.Start
        ) {
            repeat(pageCount) { iteration ->

                val color = if (pagerState.currentPage == iteration) Color.White else Color.White.copy(alpha = 0.5f)
                Box(
                    modifier = Modifier
                        .padding(4.dp)
                        .clip(RoundedCornerShape(2.dp))
                        .background(color)
                        .weight(1f)
                        .height(4.dp)
                )
            }
        }
    }
}

这导致每一页的线条,绘制而不改变它们的大小:

indicator-effect

现在,线条也需要为它们的长度变化设置动画。如果项目被选中,它应该是最长的一行。我们将为 设置动画weight,1f在右侧未选定页面、1.5f选定行和0.5f选定页面左侧的页面之间进行选择。我们使用animateFloatAsState这些权重之间的动画:

repeat(pageCount) { iteration ->
    val lineWeight = animateFloatAsState(
        targetValue = if (pagerState.currentPage == iteration) {
            1.5f
        } else {
            if (iteration < pagerState.currentPage) {
                0.5f
            } else {
                1f
            }
        }, label = "weight", animationSpec = tween(300, easing = EaseInOut)
    )
    val color =
        if (pagerState.currentPage == iteration) Color.White else Color.White.copy(alpha = 0.5f)
    Box(
        modifier = Modifier
            .padding(4.dp)
            .clip(RoundedCornerShape(2.dp))
            .background(color)
            .weight(lineWeight.value)
            .height(4.dp)
    )
}

indicator-effect

完整项目源码:

https://github.com/riggaroo/compose-playtime/blob/main/app/src/main/java/dev/riggaroo/composeplaytime/pager/LineIndicatorExample.kt

总结

正如我们在本篇博文中探讨的那样,我们可以看到在Compose中使用PagerState可以灵活地创建更加复杂的页面交互效果,这在以前是比较复杂的。通过利用pagerState.currentPagepagerState.currentPageOffsetFraction变量,我们可以创建相当复杂的UI交互和页面指示器。

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

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

相关文章

DC:4通关详解

信息收集 漏洞发现 访问web 尝试弱口令 账号admin 可以执行ls du df看看发的包,我们是否有机会执行任意命令 发现post传参radio处可以任意命令执行 弹个shell先 提权 从vps上下载LinEnum.sh来枚举脆弱性 优化shell 现在shell就有自动补齐了 在/home/jim下发现密码字典…

cube-studio AI平台 提供开源模型示例列表(3月份)

文章目录背景AI应用商店背景 cube是腾讯音乐开源的一站式云原生机器学习平台&#xff0c;目前主要包含 1、数据管理&#xff1a;特征存储、在线和离线特征&#xff1b;数据集管理、结构数据和媒体数据、数据标签平台 2、开发&#xff1a;notebook(vscode/jupyter)&#xff1b…

【PTA天梯赛】L1-001 L1-002 L1-003 L-004 L-005 L-006 L-007 L-008 L-009 L1-010 c++

&#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;狠狠的刷题&#xff01;&#xff01;&#xff01;&#x1f308; 若有帮助&#xff0c;还请…

【Ubuntu 22.04 上配置 FTP 服务器步骤】

Ubuntu 22.04 上配置 FTP 服务器步骤 1.安装 vsftpd 软件包&#xff1a; sudo apt-get update sudo apt-get install vsftpd 2.查看vsftpd版本和状态&#xff0c;确认vsftpd安装成功和正常启动 2.修改 vsftpd 配置文件&#xff1a; sudo nano /etc/vsftpd.conf 3.在配置文件中…

Ethercat概念学习

Ethercat技术调研 背景 最近我们要基于Ethercat技术进行开发&#xff0c;首先需要了解其基本原理&#xff0c;github上看到了有相关实现&#xff0c;一起来看看吧。 Ethercat技术 速度更快 传输速率:2*100 Mbaud 全双工 高速性、高实时性 微秒级 像火车一样有帧头、帧尾&a…

如何对农田温室气体进行有效模拟?

农业是甲烷&#xff08;CH4&#xff09;、氧化亚氮&#xff08;N2O&#xff09;和二氧化碳&#xff08;CO2&#xff09;等温室气体的主要排放源&#xff0c;占全产业排放的13.5%。农田温室气体又以施肥产生的N2O和稻田生产产生的CH4为主&#xff0c;如何对农田温室气体进行有效…

计算机组成原理(四)输入/输出系统

一、概述 1.1前言 I/O设备是计算机组成原理之硬件最后的一部分。输入输出系统是计算机系统当中种类最多、功能最多、结构最复杂、构成也最多样的系统。在现代计算机系统当中&#xff0c;外部设备的总成本可以占到计算机总成本的80%以上。可以说&#xff0c;没有这些丰富多彩的外…

「Vue面试题」Vue项目中有封装过axios吗?主要是封装哪方面的?

一、axios是什么 axios 是一个轻量的 HTTP客户端 基于 XMLHttpRequest 服务来执行 HTTP 请求&#xff0c;支持丰富的配置&#xff0c;支持 Promise&#xff0c;支持浏览器端和 Node.js 端。自Vue2.0起&#xff0c;尤大宣布取消对 vue-resource 的官方推荐&#xff0c;转而推荐…

(原创)Flutter基础入门:实现各种Shape效果

前言 上一篇博客讲了Flutter的装饰器Decoration Flutter基础入门&#xff1a;装饰器Decoration 装饰器就可以帮我们实现各种Shape效果 但上篇文章并没有讲如何实现具体的Shape效果 那么具体要怎么做呢&#xff1f;这篇文章就主要讲这块 在Fluter中实现Shape效果时&#xff0c;…

Servlet(一)

目录 1.什么是Servlet 2.servlet程序 2.1 创建项目 2.2 引入依赖 2.3 创建目录 2.4 编写代码 2.5 打包程序 2.6 部署程序 2.7 验证程序 3.更简单的部署方法 3.1 安装 3.2配置 4.访问出错怎么办 4.1 404 4.2 405 4.3 500 4.4 空白页面 4.5 无法访问此页面 5.se…

Gin web框架初步认识

Goland使用及gin框架下载引入 第一次使用Goland时需要配置GOROOT、GOPATH、Go Modules 配置完成后进入面板&#xff0c;右键选择Go Modules文件&#xff0c;或者在go工作区通过命令go mod init [name]创建go mod项目。 创建完的项目一般都有go.mod文件和go.sum&#xff0c;前者…

Mysql【安装教程】

Mysql安装教程 1.安装教程 可以去官网下载这个版本的&#xff1a;mysql-installer-community-8.0.31.0 双击点开&#xff0c;选择自定义&#xff1a; 选择主键&#xff1a;左边选择之后就点蓝色按钮添加到右边去&#xff0c;next&#xff1a; 如果出现这个页面&#xff0c…

机器视觉检测系统的基本流程你知道吗

工业制造业种&#xff0c;首先我们便需要了解其基本流程&#xff0c;作为工厂信息科人员&#xff0c;我们不能只依靠视觉服务商的巡检驻检来解决问题&#xff0c;为了产线的效率提升&#xff0c;我们更多的应该培养产线技术人员&#xff0c;出现问题便可以最快速度解决问题&…

领跑新能源车市“下半场”,这家企业凭什么?

中国新能源汽车市场行至下半场&#xff0c;将围绕技术升级、产品竞争力比拼、整合淘汰等趋势快速发展。 4月7日&#xff0c;在北京水立方发布的奇瑞新能源之夜上&#xff0c;奇瑞汽车全面展示新战略、新技术、新品牌和新产品&#xff0c;宣布将以全新的技术生态加速向全球科技…

光伏电池片技术N型迭代,机器视觉检测赋能完成产量“弯道超车”

电池片是光伏发电的核心部件&#xff0c;其技术路线和工艺水平直接影响光伏组件的发电效率和使用寿命。随着硅料、硅片技术逐渐接近其升级迭代空间的瓶颈&#xff0c;电池片环节正处于技术变革期&#xff0c;是光伏产业链中迭代最快的部分。P型中PERC电池片是现阶段市场的主流产…

已知原根多项式和寄存器初始值时求LFSR的简单例子

线性反馈移位寄存器&#xff08;LFSR&#xff09;是一种用于生成伪随机数序列的简单结构。在这里&#xff0c;我们有一个四项原根多项式 p(x)1x0x21102p(x) 1 x 0x^2 110_2p(x)1x0x21102​ 和初始值 S0100S_0 100S0​100。我们将使用 LFSR 动作过程来生成一个伪随机序列。…

2023美赛春季赛_赛题原文及翻译

目录 Problem Y: Understanding Used Sailboat Prices Y题翻译&#xff1a; Problem Z: The Future of the Olympics Z题翻译&#xff1a; Problem Y: Understanding Used Sailboat Prices ​Like many luxury goods, sailboats vary in value as they age and as market c…

看这一篇就够了!!!Java最全面试手册(性能优化+微服务架构+并发编程+开源框架)

Java面试手册 一、性能优化面试专栏 1.1、 tomcat性能优化整理 ​ 1.2、JVM性能优化整理 1.3、Mysql性能优化整理 二、微服务架构面试专栏 2.1、SpringCloud面试整理 2.2、SpringBoot面试整理 2.3、Dubbo面试整理 三、并发编程高级面试专栏 四、开源框架面试题专栏 4.1、Sp…

[Data structure]稀疏数组

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;数据结构。数据结构专栏主要是在讲解原理的基础上拿Java实现&#xff0c;有时候有C/C代码。 ⭐如果觉得文章写的…

清肠化湿颗粒通过激活NLRP6信号和调节Th17/Treg平衡来改善DSS诱导的结肠炎

百趣代谢组学分享-文章标题&#xff1a;Qing-Chang-Hua-Shi granule ameliorates DSS-induced colitis by activating NLRP6 signaling and regulating Th17/Treg balance 代谢组学分享-发表期刊&#xff1a;Phytomedicine 代谢组学分享-影响因子&#xff1a;6.656 代谢组学…