一个例子学会使用 Jetpack Compose Modifier

简介: Modifier是Compose中的重要概念,能够让UI呈现更加专业、好看的视觉效果。

Modifier是Compose中的重要概念,能够让UI呈现更加专业、好看的视觉效果。

1. 为什么使用Modifier?

常规的View体系中,控件以实例对象的形式存在,控件可以在实例化之后再动态配置属性,但是Composable本质上是函数,只能在调用的同时通过参数传递进行配置,如果没有Modifier,参数签名会变得很长(虽然Kotlin支持默认参数)。

使用Modiifer可以很好地解决这个问题,它就像Composable的配置文件,可以在此对Composable的样式和行为进行统一配置。

2. Modifier是一组有序的链式调用

Modifier通过链式调用“装饰”我们的Composable,为其添加BackgroundPaddingonClick事件等。

链上的每个操作符都创建一个Element,整个调用链是一组Element的有序执行单元

Text(
    "Hello, World!",
    Modifier.padding(16.dp) // Outer padding; outside background
        .background(color = Color.Green) // Solid element background color
        .padding(16.dp) // Inner padding; inside background, around text
)

如上,调用链上的两个padding不是覆盖关系,而是按照顺序发挥作用。

padding创建PaddingModifier

class PaddingModifier(val start: Dp, val top : d'p...) : Modifier.Element

fun Modifier.padding(all: Dp) = this.then(PaddingModifier(start = all, top = all, xxx))

then用来组合两个Modifier并保持顺序执行。

open infix fun then(other: Modifier): Modifier
Modifier类似RxJava的Observable,基于 函数式编程思想,将操作符串联成一组可执行函数,在Composable渲染的时候才执行。

3. 使用Modifier装饰Composable

Modifier的操作符(API)虽然数量多,但是语义明确,上手不难。下面通过一个例子带大家体验一下如何使用Modifier装饰我们的Composable。

我们试着用Compose实现一个关注列表的Item,如下

@Composable
fun Plain() {
    Row(modifier = Modifier.fillMaxWidth()) {
        Image(
            modifier = Modifier.size(40.dp),
            bitmap = imageResource(id = R.drawable.miku),
            contentDescription = null, // decorative
        )
        Column(modifier = Modifier.weight(1f)) {
            Text(text = name, maxLines = 1)
            Text(text = desc, maxLines = 1)
        }
        Text("Follow", Modifier.padding(6.dp))
    }

}

接下来,我们一步步通过Modifier对其进行装饰,在文章最后,UI将达到下面第二张图片的效果。
在这里插入图片描述

3.1 整体布局

Modifier.padding

我们使用RowColumn对Item内的元素进行了基本布局,但是元素之间缺少间距
在这里插入图片描述

Compose通过Modifier在Composable之间添加Padding

在这里插入图片描述

@Composable
fun Decorated() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .preferredHeightIn(min = 64.dp)
            .padding(8.dp) //外间隙
            .border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))
            .padding(8.dp) //内间隙

    ) {
          ...
    }
}

如上,我们对Item整体添加Padding。
border前后各有一个padding,分别表示对外和对内的间距。相对于传统布局有MarginPadding区分,Modifier中只有padding,根据调用链中的位置不同发挥不同作用,使用更加简单。

Modifier.border

border用来定义边框,RoundedCornerShape是一个Shape类型,用来指定边框的形状为圆角矩形。

我们还可以调用两次background来实现border的效果:

modifier = Modifier
    .background(Color.LightGray) 
    .padding(1.dp) //两个backgound之间形成边框
    .background(Color.White)

Modifier.preferredHeight / Modifier.preferredHeightIn

preferedXXX等用来设置初始的size,例如preferedHeight可以设置Composable的默认高度,这个值可能被其他约束覆盖,若想要高度不被覆盖,就使用Modifier.height设置固定值

本例中使用preferedHeightIn,可以设置minHeightmaxHeight

Modifier.fillMaxWidth

fillMaxWidth表示填充整个父容器,相当于传统布局的match_parent

3.2 参数中传入Modifier

填充Row中的内容,从左往右依次是,头像、文字、按钮

在这里插入图片描述

@Composable
fun Decorated() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .preferredHeight(64.dp)
            .padding(8.dp)
            .border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))
            .padding(8.dp)

    ) {

        Avatar( //头像部分
            modifier = Modifier
                .padding(4.dp)
                .align(Alignment.CenterVertically)
        )

        Info( //文字部分
            Modifier
                .weight(1f)
                .align(Alignment.CenterVertically)
        )

        FollowBtn( //按钮
            Modifier.align(Alignment.CenterVertically)
        )
    }
}

我们将具体实现抽成独立的Composable,在Row中调用并传入Modifier。

在Compose中定义Composable时,为Modifier预留参数位置是一个好习惯

Modifier为调用方提供了修改子元素样式的机会,但更重要的是有一些操作符只能在外部调用。

Modifier.align

Modifier的操作符都是扩展函数,并不是定义在一起。操作符定义在不同的空间中,可以限制某些操作符只能在特定父Comopsable中使用,避免误用。

interface RowScope {
    fun Modifier.align(alignment: Alignment.Vertical)
}

如上,align只能在Row中调用,用来设置子元素在垂直方向如何对齐。子元素不关心其在父容器中如何对齐,因此在外部设置align(Alignment.CenterVertically)后,传给子元素继续使用。

Modifier.weight

weight同样只能在Row中调用,为子元素分配在Row中的占比,类似于LinearLayoutlayout_weight。本例中让中间的文字部分占据所有所有空间

