Jetpack Compose入门详解(实时更新)

Posted 我怀里的猫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose入门详解(实时更新)相关的知识,希望对你有一定的参考价值。

Jetpack Compose入门详解


前排提醒

我知道点进来的人都是想学习JC的,所以可能都不知道环境怎么弄,事实上如果只是学习的话,安装了最新版的android studio后,创建项目时就可以构建一个Jetpack Compose,用于学习是再好不过了

前言(Compose是什么)

提示:需要对原生xml布局有一定了解,另外它最好是配合Kotlin 使用更佳

借用官方的解释:Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。

1.实战准备

因为是新东西,所以配置上和平常有点不一样,可以对照着加下依赖 和配置,有些不用的可以酌情添加,例如:navigation

app的build.gradle

  plugins 
    id 'com.android.application'
    id 'kotlin-android'


android 
    compileSdk 31

    defaultConfig 
        applicationId "com.zyf.myjetpack"
        minSdk 22
        targetSdk 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    

    buildTypes 
        release 
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        
    
    compileOptions 
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    
    kotlinOptions 
        jvmTarget = '1.8'
    
    buildFeatures 
        viewBinding true
        // Enables Jetpack Compose for this module
        compose true
    
    composeOptions 
        kotlinCompilerExtensionVersion '1.1.1'
    


dependencies 

    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
    // Integration with activities
    implementation 'androidx.activity:activity-compose:1.4.0'
    // Compose Material Design
    implementation 'androidx.compose.material:material:1.1.1'
    // Animations
    implementation 'androidx.compose.animation:animation:1.1.1'
    // Tooling support (Previews, etc.)
    implementation 'androidx.compose.ui:ui-tooling:1.1.1'
    // Integration with ViewModels
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
    // UI Tests
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
    // When using a AppCompat theme
    implementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"

    implementation  "androidx.compose.runtime:runtime-livedata:1.1.1"

    implementation   "androidx.compose.ui:ui:1.1.1"

    implementation   "androidx.navigation:navigation-compose:2.4.1"

    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'


project的build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript 
    repositories 
        google()
        jcenter()
        maven  url 'https://dl.bintray.com/kotlin/kotlin-eap' 
    
    dependencies 
        classpath "com.android.tools.build:gradle:7.0.3"
        //1.6.10版本
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    


task clean(type: Delete) 
    delete rootProject.buildDir


一、优势与缺点

它和安卓传统xml布局相比,又拥有以下几点优势

  • 更少的代码

    编写更少的代码会影响到所有开发阶段:作为代码撰写者,需要测试和调试的代码会更少,出现 bug 的可能性也更小,您就可以专注于解决手头的问题;作为审核人员或维护人员,您需要阅读、理解、审核和维护的代码就更少。
    与使用 Android View 系统(按钮、列表或动画)相比,Compose 可让您使用更少的代码实现更多的功能。无论您需要构建什么内容,现在需要编写的代码都更少了。

  • 直观
    Compose 使用声明性 API,这意味着您只需描述界面,Compose 会负责完成其余工作。这类 API 十分直观 - 易于探索和使用:“我们的主题层更加直观,也更加清晰。我们能够在单个 Kotlin 文件中完成之前需要在多个 XML 文件中完成的任务,这些 XML 文件负责通过多个分层主题叠加层定义和分配属性。”(Twitter)

  • 加速开发
    Compose 与您所有的现有代码兼容:您可以从 View 调用 Compose 代码,也可以从 Compose 调用 View。大多数常用库(如 Navigation、ViewModel 和 Kotlin 协程)都适用于 Compose,因此您可以随时随地开始采用。“我们集成 Compose 的初衷是实现互操作性,我们发现这件事情已经‘水到渠成’。我们不必考虑浅色模式和深色模式等问题,整个体验无比顺畅。”

  • 功能强大
    利用 Compose,您可以凭借对 Android 平台 API 的直接访问和对于 Material Design、深色主题、动画等的内置支持,创建精美的应用:“Compose 不仅解决了声明性界面的问题,还改进了无障碍功能 API、布局等各种内容。将设想变为现实所需的步骤更少了”;您可以轻松快速地通过动画让应用变得生动有趣:“在 Compose 中添加动画效果非常简单,没有理由不去为颜色/大小/高度变化添加动画效果”(Monzo),“不需要任何特殊的工具就能制作动画,这与显示静态屏幕没有什么不同”(Square)。

上面都是官方文档的官话,下面是我自己的归纳,上面提到的优点我就不赘述

优点

他是一套全新的声明式UI,完全不同于传统所有组件继承于臃肿庞大的view,而是基于更底层的canvas,简单来说,就是它的性能要比安卓原生的xml布局要好,比如xml的多重布局嵌套导致的一些问题,相信安卓开发对复杂页面嵌套优化都头疼过,只要你使用Compose,就不会遇到这样的问题

缺点

