如何是Jetpack Compose构建漂亮的应用程序

news2024/11/17 9:26:44

如何是Jetpack Compose构建漂亮的应用程序

beatful ui

Jetpack compose 是在 Android 上构建 UI 的未来。

如果您完全不熟悉 android 并且不知道 Jetpack Compose 是什么——它基本上是一种构建本机用户界面的新方法。
Jetpack compose官方站点

https://developer.android.com/jetpack/compose

在本文中,您将了解如何使用 Jetpack Compose 遵循最佳实践进行 UI 开发。

我们正在建设什么
我们将构建一个显示 Apple Music 专辑列表的应用程序。让我们调用应用程序MyMusic。

从我的github 存储库中,您将学习如何:

https://github.com/ibrajix/MyMusic

  1. 使用推荐的方法(启动 API)构建启动画面

https://developer.android.com/guide/topics/ui/splash-screen

  1. 使用各种 UI 可组合项,例如行、列、惰性列、动画 API
  2. 将 MVVM 模式与 Jetpack Compose 结合使用(使用可观察对象和状态持有者,如 StateFlow)
  3. 使用Room 数据库从 JSON 文件保存本地数据
  4. 在应用程序上实现搜索功能
  5. 使用这个很棒的库实现带有过渡动画的简单导航

https://github.com/raamcosta/compose-destinations

  1. 如何使用这个很棒的库有效地显示 gif 等图像

https://github.com/skydoves/Landscapist

为了降低复杂性,本文重点介绍 UI。

SpashScreen

启动画面是在您的应用程序内容加载之前显示的内容。这是向用户展示您的品牌形象或徽标的一种方式。

splashscreen

可悲的是,没有办法专门用 compose 来实现当前的 splash API。我们仍然需要一些 xml 代码

  • values/themes.xml下。添加启动画面主题
    代码中注释了每个属性的作用
<!--Parent Theme-->
<style name="Theme.MyMusic" parent="android:Theme.Material.Light.NoActionBar">
   ......
</style>

<!--Splash Screen Theme-->
<style name="Theme.MyMusic.SplashScreen" parent="Theme.SplashScreen">
   <!--splash screen background-->
   <item name="windowSplashScreenBackground">@color/splash_screen_background_color</item>

   <!--splash screen drawable-->
   <item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>

   <!--post splash screen - displayed after splash screen-->
   <item name="postSplashScreenTheme">@style/Theme.MyMusic</item>
</style>
  • 确保将主题包含在AndroidManifest.xml root 标记中
android:theme="@style/Theme.MyMusic.SplashScreen"

在您的 Launcher Activity 中安装主题,它应该是您的MainActivity.kt

class MainActivity : ComponentActivity()
  .........
super.onCreate(savedInstanceState)
  ......
installSplashScreen()
  • 运行该应用程序,您的初始屏幕应该可以正常工作。

StartSceen

图2

