Compose - 屏幕适配(尺寸、主题、状态栏、国际)
一、尺寸适配
目前 Compose 只能采用 dp 单位设置尺寸, Dp.roundToPx() 方法与 View 体系中 applyDimension() 方法一样最终都是通过 px = dp * density 公式将 dp 转为 px。
连接 Compose 和 View 体系之间的桥梁是 AndroidComposeView 类,而 AndroidComposeView 就通过 fun Density(context: Context) 方法来初始化其内部声明的 density 对象,CompositionLocals 类的 ProvideCommonCompositionLocals 方法又通过 LocalDensity 来将 AndroidComposeView 持有的 density 对象暴露给外部,从而使得框架内部和开发者均可以通过 LocalDensity.current 来获取到当前的 Density 对象,也即通过此方法拿到了 Android 系统的 density 和 fontScale 两个参数。
得益于 CompositionLocalProvider 特性,能细腻度的控制修改后的密度生效范围(当前Activity甚至某个组合中)而不会影响全局,由次解决了View体系适配中字节跳动方案的缺点。
1.1 dp
smallestWidth 方案也一样适用于Compose,通过 dimensionResource( ) 方法获取 res 中 dimen 值会转换为 Dp 类型,如果项目中已经使用了 smallestWidth 方案依然可以继续使用。
/**
* 根据UI设计图得出动态密度适配不同屏幕
* @param designWidth 填入UI设计图的屏幕短边dp值(绝对宽度)
* @param designHeight 填入UI设计图的屏幕长边dp值(绝对高度)
*/
@Composable
private fun dynamicDensity(designWidth: Float, designHeight: Float): Float {
val displayMetrics = LocalContext.current.resources.displayMetrics
val widthPixels = displayMetrics.widthPixels //屏幕短边像素(绝对宽度)
val heightPixels = displayMetrics.heightPixels //屏幕长边像素(绝对高度)
val isPortrait = LocalContext.current.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT //判断横竖屏
return if (isPortrait) widthPixels / designWidth else heightPixels / designHeight //计算密度
}
private val fontScale = LocalDensity.current.fontScale
private val appDensity = Density(density = dynamicDensity(360F, 640F), fontScale = fontScale)
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = {
CompositionLocalProvider(LocalDensity provides appDensity ) {
content()
}
}
)
1.2 sp
px = sp * fontScale * density,fontScale 代表当前系统字体的缩放比例,通过 LocalDensity.current.fontScale 获取该值,可以自由控制应用内文字的显示大小。
1.3 BoxWithConstraints
提供了一些现有参数,用来根据可用空间调用不同的布局方案。
单位dp | minWidth、minHeight、maxWidth、maxHeight |
单位px | constraints.minWidth constraints.minHeight constraints.maxWidth constraints.maxHeight |
BoxWithConstraints {
//根据可用宽度显示不同布局方案
if(maxWidth < 400.dp) {
Column{...}
} else {
Row{...}
}
}
二、主题适配(颜色)
创建项目之后,就会生成一个 项目名称+Theme 的 @Compose 函数,我们可以通过更改其中的颜色来完成对主题的修改。硬编码在配置的地方把值写死会无法支持主题切换,应该从主题中引用对应值,由于每个值都是实例还能通过 copy() 修改。
2.1 定义不同版本的颜色名称和值
方式一:在 Color.kt 中定义颜色的不同版本名称和值。
object AppColors {
//正文
val textPrimary_Light = Color(0xCC000000)
val textPrimary_Dark = Color(0xFF626567)
//红色
val red = Color(0xFFFF0000)
val red50 = Color(0x80FF0000)
//透明
val transparent = Color(0x00FFFFFF)
}
方式二:在 res/values/colors.xml 中定义颜色的不同版本名称和值。
<resources>
//正文
<color name="textPrimary_Light">#CC000000</color>
<color name="textPrimary_Dark">#FF626567</color>
//红色
<color name="red">#FFFF0000</color>
<color name="red50">#80FF0000</color>
//透明
<color name="transparent">#00FFFFFF</color>
</resources>
2.2 定义颜色集基类
- 在 Theme.kt 中创建颜色集类。
- 构造中塞入颜色名称供创建实例的时候指定具体颜色。
- 定义对应的属性并通过 MutableState 包裹提供状态,以便发生改变时所有引用该颜色的组合项会重组。
- 提供 copy() 支持调用处修改某个颜色。(可选)
class ThemeColors(
//定义需要的颜色名称
textPrimary: Color,
background: Color,
) {
//颜色是有状态的,发生改变时所有引用这个颜色的组合项都会重组
var textPrimary by mutableStateOf(textPrimary)
private set
var background by mutableStateOf(background)
private set
//提供copy支持调用处修改某个颜色
// fun copy(
// textPrimary: Color = this.textPrimary,
// background: Color = this.background,
// ): ThemeColors = ThemeColors(
// textPrimary = textPrimary,
// background = background,
// )
}
2.3 创建不同的颜色集实例(不同的主题色)
在 Theme.kt 中,创建不同的颜色集实例(即不同的主题),构造中指定具体的颜色值。暂不推荐从xml中读取,通过主题调用的颜色显示错误,未找到原因
//亮色主题(举例从 Color.kt 中读取)
private val LightColors = ThemeColors(
textPrimary = AppColors.TextPrimary_Light,
background = AppColors.transparent,
)
//暗色主题(举例从 colors.xml 中读取)
private val DarkColors = ThemeColors(
textPrimary = Color(R.color.textPrimary_Dark),
background = Color(R.color.transparent),
)
2.4 定义全局调用入口
在 Theme.kt 中,声明一个由 CompositionLocal 包装的变量用来获取颜色集实例,声明一个单例用作全局调用入口,还包含了对类型的判断。
//声明一个CompositionLocal变量供调用处获取自定义的颜色集实例
//颜色集对象中的颜色值是确定的不会更改的,因此使用staticCompositionLocalOf 的提高性能
private val LocalAppColors = staticCompositionLocalOf { LightColors }
//声明一个单例,用作全局入口
object AppTheme {
//用于获取自定义的颜色集
val colors: ThemeColors
@Composable get() = LocalAppColors.current
//提供枚举类用于传参判断类型
enum class ColorsType {
LIGHT, DARK
}
}
2.5 提供切换功能
@Composable
fun AppTheme(
colorsType: AppTheme.ColorsType = AppTheme.ColorsType.LIGHT, //指定颜色集
darkTheme: Boolean = isSystemInDarkTheme(), //自动识别是否开启暗黑模式
content: @Composable () -> Unit //需要包裹的内容
) {
//获取指定的颜色方案并添加动画(避免切换的时候一闪)
val themeColors = if (darkTheme) addAnimationToAPPColors(DarkColors) else{
when (colorsType) {
AppTheme.ColorsType.LIGHT -> addAnimationToThemeColors(LightColors)
AppTheme.ColorsType.DARK -> addAnimationToThemeColors(DarkColors)
}
}
//提供出去
CompositionLocalProvider(LocalAppColors provides themeColors){
//同时使用MaterialTheme的话就在这里包裹一下
content()
}
}
2.6 添加切换动画(避免直接一闪)
/**
* 为自定义颜色集添加过度动画(避免直接一闪)
*/
@Composable
private fun addAnimationToThemeColors(themeColors: ThemeColors) = ThemeColors(
textPrimary = animateColorAsState(themeColors = targetColor.textPrimary, TweenSpec(600), label = "textPrimary").value,
background = animateColorAsState(themeColors = targetColor.background, TweenSpec(600), label = "background ").value,
)
2.7 组合项中调用颜色
Text(text = "", color = AppTheme.colors.textPrimary)
三、沉浸式状态栏
谷歌 Accompamost 的 SystemUiController 已废弃,建议使用 androidx.activity 1.8.0-alpha03+ 添加的 enableEdgeToEdge() 方法。
implementation "androidx.activity:activity-ktx:1.8.0-alpha07"
onCreate() {
WindowCompat.setDecorFitsSystemWindows(window,false) //使内容全屏
enableEdgeToEdge() //使透明状态栏
}
四、国际化
一样是提供多套strings文件。
更多推荐
所有评论(0)