目前还是一个新的东西,大部分公司都还没有将Compose 纳入到项目当中,一些将Compose 融入到项目中的细节还没有敲定,例如:如何优雅的将viewmodel与Compose 绑定用于显示UI;一些技术点还待开发人员熟悉

说的好听支持java,但是大家也就图一乐,现在安卓官方主推的是什么语言大家心里都有数

二、前四课

安卓官方文档推出了Jetpack Compose的四课内容,带我们从开始到构建一个简单的聊天屏幕,如果你拥有安卓xml布局和Kotlin 的基础,那么这将非常的简单
安卓官方Jetpack Compose 教程

成果类似下图:

该屏幕显示包含图片和文字的可展开的动画消息列表,使用 Material Design 原则设计,添加了深色主题,具有预览功能,所有内容只需不到 100 行代码!

以下是您目前为止所学的内容:

  • 定义可组合函数
  • 在可组合项中添加不同的元素
  • 使用布局可组合项构建界面组件
  • 使用修饰符扩展可组合项
  • 创建高效列表
  • 跟踪状态以及修改状态
  • 在可组合项上添加用户互动
  • 在展开消息时显示动画效果

三、标准布局组件

在许多情况下,我们只需使用 Compose 的标准布局元素即可。

1.Column

使用 Column 可将多个项垂直地放置在屏幕上。

@Composable
fun ArtistCard() 
    Column 
        Text("Alfred Sisley")
        Text("3 minutes ago")
    

我们会得到如下布局

2.Row

同样,使用 Row 可将多个项水平地放置在屏幕上。Column 和 Row 都支持配置它们所含元素的对齐方式。

@Composable
fun ArtistCard(artist: Artist) 
    Row(verticalAlignment = Alignment.CenterVertically) 
        Image(/*这里是你的图片*/)
        Column 
            Text(artist.name)
            Text(artist.lastSeenOnline)
        
    

得到如下布局

3.Box

使用 Box 可将元素放在其他元素上。Box 还支持为其包含的元素配置特定的对齐方式。

@Composable
fun ArtistAvatar(artist: Artist) 
    Box 
        Image(/*这里是头像*/)
        Icon(/*这里是角标*/)
    

得到如下布局

通常,您只需要这些构建块。您可以自行编写可组合函数,将这些布局组合成更精美的布局,让其适合您的应用。

四、xml和compose混合使用 + livedata数据绑定

1.xml和compose混合使用

a.xml中使用compose

在xml中嵌入composeView,通过id: compose_home 绑定布局 ,这个时候就可以和原生xml布局混合使用

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.home.HomeFragment">

    <TextView
        android:id="@+id/xml_home"
        android:text="我是原生xml"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:textAlignment="center"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_home"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

kt代码(这里博主用的Fragment示例)

class HomeFragment : Fragment() 


    private var _binding: FragmentHomeBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!




    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? 
        
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        val root: View = binding.root
        //这里绑定了ComposeView
        val view = binding.composeHome
        view.setContent 
            AppCompatTheme
                HomePge()
            
        
        
        

        return root
    

    override fun onDestroyView() 
        super.onDestroyView()
        _binding = null
    


    @Preview
    @Composable
    private fun HomePge()
        Column() 
            Text(text = "我是 Jetpack Compose")
        

    




效果如下

b.compose中使用view

作为之前的补充,所以阅读起来可能会感到断层

通过跟评论区的博友沟通,我留意了在compose中使用view的相关方面,在补充的今天发现了AndroidView这一compose控件,它设计的初衷在于在compose中嵌套使用一些compose暂未支持的view控件(例如WebView, SurfaceView, AdView),当然也可以嵌入xml布局

我们先来看看源码

  • factory 是我们需要嵌套的view
  • modifier 修饰符
  • update 在布局膨胀后调用的回调,每当在该回调中读取的 State 发生变化时,AndroidView 都会重组。

使用代码如下

@Composable
fun XmlView()
    var selectedItem by remember  mutableStateOf(0) 

        AndroidView(factory =  context ->
            android.widget.Button(context).apply
                setOnClickListener
                    selectedItem += 1
                
            
        ,
        modifier = Modifier.fillMaxSize(),
        update = view ->
            view.text = selectedItem.toString()
        )

效果如下

(安卓官方提醒)最好在 AndroidView factory lambda 中构建一个 View,而不是使用 remember 在 AndroidView 之外保存对 View 的直接引用。

如需嵌入 XML 布局,请使用 androidx.compose.ui:ui-viewbinding 库提供的 AndroidViewBinding API。为此,您的项目必须启用视图绑定。
这边直接引入官方代码

@Composable
fun AndroidViewBindingExample() 
    AndroidViewBinding(ExampleLayoutBinding::inflate) 
        exampleView.setBackgroundColor(Color.GRAY)
    


可以看到代码中的ExampleLayoutBinding,这里的布局应该就是 exampl_layout,而exampleView则是xml布局中一个view的id

2.livedata数据绑定