创建一个新的composable,命名为StartScreen(代码路径 ui/screens/start/StartScreen.kt

@RootNavGraph(start = true)
@Destination(style = StartScreenTransitionAnimation::class)
@Composable
fun StartScreen(
    modifier: Modifier = Modifier,
    navigator: DestinationsNavigator
) {

   ..//we'll build the layout above here

}

@RootNavGraph - 这是我们导航库中的一个注释,表示这是我们导航图的起始屏幕。

@Destination - 也是来自我们导航库的注释,表示这个组合式是一个可以让用户往返导航的目的地。我们还包括了一个过渡动画的样式属性(请查看animations/StartScreenTransition.kt)。

@Modifier - 是传递给组合式的参数,用于装饰组合式(例如,大小、背景等)。

@Navigator - 帮助我们从一个目的地或屏幕导航到另一个。

  • 正如您从上面的图2中可以看到的那样,卡片被放置在肯伊·威斯特(Kanye West)的图像上方。
  • 因此,我们将使用一个Box。
  • Box是一个UI可组合,可以让您将项目放置在其他项目之上。

请在上面的{…}之间放置以下可组合:

Box(
    modifier = modifier
        .fillMaxSize()
){
.......
}
  • modifier 说明该 Box 应填充整个屏幕的大小。
  • 现在,在 Box 内部,我们放置我们的卡尼·韦斯特图片,这是一个 GIF(使用我们的图像加载库)。
Box(
    modifier = modifier
        .fillMaxSize()
){

    GlideImage(
        imageModel = R.drawable.kanye
    ) 
    ........
}
  • 接下来,我们需要创建一个放置在 Kanye 图片上方的卡片。我们可以使用 Card 组合来实现。
Box(
        modifier = modifier
            .fillMaxSize()
    ){

        GlideImage(
            imageModel = R.drawable.kanye
        )

        Card(modifier = modifier
            .fillMaxWidth(0.8F)
            .align(Alignment.BottomCenter)
            .padding(bottom = 50.dp),
            shape = RoundedCornerShape(50.dp),
            backgroundColor = MaterialTheme.colors.secondary
        ) {

          ...........

        }

}
  • 我们指定卡片应该在屏幕底部居中,宽度恰好占据屏幕的 80%,并有 50dp 的 padding。
  • 我们还指定了卡片应该有圆角,半径为 50dp,并具有辅助背景色。
  • 从上图2中可以看出,红色箭头表示物品是从上到下(纵向)排列的。

图a

  • Column是一个UI组件,可将其子项垂直排列在一起。
Box(
        modifier = modifier
            .fillMaxSize()
    ){

        GlideImage(
            imageModel = R.drawable.kanye
        )

        Card(modifier = modifier
            .fillMaxWidth(0.8F)
            .align(Alignment.BottomCenter)
            .padding(bottom = 50.dp),
            shape = RoundedCornerShape(50.dp),
            backgroundColor = MaterialTheme.colors.secondary
        ) {

            Column(
                modifier = modifier
                    .padding(bottom = 20.dp),
                verticalArrangement = Arrangement.SpaceEvenly,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                ...........
            }
            
       }
}
  • 这些修饰符和属性都是很容易理解的。
  • 我们确保在此列中放置的所有项目从上到下(垂直)均匀分布。
  • 同时,确保项目从左到右(水平)居中。
  • 现在,我们将在列中添加其他 UI 组合件——文本和按钮。
    开始界面的完整代码如下:
//StartScreen.kt
@RootNavGraph(start = true)
@Destination(style = StartScreenTransitionAnimation::class)
@Composable
fun StartScreen(
    modifier: Modifier = Modifier,
    navigator: DestinationsNavigator
) {

    Box(
        modifier = modifier
            .fillMaxSize()
    ){

        GlideImage(
            imageModel = R.drawable.kanye
        )

        Card(modifier = modifier
            .fillMaxWidth(0.8F)
            .align(Alignment.BottomCenter)
            .padding(bottom = 50.dp),
            shape = RoundedCornerShape(50.dp),
            backgroundColor = MaterialTheme.colors.secondary
        ) {

            Column(
                modifier = modifier
                    .padding(bottom = 20.dp),
                verticalArrangement = Arrangement.SpaceEvenly,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {

                Text(
                    modifier = modifier
                        .padding(30.dp),
                    text = stringResource(id = R.string.explore_your_world_of_music),
                    style = MaterialTheme.typography.h1,
                    textAlign = TextAlign.Center,
                    color = MaterialTheme.colors.onSecondary
                )

                Text(
                    modifier = modifier
                        .padding(horizontal = 30.dp),
                    text = stringResource(id = R.string.see_trending_songs_from_favs),
                    style = MaterialTheme.typography.caption,
                    textAlign = TextAlign.Center,
                    fontSize = 14.sp,
                    color = MaterialTheme.colors.onSecondary
                )

                Button(
                    modifier = modifier
                        .fillMaxWidth(0.6f)
                        .height(80.dp)
                        .padding(top = 8.dp)
                        .align(Alignment.CenterHorizontally)
                        .padding(8.dp),
                    shape = RoundedCornerShape(50.dp),
                    onClick = {
                        navigator.popBackStack()
                        navigator.navigate(HomeScreenDestination)
                    }
                ) {

                    Text(
                        text = stringResource(id =R.string.get_started),
                        style = MaterialTheme.typography.h3
                    )
                    
                }
            }
        }
    }
}
  • 请注意,我们在按钮点击时使用 onClick lambda 参数导航到 HomeScreenDestination
  • 请确保您已经创建了 HomeScreen composable,请检查 ui/screens/home/HomeScreen
    它应该带有 @Destination 注解。在成功构建后,这将自动为您创建 HomeScreenDestination 文件。

Home Screen

图3

  • 您必须已经创建了HomeScreen可组合项(检查ui/screens/home/HomeScreen.kt
  • 主屏幕基本上由从上到下排列的项目组成——垂直(如上面的黑色箭头所示)
@Destination(style = StartScreenTransitionAnimation::class)
@Composable
fun HomeScreen(
    navigator: DestinationsNavigator,
    albumDatabaseViewModel: AlbumDatabaseViewModel = hiltViewModel()
){
//--> Home screen layout here
}

个人而言,我喜欢干净、可重复使用的代码。所以,我不会将整个主屏幕项目放在此组合中。我们将创建另一个名为 HomeScreenItems.kt 的组合,并将其放在我们的 HomeScreen 中。

//HomeScreen.kt
@Destination(style = StartScreenTransitionAnimation::class)
@Composable
fun HomeScreen(
    navigator: DestinationsNavigator,
    albumDatabaseViewModel: AlbumDatabaseViewModel = hiltViewModel()
){

.............// Check full code for other variables

    HomeScreenItems(navigator = navigator, albums = albums.value,
        onCardClicked = {
        shouldOpenAlbumDetails = true
        albumUrl = it
    },
        onPopularAlbumClicked = {
           shouldOpenTrendingAlbums = true
        }
    )

}

这是我们的HomeScreenItems.kt文件

//HomeScreenItems.kt
@Composable
fun HomeScreenItems(
    modifier: Modifier = Modifier,
    navigator: DestinationsNavigator,
    albums: List<Album>,
    albumDatabaseViewModel: AlbumDatabaseViewModel = hiltViewModel(),
    onCardClicked: (String) -> Unit,
    onPopularAlbumClicked: () -> Unit
) {

    ........

}
  • 该可组合接受许多参数。
    我将强调一下之前没有解释过的参数:

  • albums - 来自Room数据库实体/表的专辑列表

  • albumDatabaseViewModel - 数据源的viewModel

  • onCardClicked - 当卡片被点击时调用的Lambda函数

  • onPopularAlbumClicked - 当点击热门专辑时调用的Lambda函数

  • 由于我们希望我们的主屏幕可以滚动,并且随着我们滚动逐渐加载项目,因此我们将使用LazyColumn作为根布局。

  • LazyColumn是一个垂直滚动的可组合列表,仅组合和布局当前可见的项目。

//HomeScreenItems.kt
@Composable
fun HomeScreenItems(
    modifier: Modifier = Modifier,
    navigator: DestinationsNavigator,
    albums: List<Album>,
    albumDatabaseViewModel: AlbumDatabaseViewModel = hiltViewModel(),
    onCardClicked: (String) -> Unit,
    onPopularAlbumClicked: () -> Unit
) {

    LazyColumn(
        modifier = modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.bgHome)
            .padding(20.dp)
    ){
    
       .........
     
    }

}
  • 因此,我们想在 LazyColumn 中放置什么?非动态内容(单个静态项)和动态内容(变化的项)。
  • 从图3中,您会发现蓝色框和绿色框中的项目是非动态内容。它们不会改变。
  • 因此,我们将使用单个项目 lambda 函数来显示这些项。
  • 每个部分都有一个用于显示 UserHomeSection()SearchSection()PopularAlbumSection() 的组合体。
//HomeScreenItems.kt 
LazyColumn(
        modifier = modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.bgHome)
            .padding(20.dp)
    ){

        /**
         * Non-Dynamic Items
         */

        item {

            //first section
            UserHomeSection()

            //search home screen
            SearchSection(
                searchTextFieldValue = "",
                onSearchTextFieldValueChange = {  },
                onSearchTextFieldClicked = { navigator.navigate(SearchScreenDestination) },
                searchFieldPlaceHolder = R.string.search_albums,
                searchEnabled = false,
                showKeyboardOnStart = false
            )

            //popular item section
            PopularAlbumSection(
                cardTextTitle = R.string.popular,
                cardTextItem = R.string.top_trending_albums,
                cardImage = R.drawable.ic_character,
                onPopularAlbumCardClicked = {
                    //popular album clicked, go to apple music
                    onPopularAlbumClicked()
                }
            )

        }

      item{

          Text(
              modifier = modifier
                  .fillMaxWidth()
                  .padding(top = 12.dp),
                style = MaterialTheme.typography.h2,
                fontSize = 18.sp,
                color = MaterialTheme.colors.onSecondary,
                text = stringResource(id = R.string.all_albums)
            )

        }

       .................


     }

}
  • 这样做是为了分离关注点,并且主要是为了可重用性(例如,我可以在应用程序的其他位置使用SearchSection可组合部件,而无需复制SearchSection可组合部件中的整个代码)
  • 实际上,我在SearchScreen中使用了相同的可组合部件。
  • 请注意,在图3中,第一个框标记为蓝色。这只是表示项目从左到右(水平)放置。因此,我们需要使用名为Row的可组合部件。
  • Row用于在屏幕上水平放置项目。
  • 现在,对于我们从Room数据库获取的动态项目,我们将使用称为items的lambda函数来显示它们。
    我们完整的HomeScreenItems.kt如下:
