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

如何是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

猜你喜欢

转载自blog.csdn.net/u011897062/article/details/130561025
今日推荐