Android | Compose 初上手(上)

简介: Android | Compose 初上手(上)

简介


Jetpack Compose 是用于构建原生 Andorid 界面的新工具包,Compose 使用了更少的代码,强大的工具和直观的 Kotlin Api 简化并且加快了 Android 上界面的开发。


在 Compose 中,在构建界面的时候,无需在像之前那么构建 XML 布局,只需要调用 Jetpack Compose 函数来声明你想要的的元素,Compose 编译器就会自动帮你完成后面的工作。


注解


@Compose


所有的组合函数都必须添加 @Compose 注解才可以。


被 @Compose 注解的方法只能被同类型的方法调用。


@Preview


使用该注解的方法可以不在运行 App 的情况下就可以查看布局。@Preview 中常用的参数如下:


name: String: 为该Preview命名,该名字会在布局预览中显示。


showBackground: Boolean: 是否显示背景,true为显示。


backgroundColor: Long: 设置背景的颜色。


showDecoration: Boolean: 是否显示Statusbar和Toolbar,true为显示。


group: String: 为该Preview设置group名字,可以在UI中以group为单位显示。


fontScale: Float: 可以在预览中对字体放大,范围是从0.01。


widthDp: Int: 在Compose中渲染的最大宽度,单位为dp。


heightDp: Int: 在Compose中渲染的最大高度,单位为dp。


Compose 编程思想


Jetpack COmpose 是一个适用于 android 的新式声明性界面工具包。Compose 提供了声明性 API ,可以在不以命令的方式改变前端视图的情况下呈现应用界面,从而使得编写和维护界面变得更加容易。


申明性编程范式


长期以来,android 的视图结构一直可以表示为界面微件数。由于应用的状态会因用户交互等因素而发生变化,因此界面层次结构需要进行更新以显示当前的数据,最常见的就是 findviewById 等函数遍历树,并调用设置数据的方法等改变节点,这些方法会改变微件的内部状态


再过去的几年中,整个行业已经转向声明性界面模型,该模型大大的简化了构建和更新界面管理的工程设计,改技术的工作原理是在改建上重头生成整个屏幕,然后执行必要的更改。此方法可以避免手动更新有状态视图结构的复杂性。Compose 是一个声明性的界面框架。


重新生成整个屏幕所面临的一个难题是,在时间,计算力和电量方面可能成本高昂,为了减轻这一成本,Compose 会智能的选择在任何时间需要重新绘制界面的那些部分。这回对设计界面的组件有一定影响。


组合函数


Jetpack Compose 是围绕可组合函数构建的,这些函数就是要显示在界面上的元素,在函数中只需要描述应用界面形状和数据依赖关系,而不用去关系界面的构建过程,


如果需要创建组合函数,只需要将 @Composeable 注解添加到对于的函数上即可,需要注意的是组合函数的名称一般都是以大写字母开头的,如下:


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PrimaryTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Greeting("Android")
                }
            }
        }
    }
}
@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!", fontSize = 18.sp, color = Color.Red)
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    PrimaryTheme {
        Greeting("Android")
    }
}


setContent 块 定义了 Activity 的布局,我们不需要去定义 XML 的布局内容,只需要在其中调用组合函数即可。


上面的 一个简单的示例Greeting 微件,它接收 String 而发出的一个显示问候消息的 Text 微件。此函数不会返回任何内容,因为他们描述所需的屏幕状态,而不是构造界面微件。


其中 Greeting 就是一个非常简单的可组合函数,里面定义了一个 Text,顾名思义,就是用来显示一段文本


并且,我们可以在 Test 函数上添加 @PreView 注释,这样就可以非常方便的进行预览。


声明式范式转变


在 Compose 的声明方法中,微件相对无状态,并且不提供 get,set 方法。实际上,微件微件不会以对象的形式提供。你可以通过调用带有不同参数的统一可组合函数来更新界面。这使得架构模式,如 ViewModel 变得很容易。


引用逻辑为顶级可组合函数提供数据。该函数通过调用其他可组合函数来使用这些数据来描述界面。将适当的数据传递给这些可组合函数,并沿层次结构向下传递数据。