//HomeScreenItems.kt
@Composable
fun HomeScreenItems(
    modifier: Modifier = Modifier,
    navigator: DestinationsNavigator,
    albums: List<Album>,
    albumDatabaseViewModel: AlbumDatabaseViewModel = hiltViewModel(),
    onCardClicked: (String) -> Unit,
    onPopularAlbumClicked: () -> Unit
) {

    LazyColumn(
        modifier = modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.bgHome)
            .padding(20.dp)
    ){

        /**
         * Non-Dynamic Items
         */

        item {

            //first section
            UserHomeSection()

            //search home screen
            SearchSection(
                searchTextFieldValue = "",
                onSearchTextFieldValueChange = {  },
                onSearchTextFieldClicked = { navigator.navigate(SearchScreenDestination) },
                searchFieldPlaceHolder = R.string.search_albums,
                searchEnabled = false,
                showKeyboardOnStart = false
            )

            //popular item section
            PopularAlbumSection(
                cardTextTitle = R.string.popular,
                cardTextItem = R.string.top_trending_albums,
                cardImage = R.drawable.ic_character,
                onPopularAlbumCardClicked = {
                    //popular album clicked, go to apple music
                    onPopularAlbumClicked()
                }
            )

        }

        /**
         * Dynamic Items
         */

        item{

            Text(
                modifier = modifier
                    .fillMaxWidth()
                    .padding(top = 12.dp),
                style = MaterialTheme.typography.h2,
                fontSize = 18.sp,
                color = MaterialTheme.colors.onSecondary,
                text = stringResource(id = R.string.all_albums)
            )

        }


        items(items = albums){ album->

            AlbumCard(
                album = album,
                onClickCard = { albumUrl->
                  //card clicked, go to details screen
                    onCardClicked(albumUrl)
                },
                onClickLike = { isLiked, albumId->
                    albumDatabaseViewModel.doUpdateAlbumLikedStatus(!isLiked, albumId)
                }
            )

        }

    }

}