创建 一个ViewModel如下:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class HomeViewModel : ViewModel() 


    private val _count = MutableLiveData(0)
    val count: LiveData<Int> = _count

    fun add() 
        _count.value = _count.value?.plus(1)
    


在HomePge()页面中稍作改动

    @Preview
    @Composable
    /*    括号里的HomeViewModel 是初始化viewModel,实际并不需要在传参中带进来
    viewModel()为androidx.lifecycle.viewmodel.compose中方法,等同于
    ViewModelProvider(this).get(HomeViewModel::class.java)*/
    private fun HomePge(viewModel: HomeViewModel = viewModel())
        Column() 
            Text(text = "我是 Jetpack Compose")
            //引用包import androidx.compose.runtime.livedata.observeAsState
            //.observeAsState()绑定 viewModel中的count值,当count发生改变Text组件将会重绘
            val count = viewModel.count.observeAsState()
            Text(
                text =count.value.toString(),
                style = MaterialTheme.typography.h5,
                fontSize = TextUnit.Unspecified,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = dimensionResource(R.dimen.dp20))
                    .wrapContentWidth(Alignment.CenterHorizontally)
                    .clickable 
                        //调用  viewModel的add()方法让其值+1
                        viewModel.add()
                        Toast.makeText(context,count.value.toString(), Toast.LENGTH_SHORT).show()
                    
            )
        

    

效果图

五.compose结合navigation使用

1.集成导航

先创建三个页面,分别为HomePage,DashboardPage和NotificationPage

@Composable
    fun HomePage()
        Text(
            "This is HomePage",
            textAlign = TextAlign.Center,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = dimensionResource(R.dimen.dp20))
                .wrapContentWidth(Alignment.CenterHorizontally)
        )
    

@Composable
    fun DashboardPage()
        Text(
            "This is DashboardPage",
            textAlign = TextAlign.Center,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = dimensionResource(R.dimen.dp20))
                .wrapContentWidth(Alignment.CenterHorizontally)
        )
    


    @Composable
    fun NotificationPage()
        Text(
            "This is NotificationPage",
            textAlign = TextAlign.Center,
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = dimensionResource(R.dimen.dp20))
                .wrapContentWidth(Alignment.CenterHorizontally)
        )
    

为了让代码看起来稍微规范些,我们创建了一个RouteConfig

object  RouteConfig 
    /**
     * homePage路由
     */
    const val ROUTE_HomePage = "Home"

    /**
     * dashboardPage路由
     */
    const val ROUTE_DashboardPage = "Dashboard"

    /**
     * dashboardPage路由
     */
    const val ROUTE_NotificationPage = "Notifications"

然后创建一个NavHost对象

@Composable
    fun MainNavHost()
        NavHost(navController = ,startDestination = ,
            )
           
        
    

NavHost对象需要两个必传参数,一个是NavController,一个是起始路由地址,NavController 对象是 Navigation 组件的中心 API,我们可以通过 rememberNavController创建,代码如下所示:

import androidx.navigation.compose.rememberNavController

val navController = rememberNavController()

我们接着往下写,我们先将navController作为参数传递进来,然后startDestination 设置HomePage为我们的启始路由,每一个composable中为页面添加路由(route)

    @Composable
    fun MainNavHost(navController:NavHostController)
        NavHost(navController = navController,
            startDestination = RouteConfig.ROUTE_HomePage,
            )
            composable(
                route = RouteConfig.ROUTE_HomePage,
                )
                HomePage()
            
            composable(
                route = RouteConfig.ROUTE_DashboardPage,
            )
                DashboardPage()
            
            composable(
                route = RouteConfig.ROUTE_NotificationPage,
            )
                NotificationPage()
            
        
    
  • RouteConfig.ROUTE_HomePage 对应 HomePage()页面
  • RouteConfig.ROUTE_DashboardPage 对应 DashboardPage()页面
  • RouteConfig.ROUTE_NotificationPage 对应 NotificationPage()

学习过navigation的小伙伴们应该知道BottomNavigationView这个组件,compose中也有相对应的组件名为BottomNavigation,我们也这里使用到了,并通过navController.navigate()方法实现了页面之间的跳转,值得一提的是,navController依旧是传递进来的

 @Composable
    fun MyBottomNavigation(navController:NavHostController)
        BottomNavigation(
            Modifier
                .fillMaxWidth()
                .height(64.dp)

        ) 
            BottomNavigationItem(
                true,
                onClick = 
                    navController.navigate(RouteConfig.ROUTE_HomePage)
                ,
                modifier = Modifier.padding(5.dp),
                icon = 
                    Image(
                        painter = painterResource(R.drawable.ic_home_black_24dp),
                        contentDescription = RouteConfig.ROUTE_HomePage,
                    )
                ,
                label = 
                    Text(
                        text = RouteConfig.ROUTE_HomePage,
                        color = Color.Black,
                        modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally)
                        )
                

            )
            BottomNavi

以上是关于Jetpack Compose入门详解(实时更新)的主要内容,如果未能解决你的问题,请参考以下文章

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门