Neumorphism简介
新拟态规划风格(Neumorphism)是一种近年来兴起的UI规划趋势,它结合了扁平化规划和拟物化规划的元素,创造出一种具有立体感和质感的视觉作用。
新拟态规划风格的特点是经过运用柔软的暗影和光线作用,让界面元素看起来像是凸起或洼陷的,似乎是真实的物体。这种风格常常运用淡色或中性色彩的布景,搭配柔软的色彩来营建温温暖舒适的感觉。
与传统的拟物化规划不同的是,新拟态规划愈加重视细节和柔软的过渡作用。它强调了元素之间的层次感和深度,运用户可以更直观地了解界面中不同元素之间的联系。
ShadeTabElevated简介
ShadeTabElevated是一个选用新拟态规划风格的底部导航控件,经过巧妙运用暗影和动画作用,运用户可以直观地了解界面元素之间的逻辑联系。
ShadeTabElevated是运用Jetpack Compose编写的控件,它的完成逻辑非常酷炫。让我们一起来共享一下代码的完成原理。
ShadeTabElevated的代码运用了Compose的声明式方法来描绘界面的外观和行为。它运用暗影作用和动画特性,使导航控件看起来像是凸起的,给用户一种立体感。
经过运用Compose供给的动画和过渡作用支撑,我们可以轻松地为界面增加各种交互和视觉作用。这使得ShadeTabElevated的界面愈加生动和有吸引力。
一起,ShadeTabElevated的代码还充分运用了数据驱动的思想,经过运用不行变数据模型和可观察状况,界面可以在数据改变时自动更新。这样可以简化界面的办理和保护,并供给更好的性能和可测试性。
完成思路
底部导航控件由布景控件和五个图标组成。在未选中状况下,图标呈灰色,位于布景控件的笔直中心,并且没有暗影作用。而在选中状况下,图标呈橙红色,位于布景控件上方,并且带有暗影作用。
完成这个控件时,主要需要处理以下两个问题:
- 为图标控件增加暗影作用
- 图标状况切换时增加动画作用
完成底部导航控件的代码思路是:经过Compose的润饰符为图标控件增加暗影作用,并运用动画支撑完成图标状况切换的动画作用。
代码完成
1.图标控件增加暗影作用
Compose中运用Modifier润饰符来装饰控件款式,我们同样运用这个思路,为Modifier创立增加暗影的扩展函数,那么一切的控件都可以运用这一特性。
- 为Mdifier创立backgroundShadow的扩展函数,参数如下
fun Modifier.backgroundShadow(
shadowColorLight: Color = Color(ConstantColor.THEME_LIGHT_COLOR_SHADOW_LIGHT),//淡色暗影色彩
shadowColorDark: Color = Color(ConstantColor.THEME_LIGHT_COLOR_SHADOW_DARK),//深色暗影色彩
blurRadius:Float = 8f,//暗影含糊系数
lightSource: Int = LightSource.DEFAULT,//光源方向
offset:Float = 10f,//暗影偏移量
cornerRadius:Dp = 0.dp,//暗影圆角巨细
shape:Int = Shape.Rectangle,//暗影形状
borderWidth :Dp = 20.dp,//Shape.Circle中作为圆环宽度
)
- 暗影画笔设置抗锯齿、防抖、色彩、含糊作用
//淡色暗影画笔
val paintShadowLight = Paint().also { paint: androidx.compose.ui.graphics.Paint ->
paint.asFrameworkPaint() //将自界说的制作操作转换成底层烘托引擎可以了解的烘托描绘目标,从而完成愈加高效和灵敏的制作操作。
.also {nativePaint: NativePaint ->
nativePaint.isAntiAlias = true //设置抗锯齿
nativePaint.isDither = true //敞开防抖
nativePaint.color = shadowColorLight.toArgb() //设置画笔色彩
if (offset>0)nativePaint.maskFilter = BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL) //设置含糊滤镜作用
}
}
//深色暗影画笔
val paintShadowDark = Paint().also { paint: androidx.compose.ui.graphics.Paint ->
paint.asFrameworkPaint() //将自界说的制作操作转换成底层烘托引擎可以了解的烘托描绘目标,从而完成愈加高效和灵敏的制作操作。
.also {nativePaint: NativePaint ->
nativePaint.isAntiAlias = true //设置抗锯齿
nativePaint.isDither = true //敞开防抖
nativePaint.color = shadowColorDark.toArgb() //设置画笔色彩
if (offset>0)nativePaint.maskFilter = BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL) //设置含糊滤镜作用
}
}
- 获取不同暗影在光源方向的偏移量
//淡色暗影在光源方向的偏移量
val backgroundShadowLightOffset:Offset = when(lightSource){
LightSource.LEFT_TOP -> Offset(-offset,-offset)
LightSource.LEFT_BOTTOM -> Offset(-offset,offset)
LightSource.RIGHT_TOP -> Offset(offset, -offset)
LightSource.RIGHT_BOTTOM -> Offset(offset, offset)
else -> {Offset(0f,0f)}
}
//深色暗影在光源方向的偏移量
val backgroundShadowDarkOffset:Offset = when(LightSource.opposite(lightSource)){
LightSource.LEFT_TOP -> Offset(-offset,-offset)
LightSource.LEFT_BOTTOM -> Offset(-offset,offset)
LightSource.RIGHT_TOP -> Offset(offset, -offset)
LightSource.RIGHT_BOTTOM -> Offset(offset, offset)
else -> {Offset(0f,0f)}
}
- 制作浅/深色暗影
首先,我们保存画布的当时状况,以便在制作完成后可以恢复。然后,经过translate
函数将画布平移,以便制作暗影的偏移量。
接下来,依据形状的类型,运用不同的制作方法来制作暗影。假如形状是圆形,则运用drawCircle
方法来制作圆形的暗影。我们传入圆心的偏移量(依据组件的巨细计算得出),以及半径(组件宽度减去边框宽度的一半),以及画笔paintShadowLight
来制作暗影的款式和色彩。
假如形状是矩形,则运用drawRoundRect
方法来制作圆角矩形的暗影。我们传入矩形的四个角的坐标、圆角的半径(经过将cornerRadius
转换为像素值)以及画笔paintShadowLight
来制作暗影的款式和色彩。
//画布平移制作淡色暗影
it.save()
it.translate(backgroundShadowLightOffset.x,backgroundShadowLightOffset.y)
when(shape){
Shape.Circle ->{
paintShadowLight.style = PaintingStyle.Stroke
paintShadowLight.strokeWidth = borderWidth.toPx()
it.drawCircle(
Offset(this.size.width/2,this.size.height/2),
(this.size.width - borderWidth.toPx() )/2,
paintShadowLight
)
}
Shape.Rectangle ->{
it.drawRoundRect(
0f,
0f,
this.size.width,
this.size.height,
cornerRadius.toPx(),
cornerRadius.toPx(),
paintShadowLight
)
}
}
it.restore()
//画布平移制作深色暗影
it.save()
it.translate(backgroundShadowDarkOffset.x,backgroundShadowDarkOffset.y)
when(shape){
Shape.Circle ->{
paintShadowDark.style = PaintingStyle.Stroke
paintShadowDark.strokeWidth = borderWidth.toPx()
it.drawCircle(
Offset(this.size.width/2,this.size.height/2),
(this.size.width - borderWidth.toPx() )/2,
paintShadowDark
)
}
Shape.Rectangle ->{
it.drawRoundRect(
0f,
0f,
this.size.width,
this.size.height,
cornerRadius.toPx(),
cornerRadius.toPx(),
paintShadowDark
)
}
}
it.restore()
}
2.自界说可增加暗影作用的图标控件
ShadeImageButton是新拟态风格的图标控件。规划思路如下:
- 组件继承自Compose结构的@Composable注解润饰的函数。
- 增加暗影作用。设置淡色/深色暗影色彩、含糊系数、光源方向、暗影偏移量、圆角巨细。
- 增加交互。对手势的点击操作进行监听。
- 操控形状。操控图标布景的圆角巨细。
- 显现图标。经过Image控件来显现图标。
ShadeImageButton的代码如下:
@Composable
fun ShadeImageButton(
modifier: Modifier = Modifier,//润饰器
shadowColorLight: Color = Color(THEME_LIGHT_COLOR_SHADOW_LIGHT),//淡色暗影色彩
shadowColorDark: Color = Color(THEME_LIGHT_COLOR_SHADOW_DARK),//深色暗影色彩
blurRadius: Float = BlurProvider.getDefaultBlurRadius(App.context()),//暗影含糊系数
lightSource: Int = LightSource.DEFAULT,//光源方向,默许从左上向右下
offset: Float = 20f,//暗影偏移量
cornerRadius: Dp = 0.dp,//圆角巨细,操控暗影和图标布景的圆角。
onClick: () -> Unit,//点击监听
backgroundColor: Color = NeumorphismLightBackgroundColor,//布景色彩
size: Dp,//图标巨细
painter: Painter,
contentDescription: String? = null,
iconColor: Color? = null,//图标色彩
shape: Int = Shape.Rectangle,//暗影/图标布景形状
iconPadding: Dp = 0.dp //图标内边距
){
var currentOffset by remember { mutableStateOf(offset) }
LaunchedEffect(offset) { // 运用LaunchedEffect监听offset的改变
currentOffset = offset
}
var currentCornerRadius by remember { mutableStateOf(cornerRadius) }
if (shape == Shape.Circle) {
currentCornerRadius = (size + size / 5 * 2) / 2
}
Card(
modifier = modifier
.backgroundShadow(//设置图标暗影
shadowColorLight,
shadowColorDark,
blurRadius,
lightSource,
currentOffset,
currentCornerRadius,
)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
currentOffset = 0f
}
MotionEvent.ACTION_UP -> {
currentOffset = offset
onClick()
}
}
true
},
shape = RoundedCornerShape(currentCornerRadius),//设置圆角巨细
elevation = 0.dp
) {
Box(
Modifier
.background(backgroundColor)//设置布景色彩
.padding(size / 5)
) {
Image(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier
.align(Alignment.Center)//对齐方法
.size(size)//巨细
.padding(iconPadding),//设置内边距
colorFilter = if (iconColor == null) null else ColorFilter.tint(iconColor),//设置图标色彩
)
}
}
}
ShadeTabElevated底部导航完成
ShadeTabElevated是一个自界说的组件,用于显现带暗影作用的选项卡。该组件完成的规划思路如下:
- 运用@Composable注解将函数声明为可组合函数。
- 界说ShadeTabElevated函数,接受一些参数用于自界说组件的外观和行为。
- 运用BoxWithConstraints组件包裹整个组件,以便获取容器的宽度和高度。
- 运用remember和mutableStateOf创立一个可变的currentIndex变量,用于跟踪当时选中的选项卡的索引。
- 界说一些动画所需的变量和特点,包含偏移量、巨细和色彩。运用animateDpAsState和animateColorAsState函数创立动画作用,并在点击选项卡时更新currentIndex的值。
- 运用Row和Box组件创立一个水平布局,用于显现多个选项卡。
- 在每个选项卡的Box中,运用ShadeImageButton组件来显现带暗影作用的圆形图标按钮。依据currentIndex的值确认当时选中的选项卡,并依据动画特点来设置按钮的巨细、偏移量、布景色彩和图标色彩。
- 在点击选项卡时,更新currentIndex的值,并调用回调函数onSelectedChange来告诉父组件选项卡的改变。
示例代码:
//上下移动动画
val animateOffset_0 :Dp by animateDpAsState(
targetValue = if (currentIndex.value == 0) offsetUp else 0.dp,
animationSpec = tween( durationMillis = 300)
)
Row(modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()) {
Box(modifier = Modifier.weight(1f)){
ShadeImageButton(
modifier = Modifier
.align(Alignment.Center)
.offset(0.dp, -animateOffset_0),
onClick = {
currentIndex.value = 0
onSelectedChange.invoke(0)
},
size = if (currentIndex.value == 0) imgSize * 1.2f else imgSize,
painter = painterResource(id = R.drawable.time),
shape = Shape.Circle,
iconColor = animateColor_0,
offset = if (currentIndex.value == 0) offsetShadowDefault else 0f,
backgroundColor = if (currentIndex.value == 0) backgroundColor else Color.White,
blurRadius = blurRadius
)
}
....
}