当用户与界面交互时,界面发起 onClick事件。这些事件会通知应用逻辑,应用逻辑可以改变应用状态。当状态发生变化时,系统就会重新调用可组合函数。这回导致重新绘制界面描述,此过程称为重组。


动态内容


由于可组合函是 kotlin 编写的,因此他们可以像任何 kotlin 代码一样动态,例如,假设你想要的构建一个界面,如下:


@Composable
fun Greeting(names: List<String>) {
    for (name in names) {
        Text("Hello $name")
    }
}


此函数接受一个列表,每位每个列表元素生成一个 Text。可组合函数可能性非常复杂,你可以使用 if 语句来确定是否需要显示特定的界面元素。例如循环,辅助函数等。你拥有地城语言的灵活性,这种强大的功能和灵活性是 JetpackCompose 的主要优势之一。


重组


在 Compose 中,你可以用新数据再次调用某个可组合函数,这回导致组合函数重新进行重组。系统会根据需要使用新数据重新绘制发出的微件。Compose 框架可以只能的重组已经更改的组件。


例如,下面这个可组合函数,用于显示一个按钮:


@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("I've been clicked $clicks times")
    }
}


每次点击按钮,就会更新 clicks 的值,Compose 会再次调用 lambda 与 Text 函数以显示新值,此过程称为 重组。不依赖该值的其他元素不会重组。


重组是指在输入更改的时候再次调用可组合函数的过程。当函数更改时,会发生这种情况。当 Compose 根据新输入重组时,它仅调用可能已经更改的函数或 lambad,而跳过其余函数或 lambda。通过跳过岂会为更改参数的函数或者 lambda ,Compose 可以高效的重组。


切勿依赖于执行可组合函数所产生的附带效应,因为可能会跳过函数的重组,如果这样做,用户可能在应用中遇到奇怪且不可预测的行为。例如:


写入共享对象的属性

更新 viewmodel 中的可观察项

更新共享偏好设置

可组合函数可能会每一帧一样的频繁执行,例如呈现动画的时候。所以可组合函数需要快速执行,所以避免在组合函数中出现卡顿,如果你需要执行高昂的操作,请在狗太协程中执行,并将结果作为参数传递给可组合函数。


例如下面代码,应该将 sp 读取的操作放在 viewmode 中,然后在回调中触发更新:


@Composable
fun SharedPrefsToggle(
    text: String,
    value: Boolean,
    onValueChanged: (Boolean) -> Unit
) {
    Row {
        Text(text)
        Checkbox(checked = value, onCheckedChange = onValueChanged)
    }
}


可组合函数可以按照任何顺序执行


如果你看到了可组合函数的代码,可能会认为他们按照顺序运行。但实际上未必是这样。如果某个可组合函数包含对其他组合代码的调用,这些函数可以按照顺序执行。


Compose 可以选择识别出某些界面元素的优先级高于其他界面元素,因此首先绘制这些元素。


假设你有如下代码:


@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}


对于这三个的调用可以按照任何顺序进行。这意味着你不能让某个函数设置一个全局变量(附带效应),并让别的函数利用这个全局变量而发生更改。所以每个函数都应该独立。


可组合函数可以并行运行


Compose 可以通过并行运行可组合函数来优化重组。这样依赖,Compose 就可以利用多个核心,并按照较低的优先级运行可组合函数(不在屏幕上)


这种优化方方式意味着可组合函数可能会在后台的线程池中执行,如果某个可组合函数对 viewModel 调用一个函数,则 Compose 可能会同时从多个线程调动该函数。


为了确保应用可以正常运行,所有的组合都不应该有附带效应,而应该通过始终在界面线程上执行的 onClick 等回调触发附带效应。


调用某个可组合函数时,调用可能发生在与调用方不同的线程上。这意味着,应避免修改可组合函数 lambda 中的变量代码,基因为此类代码并非线程安全代码,又因为他是可组合 lambda 不允许的附带效应。


下面展示了一个可组合函数,他显示了一个列表已经数量。


@Composable
fun ListComposable(myList: List<String>) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
            }
        }
        Text("Count: ${myList.size}")
    }
}


此函数没有附带效应,他会将输出列表转为界面。才代码非常适合展示小列表。不过此函数写入局部变量,则这并不是非线程安全或者正确的代码:


@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}


在上面例子中,每次重组都会修改 items。这可以在动画的第一帧,或者在列表更新的时候。但不管怎么样,界面都会显示出错误的数量。因此 Compose 不支持这样的写入操作。通过静止此类操作,我们允许框架更改线程以执行可组合 lambda。


重组跳过尽可能多的内容


如果界面某些部分无需,Compose 会尽力只重组需要更新的部分。这意味着,他可以跳过某些内容以重新运行单个按钮的可组合项,而不执行树中其上面或下面的任何可组合项。


每个可组合函数和 lambda 都可以自行重组。以下演示了在呈现列表时重组如何跳过某些元素:


/**
 * Display a list of names the user can click with a header
 */
@Composable
fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) -> Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.h5)
        Divider()
        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name ->
                // When an item's [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}
/**
 * Display a single name the user can click.
 */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}


这些作用域中的每一个都可能是在重组期间执行唯一一个作用域。当 header 发生更改时,Compose 可能会跳至 Column lambda 。二部执行他的任何父项。此外,执行 Colum 时,如果 names 未更改,Compose 可能会旋转跳过 LazyColum 的项。


同样,执行所有组合函数或者 lambda 都应该没有附带效应。当需要执行附带效应时,应该通过回调触发。


重组是乐观操作


只要 Compose 任务某个可组合函数可能已经更改,就会开始重组。重组是乐观操作,也就是说 Compose 预计会在参数再次更改之前完成重组。如果某个参数在重组完成之间发生改变,Compose 可能会取消重组,并使用新的参数重新开始。


取消重组后,Compose 会从重组中舍弃界面树。如有附带效应依赖于显示的界面,即使取消了组成操作,也会应用该附带效应。这可能导致应用状态不一致。


确保每个可组合函数和 lambda 都幂等,且没有附带效应,以处理乐观的重组


可组合函数可能会非常频繁的运行


在某些情况下,可能针对界面每一帧运行一个可组合函数,如果该函数成本高昂,可能会导致界面卡顿。


例如,你的微件重试读取设备配置,或者读取 sp,他可能会在一秒钟内读取这些数据上百次,这回对性能造成灾难性的影响。


如果您的可组合函数需要数据,它应为相应的数据定义参数。然后,您可以阿静成本高昂的工作移到其他线程,并使用 mutableStateOf 或者 LiveData 将相应的数据传递给 Compose。


主题


//深色
val DarkColorScheme = darkColors(
    primary = Purple80,
    onPrimary = Color(0xFFFFFFFF),
    secondary = PurpleGrey80,
)
//亮色
val LightColorScheme = lightColors(
    primary = Purple40,
    onPrimary = Color(0xFF333333),
    secondary = PurpleGrey40,
)
@Composable
fun PrimaryTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = LightColorScheme,
        typography = Typography,
        content = content
    )
}


默认的主题定义如上所示,最终会调用 MaterialTheme。


Material 主题主要包含三个属性,分别是 颜色,排版,和内容,Api 如下:


@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors, // 颜色集合
    typography: Typography = MaterialTheme.typography, // 排版集合
    shapes: Shapes = MaterialTheme.shapes, // 形状集合
    content: @Composable () -> Unit // 要展示的内容
)


颜色


class Colors(
    primary: Color, // 主颜色,屏幕和元素都用这个颜色
    primaryVariant: Color, // 用于区分主颜色,比如app bar和system bar
    secondary: Color, // 强调色,悬浮按钮,单选/复选按钮,高亮选中的文本,链接和标题
    secondaryVariant: Color, // 用于区分强调色
    background: Color, // 背景色,在可滚动项下面展示
    surface: Color, // 表层色,展示在组件表层,比如卡片,清单和菜单(CardView,SheetLayout,Menu)等
    error: Color, // 错误色,展示错误信息,比如TextField的提示信息
    onPrimary: Color, // 在主颜色primary之上的文本和图标的颜色
    onSecondary: Color, // 在强调色secondary之上的文本和图标的颜色
    onBackground: Color, // 在背景色background之上的文本和图标的颜色
    onSurface: Color, // 在表层色surface之上的文本和图标的颜色
    onError: Color, // 在错误色error之上的文本和图标的颜色
    isLight: Boolean // 是否是浅色模式
)