我创建了一个名为AlbumCard的可重复使用的可组合项,我们可以将其用作显示所有动态项目的模型。
确保检查完整代码以正确理解其工作原理。

我希望我已经能够解释基本 UI 如何与 Jetpack Compose 一起工作。有关 UI 的更多信息,请查看官方文档

github代码网址

https://github.com/ibrajix/MyMusic

参考

https://ibrajix.medium.com/how-i-built-this-nice-looking-app-using-jetpack-compose-3974db7eb9e

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

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

相关文章

MySQL执行顺序

MySQL执行顺序 MySQL语句的执行顺序也是在面试过程中经常问到的问题&#xff0c;并且熟悉执行顺序也有助于SQL语句的编写。 SELECT FROM JOIN ON WHERE GROUP BY HAVING ORDER BY LIMIT执行顺序如下&#xff1a; FROM ON JOIN WHERE GROUP BY # (开始使用别名) SUM # SUM等…

4.4——多重继承

在前面学习了一个派生类只有一个基类&#xff0c;这种派生方法称为单继承或单基派生。当一个派生类具有两个或多个基类时&#xff0c;这种派生方法称为多重继承或多基派生。 多重继承派生类的声明 在C中&#xff0c;声明具有两个以上基类的派生类与声明单基派生类的形式相似&a…