3.3 头像图片

在这里插入图片描述

我们对头像图片做圆形处理并添加边框,提升整体视觉效果。

@Composable
fun Avatar(modifier: Modifier) {
    Image(
        modifier = modifier
            .size(50.dp)
            .clip(CircleShape)
            .border(
                shape = CircleShape,
                border = BorderStroke(
                    width = 2.dp,
                    brush = Brush.linearGradient(
                        colors = listOf(blue, teal200, green200, orange),
                        start = Offset( 0f, 0f),
                        end = Offset(100f,100f)
                    )
                )
            )
            .border(
                shape = CircleShape,
                border = BorderStroke(4.dp, SolidColor(Color.White))
            ),
        bitmap = imageResource(id = R.drawable.miku),
        contentDescription = null, // decorative
    )

}

Modifier.size

首先size(50.dp) 设置图片的整体大小

Modifier.clip

clip用来将图片裁剪成指定形状,例子中clip(CircleShape)将图片裁剪成圆形

Modifier.border调用顺序

图片的边框由两部分组成,外层带颜色的部分,和内层的白色边框,因此调用链中出现了两个border()。两个border的调用顺序需要特备注意,border表示为后面的调用添加边框,所以在前面调用的后生效。所以例子中的border调用顺序如下:

Modifier
.border() //2dp 颜色边框
.border() //4dp 白色边框

BorderStroke & Brush

border使用BorderStroke填充边框颜色。

外边框使用Brush.linearGradient填充多种颜色组成的渐变色,startend表示颜色范围

BroderStroke(
    brush = Brush.linearGradient(
         colors = listOf(blue, teal200, green200, orange),
         start = Offset( 0f, 0f),
         end = Offset(100f,100f)
    )
)

内边框使用SolidColor填充固定颜色

BorderStroke(brush = SolidColor(Color.White))

3.4 文字部分

在这里插入图片描述

@Composable
fun Info(modifier: Modifier) {
    Column(modifier = modifier) {
        Text(
            text = name,
            color = Color.Black,
            maxLines = 1,
            style = TextStyle(
                fontWeight = FontWeight.Bold,
                fontSize = 16.sp,
                letterSpacing = 0.15.sp
            )
        )
        Text(
            text = desc,
            color = Color.Black.copy(alpha = 0.75f),
            maxLines = 1,
            style = TextStyle( // here
                fontWeight = FontWeight.Normal,
                fontSize = 14.sp,
                letterSpacing = 0.25.sp
            )
        )
    }
}

许多字体的样式不借助Modifier,而是通过Text自身的属性以及TextStyle设置

文字颜色

color设置文字颜色,Compose的Color类功能强大, 例如这里可以设置透明度:Color.Black.copy(alpha = 0.75f)

TextStyle

TextStyle可以设置字体、字号等,例子中通过fontWeight设置了粗体

textDecoration

虽然本例中没有使用,但是Text还有一个重要属性textDecoration,对文字进行更有针对性的“装饰”,例如添加下划线、删除线等

textDecoration = TextDecoration.Underline

3.5 按钮

在这里插入图片描述

虽然有Compose提供了专门的Button实现按钮,使用Text同样可以实现按钮,而且可定制性更高。

@Composable
fun FollowBtn(modifier: Modifier) {
    val backgroundShape: Shape = RoundedCornerShape(4.dp)

    Text(
        text = "Follow",
        style = typography.body1.copy(color = Color.White),
        textAlign = TextAlign.Center,
        modifier =
        modifier
            .preferredWidth(80.dp)
            .clickable(onClick = {})
            .shadow(3.dp, shape = backgroundShape)
            .clip(backgroundShape)
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        Red500,
                        orange700,
                    ),
                    startY = 0f,
                    endY = 80f
                )
            )
            .padding(6.dp)
    )
}

Modifier.background

为按钮添加了渐变的背景色以及阴影后,显得更加拟物、有质感
background中同样通过Brush添加渐变色

Modifier.shadow

在这里插入图片描述

shadow添加阴影,需要特别shadow在调用链中的位置,阴影本身也是占用面积的,所以要在background之前调用,避免阴影进入背景色区域中

Modifier.click

Text之所以可以替代Button实现按钮,是因为Modifier提供了click,可以让Composable处理onClick事件

4. 最后

在这里插入图片描述
通过上面的例子,相信大家已经掌握了Modifier的基本使用方式,Modifier还有很多高级的API,后续有机会陆续分享给大家。Modifier在设计上吸取了装饰器模式、FP等多种编程思想,思路巧妙,值得大家学习。

<br/>

【参考】

Sample Code

More Modifiers API Reference

目录
相关文章
|
7月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
7月前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
4月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
103 4
|
6月前
|
存储 移动开发 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;
|
7月前
|
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()`。
|
7月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
7月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
7月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
7月前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
7月前
|
存储 Android开发
安卓app,MediaPlayer播放本地音频 | 按钮控制播放和停止
在Jetpack Compose中,不直接操作原生Android组件如`Button`和`MediaPlayer`,而是使用Compose UI构建器定义界面并结合ViewModel管理音频播放逻辑。以下示例展示如何播放本地音频并用按钮控制播放/停止:创建一个`AudioPlayerViewModel`管理`MediaPlayer`实例和播放状态,然后在Compose UI中使用`Button`根据`isPlaying`状态控制播放。记得在`MainActivity`设置Compose UI,并处理相关依赖和权限。
下一篇
开通oss服务