更多的可以查看 lightColorScheme 函数。


排版


@Immutable
class Typography internal constructor(
    val h1: TextStyle,
    val h2: TextStyle,
    val h3: TextStyle,
    val h4: TextStyle,
    val h5: TextStyle,
    val h6: TextStyle,
    val subtitle1: TextStyle,
    val subtitle2: TextStyle,
    val body1: TextStyle,
    val body2: TextStyle,
    val button: TextStyle,
    val caption: TextStyle,
    val overline: TextStyle
)


形状


class Shapes( 
    // 小组件使用的形状,比如: Button,SnackBar,悬浮按钮等
    val small: CornerBasedShape = RoundedCornerShape(4.dp),
    // 中组件使用的形状,比如Card(就是CardView),AlertDialog等
    val medium: CornerBasedShape = RoundedCornerShape(4.dp),
    // 大组件使用的形状,比如ModalDrawer或者ModalBottomSheetLayout(就是抽屉布局和清单布局)
    val large: CornerBasedShape = RoundedCornerShape(0.dp),
)


使用


setContent {
    PrimaryTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.color.background
        ) {
            Greeting("Android")
        }
    }
}


@Composable
fun PrimaryTheme(
    themeType: ThemeType = themeTypeState.value,
    content: @Composable () -> Unit
) {
    val shapes = Shapes(
        small = RoundedCornerShape(4.dp),
        medium = RoundedCornerShape(8.dp),
        large = RoundedCornerShape(12.dp),
    )
    MaterialTheme(
        colors = getThemeForTheme(themeType),
        typography = Typography,
        shapes = shapes,
        content = content
    )
}


另外,我写了一个可动态切换的主题,有兴趣的可以看一下


UI


SetContent
setContent {
    PrimaryTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            Greeting("Android")
        }
    }
}


同 Android 中的 SetContentView。


Theme


创建项目之后,就会生成一个 项目名称+Theme 的 @Compose 方法,我们可以通过更改其中的颜色来完成对主题的修改。具体如上面的主题所示.


Modifier


Modifier 本质是一个接口,可以用来修饰各种布局,例如 宽高,padding 等,常见的如下:


padding:有四个重载方法

plus:将其他的 Modifer 加入到当前的 Modifer 中。

fillMaxHeight,fillMaxWidth,fillmaxSize:类似于 match_parent,填充整个父 Layout

with,height,size :设置宽高度

rtl,ltr:开始布局的方向

widthIn,heightIn,sizeIn 设置布局的宽度和高度的最大值和最小值

gravity:元素的位置,

等等

需要注意的是 Modifier 系列的方法都支持链式调用


Column,Row


类似于 LinearLayout,Column 是横向的,Row 是竖向的。有四个参数:


Modifer: 具体值如上述所示


verticalArrangement:子元素竖向的排列规则


常见的就是,上下左右中,比较特殊的就是


SpaceEvenly 均匀分配,


SpaceBetween 第一个元素前和最后一个元素后没有空隙,其他的按比例放入。


SpaceAround 把整体中的一半空隙凭据放入第一个和最后一个的开始和结束,剩余的一半等比放入各个元素。


horizontalAlignment:和上面一个,只不过方向不同


content:要显示的内容


相关文章
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
449 11
|
存储 移动开发 Android开发
使用kotlin Jetpack Compose框架开发安卓app, webview中h5如何访问手机存储上传文件
在Kotlin和Jetpack Compose中,集成WebView以支持HTML5页面访问手机存储及上传音频文件涉及关键步骤:1) 添加`READ_EXTERNAL_STORAGE`和`WRITE_EXTERNAL_STORAGE`权限,考虑Android 11的分区存储;2) 配置WebView允许JavaScript和文件访问,启用`javaScriptEnabled`、`allowFileAccess`等设置;3) HTML5页面使用`<input type="file">`让用户选择文件,利用File API;
|
算法 Android开发
Compose - Text 详解,2024年Android社招面试题精选
Compose - Text 详解,2024年Android社招面试题精选
Compose - Text 详解,2024年Android社招面试题精选
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:

热门文章

最新文章