K8S:二进制安装K8S(单台master)

目录 一、安装K8S 1、拓扑图​编辑 2、系统初始化配置 3、部署docker引擎 4、部署etcd集群 ①etcd简介 ②准备签发证书环境 ③etcd部署 5、master部署组件 6、 部署 Worker Node 组件 7、 node节点部署flannel网络插件 一、安装K8S 1、拓扑图​​​​​​​ 2、系…

C++好难(4):类和对象(下)

okk我们终于来到了C类和对象的最后一节&#xff0c;大多都是对之前学习的内容做的补充 所以加油继续冲啦&#xff01; ∧_∧::   (&#xff65;ω&#xff65;):: /⌒  ⌒):: /へ_&#xff3f; / /:: (&#xff3f;&#xff3c;&#xff3c; &#xff90;)/::   &am…

一种轻松且客观介绍大模型方式,避免过度解读:一

这是我关于《一种轻松且客观介绍大模型方式&#xff0c;避免过度解读》第一篇 一、前言 这篇文章旨在为没有计算机科学背景的读者提供一些关于ChatGPT及其类似的人工智能系统&#xff08;如GPT-3、GPT-4、Bing Chat、Bard等&#xff09;如何工作的原理。ChatGPT是一种聊天机器…

【网络安全】mysql数据库提权

数据库提权 数据库提权适用场景前提条件提权步骤如何探查数据库服务是否启动 mysql提权mysql 获取数据库最高权限账号密码方法1.读取网站配置文件2. 读取数据库存储或备份文件3. 脚本暴力破解 利用udf提权udf定义udf提权原理udf常用函数1. 获取mysql版本号2.udf.dll放置到被攻击…

华为P60 Pro灵犀双Wi-Fi流畅通信,让你体验超凡网速

家里装有宽带的朋友&#xff0c;用手机进行无线网络连接时&#xff0c;经常会发现有2.4G和5G两个Wi-Fi信号网络。Wi-Fi信号是通过无线电波传输的&#xff0c;2.4G和5G是两个不同的频段&#xff0c;Wi-Fi信号就在这样的频段内进行传输。 2.4G信号频率低&#xff0c;在空气或障碍…

【机器学习】集成学习(理论)

集成学习&#xff08;理论&#xff09; 目录 一、何为集成学习二、集成学习最简单的模型&#xff1a;投票策略三、弱学习器的组合算法&#xff1a;自助聚合&#xff08;Bagging模型&#xff09;1、数据划分方法&#xff1a;自助法&#xff08;Bootstrap Method&#xff09;2、B…

决策树的介绍

一、介绍 决策树 (decision tree) 是一类常见的机器学习方法。它是一种树形结构&#xff0c;其中每个内部节点表示一个属性上的判断&#xff0c;每个分支代表一个判断结果的输出&#xff0c;最后每个叶节点代表一种分类结果。 例如&#xff0c;我们要对"这是好瓜吗?&qu…

3.docker—应用部署MySQL

文章目录 1、mysql部署2、使用Navicat客户端来连接 docker应用部署 docker出现后&#xff0c;这些软件的安装会变得比较简单 1、mysql部署 四步走&#xff1a; 1️⃣搜索mysql镜像 2️⃣拉取mysql镜像 3️⃣创建容器 4️⃣操作 遇到问题&#xff1a; 容器内的网络服务和…

MySQL定时刷新数据

