Neumorphism简介

新拟态规划风格(Neumorphism)是一种近年来兴起的UI规划趋势,它结合了扁平化规划和拟物化规划的元素,创造出一种具有立体感和质感的视觉作用。

Compose Neumorphism新拟态风格底部导航控件--ShadeTabElevated

新拟态规划风格的特点是经过运用柔软的暗影和光线作用,让界面元素看起来像是凸起或洼陷的,似乎是真实的物体。这种风格常常运用淡色或中性色彩的布景,搭配柔软的色彩来营建温温暖舒适的感觉。

Compose Neumorphism新拟态风格底部导航控件--ShadeTabElevated

Compose Neumorphism新拟态风格底部导航控件--ShadeTabElevated

与传统的拟物化规划不同的是,新拟态规划愈加重视细节和柔软的过渡作用。它强调了元素之间的层次感和深度,运用户可以更直观地了解界面中不同元素之间的联系。

Compose Neumorphism新拟态风格底部导航控件--ShadeTabElevated

ShadeTabElevated简介

ShadeTabElevated是一个选用新拟态规划风格的底部导航控件,经过巧妙运用暗影和动画作用,运用户可以直观地了解界面元素之间的逻辑联系。

ShadeTabElevated是运用Jetpack Compose编写的控件,它的完成逻辑非常酷炫。让我们一起来共享一下代码的完成原理。

ShadeTabElevated的代码运用了Compose的声明式方法来描绘界面的外观和行为。它运用暗影作用和动画特性,使导航控件看起来像是凸起的,给用户一种立体感。

经过运用Compose供给的动画和过渡作用支撑,我们可以轻松地为界面增加各种交互和视觉作用。这使得ShadeTabElevated的界面愈加生动和有吸引力。

一起,ShadeTabElevated的代码还充分运用了数据驱动的思想,经过运用不行变数据模型和可观察状况,界面可以在数据改变时自动更新。这样可以简化界面的办理和保护,并供给更好的性能和可测试性。

Compose Neumorphism新拟态风格底部导航控件--ShadeTabElevated

完成思路

底部导航控件由布景控件和五个图标组成。在未选中状况下,图标呈灰色,位于布景控件的笔直中心,并且没有暗影作用。而在选中状况下,图标呈橙红色,位于布景控件上方,并且带有暗影作用。

完成这个控件时,主要需要处理以下两个问题:

  1. 为图标控件增加暗影作用
  2. 图标状况切换时增加动画作用

完成底部导航控件的代码思路是:经过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
                )
            }
            ....
}

ShadeTabElevated源码

ShadeTabElevated是Neumorphism新拟态风格组件的一种,想了解更多的组件,移步[Github](DingMouRen/ShadeCraft: Neumorphism-style Compose component)