前几天开发新程序的时候,选择了jetpack最新的组件compose来构建应用程序的界面。但是因为compose面世不久,网上基本找不到太多相关的资料,想找一个新拟态风格的控件库但是找不到,也就只能自己写一个。
效果图:
这里以输入框为例,其他的空间做成新拟态的原理是一样的。
一、原理
其实新拟态的原理是很简单的,大致就是给控件加两个阴影,显示出光线明暗的变化,如上图所示,左上加的是白色的阴影,右下加的是灰色的阴影。
二、面临的困难
在compose里面,api实际上是进一步封装的,改变控件的modifier与阴影相关的api只有:
@Stable
public fun Modifier.shadow(
elevation: Dp,
shape: Shape,
clip: Boolean
): Modifier
这样一个。
看到elevation这个单词,应该不会感到陌生,这种阴影效果就是在Android5.0,apiLevel 21引入material design时引入的效果。
这种阴影效果只会出现在控件的右下角,没办法在控件的任意位置添加阴影,而且这种阴影效果是没办法指定颜色的。比如下图中按键的阴影:
三、实现的思路
可能谷歌官方也知道compose的功能比现有Android framework的功能还是稍显羸弱,所以在绘图方面,compose的Canvas是可以直接借助底层的Android framework绘图工具来绘制的。也就是:
public inline fun DrawScope.drawIntoCanvas(
block: (Canvas) → Unit
): Unit
这个api来调用底层的canvas,然后使用画笔,设置好阴影相关的底层属性,然后在canvas上面绘制两个阴影。
四、效果实现
修改控件的外观需要用到Modifier,所以一般习惯把实现封装成Modifier的扩展函数:
@RequiresApi(Build.VERSION_CODES.O)
fun Modifier.drawColoredShadow(
color: Color,
alpha: Float = 0.2f,
borderRadius: Dp = 0.dp,
shadowRadius: Dp = 20.dp,
offsetX: Dp = 0.dp,
offsetY: Dp = 0.dp,
roundedRect: Boolean = true
) = this.drawBehind {
}
其中参数的含义:
color:阴影的颜色;
alpha:阴影的不透明度;
borderRadius:绘制的圆角矩形阴影的圆角半径;
shadowRadius:阴影所使用的高斯模糊算法的模糊半径,这里高斯模糊算法,就是把一个点的色彩用周围一定范围内的图像的色彩来取均值(或者加权均值)。这个范围的大小就是半径了。
offsetX:阴影在水平方向的偏移,大于零则向右偏移,小于零则向左。
offsetY:阴影在竖直方向的偏移,大于零则向下偏移,小于零则向上。
roundedRect:是否两边都是半圆的圆角矩形。
之所以这个地方调用了this.drawBehind,是因为底层Android framework里面的阴影是依附于图形的。也就是说,设置好了阴影,你只有画一个图形,才能显示出图形的阴影。而我们并不想让这个图形显示出来而且不让它影响控件的触发事件,所以这个时候就要调用drawBehind把阴影依附的图形绘制到控件的下面(z轴方向,类似于前端css里面的after伪类)。
首先初始化两个颜色,一个是阴影依附的图形的颜色,这个地方为了不显示出来,这里直接把不透明度alpha值设置为0f,第二个是阴影的颜色:
val transparentColor = android.graphics.Color.toArgb(color.copy(alpha = .0f).value.toLong())
val shadowColor = android.graphics.Color.toArgb(color.copy(alpha = alpha).value.toLong())
然后调用drawIntoCanvas来调用底层android framework的canvas:
this.drawIntoCanvas {
}
在drawIntoCanvas里面,初始化一个画笔对象:
val paint = Paint()
val frameworkPaint = paint.asFrameworkPaint()
设置好画笔颜色和阴影效果:
frameworkPaint.color = transparentColor
frameworkPaint.setShadowLayer(
shadowRadius.toPx(),
offsetX.toPx(),
offsetY.toPx(),
shadowColor
)
上面这段代码中的setShadowLayer就是Android 底层设置阴影的api,第一个参数是阴影的半径,第二个是水平偏移,第三个是竖直偏移,第四个是阴影的颜色。
最后用画笔在canvas上面画一个跟控件尺寸一样,而且透明的图形,阴影就显示出来了:
it.drawRoundRect(
0f,
0f,
this.size.width,
this.size.height,
if(roundedRect) this.size.height / 2 else borderRadius.toPx(),
if(roundedRect) this.size.height / 2 else borderRadius.toPx(),
paint
)
在需要新拟态风的控件Modifier上调用这个扩展函数即可:
TextField(value = password, onValueChange = { password = it }, modifier = Modifier
.fillMaxWidth()
.background(Color.Transparent)
.padding(horizontal = 30.dp)
.drawColoredShadow(
Color.Black,
0.1f,
borderRadius = 25.dp,
shadowRadius = 10.dp,
offsetX = passwordAnimation.value.dp,
offsetY = passwordAnimation.value.dp
)
.drawColoredShadow(
Color.White,
0.9f,
borderRadius = 25.dp,
shadowRadius = 10.dp,
offsetX = (-passwordAnimation.value).dp,
offsetY = (-passwordAnimation.value).dp
)
)