一、步骤 1.查看定时策略是否开启&#xff0c;查看命令: show variables like %event_sche%; 2.显示的 event_scheduler 为 OFF 时用以下命令开启: set global event_scheduler1; 3.创建存储过程 use toursim_platform; -- 选择数据库toursim_platform delimiter // create pro…

【刷题之路Ⅱ】LeetCode 86. 分隔链表

【刷题之路Ⅱ】LeetCode 86. 分隔链表 一、题目描述二、解题1、方法1——先分离再连接1.1、思路分析1.2、代码实现 2、方法2——将较大的节点后移2.1、思路分析2.2、代码实现 一、题目描述 原题连接&#xff1a; 86. 分隔链表 题目描述&#xff1a; 给你一个链表的头节点 head…

科大讯飞交卷,实测星火大模型

作者 | 辰纹 来源 | 洞见新研社 星星之火&#xff0c;可以燎原。 5月6日&#xff0c;讯飞星火认知大模型揭开神秘面纱。 发布会上&#xff0c;科大讯飞董事长刘庆峰、研究院院长刘聪现场实测了星火大模型七大核心能力&#xff0c;并发布基于该大模型的教育、办公、汽车和数字…

docker-mysql的几个问题

来水一篇文章 文章目录 问题一&#xff1a;问题2&#xff1a; 问题一&#xff1a; 在Navicat上执行大脚本mysql的sql文件时&#xff0c;出现插入数据报错的问题&#xff0c;查了一下innodb_log_file_size参数show variables like innodb_log_file_size;只有50331648即48M&…

WB_BF项目问题说明以及探究

我就现在WB_BF项目群里面提到的“根据测试脚本运行日志来看&#xff0c;bf运行了约31小时后又开始出现了api调用返回nginx的错误信息。之后&#xff0c;bf客户端也无法打开。”问题做一下说明&#xff0c;今天早上我在机器上复现了这一问题。针对于api调用会返回nginx的错误信息…

CTF权威指南 笔记 -第四章Linux安全机制-4.1-Linux基础

常用命令 这里给出linux常用命令 cd ls pwd 显示当前工作目录 uname 打印系统信息 whoami 打印用户名 man 查询帮助信息 find echo cat less head grep diff mv cp rm ps top kill touch 创建文件 mkdir 创建文件夹 chmod 变更权限 chown 变更所属者 nano 终端文本编辑器 e…

MySQL获取当前日期、时间、时间戳函数

目录 1.MySQL 获取当前日期时间 函数 1.1 获取当前日期&#xff08;date&#xff09;函数&#xff1a;curdate() 1.2 获取当前时间&#xff08;time&#xff09;函数&#xff1a;curtime() 1.3 获取当前日期时间&#xff08;date time&#xff09;函数&#xff1a;now() …

BClinux8.6 制作openssh9.3p1 rpm升级包和升级实战

一、背景说明 BClinux8.6 默认安装的openssh 版本为8.0&#xff0c;经绿盟扫描&#xff0c;存在高危漏洞&#xff0c;需要升级到最新。 官网只提供编译安装包&#xff0c;而BClinux8.6 为rpm方式安装。 为了方便升级&#xff0c;先通过编译安装包&#xff0c;制作rpm包&…

什么是无感电阻?无感电阻和普通电阻的区别

无感电阻&#xff0c;也称为电感电阻、电感器、电感元件等&#xff0c;是一种电气元件&#xff0c;常用于电子电路中&#xff0c;用于限制电流、防止电磁干扰等。无感电阻是指一种电阻器件&#xff0c;它能够在高频电路中工作而不会产生电感&#xff0c;从而避免了电感对电路性…

【Python】更改matplotlib绘图样式,要创建一个后缀名为mplstyle的样式清单,如何实现?

要更改 matplotlib 绘图样式&#xff0c;可以按照以下步骤创建一个后缀名为 mplstyle 的样式清单&#xff1a; 打开终端或 Anaconda Prompt&#xff08;Windows 用户&#xff09;&#xff1b;确保您的 Matplotlib 版本是 2.0.0 以上版本&#xff0c;通过运行&#xff1a; imp…