Brush
刷子,用于创建纹理和渐变图像
linearGradient
start和end的取值范围和元素大小有关,下面的Box为200dp所以用with(LocalDensity.current) { 200.dp.toPx() }
转成了Float
var startX by remember {
mutableStateOf(0f)
}
var startY by remember {
mutableStateOf(0f)
}
var endX by remember {
mutableStateOf(0f)
}
var endY by remember {
mutableStateOf(0f)
}
var tileMode by remember {
mutableStateOf(0)
}
var startType by remember {
mutableStateOf(0)
}
var endType by remember {
mutableStateOf(0)
}
val max = with(LocalDensity.current) { 200.dp.toPx() }
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Slider(modifier = Modifier.padding(horizontal = 30.dp), enabled = startType == 1,value = startX, valueRange = 0f..max, onValueChange = {
startX = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), enabled = startType == 1, value = startY, valueRange = 0f..max, onValueChange = {
startY = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), enabled = endType == 1,value = endX, valueRange = 0f..max, onValueChange = {
endX = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), enabled = endType == 1, value = endY, valueRange = 0f..max, onValueChange = {
endY = it
})
Column(modifier = Modifier.padding(bottom = 20.dp)) {
Row(modifier = Modifier.clickable {
startType = 0
}) {
RadioButton(selected = startType == 0, onClick = null)
Text(text = "Offset.Zero")
}
Row(modifier = Modifier.clickable {
startType = 1
}) {
RadioButton(selected = startType == 1, onClick = null)
Text(text = "Slider")
}
}
Column(modifier = Modifier.padding(bottom = 20.dp)) {
Row(modifier = Modifier.clickable {
endType = 0
}) {
RadioButton(selected = endType == 0, onClick = null)
Text(text = "Offset.Infinity")
}
Row(modifier = Modifier.clickable {
endType = 1
}) {
RadioButton(selected = endType == 1, onClick = null)
Text(text = "Slider")
}
}
val tileModeLabel = listOf("Clamp","Repeated","Mirror","Decal")
val tileModeList = listOf(TileMode.Clamp,TileMode.Repeated,TileMode.Mirror,TileMode.Decal)
Column {
for (i in 0 until 4) {
Row(modifier = Modifier.clickable {
tileMode = i
}) {
RadioButton(selected = tileMode == i, onClick = null)
Text(text = tileModeLabel[i])
}
}
}
Box(modifier = Modifier
.size(200.dp)
.background(
Brush.linearGradient(
0f to Color.Red,
.3f to Color.Green,
.5f to Color.Blue,
1f to Color.White,
start = if (startType == 0) Offset.Zero else Offset(startX, startY) ,
end = if (endType == 0) Offset.Infinite else Offset(endX, endY),
tileMode = tileModeList[tileMode]
)
)) {
Text(text = "test")
}
}
另一种方式:
Brush.linearGradient(listOf(Color.Red,Color.Green, Color.Blue))
horizontalGradient和verticalGradient
线性渐变
var startX by remember {
mutableStateOf(0f)
}
var endX by remember {
mutableStateOf(0f)
}
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Slider(modifier = Modifier.padding(horizontal = 30.dp),value = startX, valueRange = 0f..1000f, onValueChange = {
startX = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), value = endX, valueRange = 0f..1000f, onValueChange = {
endX = it
})
Text("startX:$startX,endX:$endX")
Box(
modifier = Modifier
.size(200.dp)
.background(
brush = Brush.horizontalGradient(listOf(Color.Red, Color.Yellow), startX = startX, endX = endX),
shape = CircleShape
)
)
Box(
modifier = Modifier
.size(200.dp)
.background(
brush = Brush.horizontalGradient(
0f to Color.Blue,
.3f to Color.Black,
.7f to Color.Yellow,
1f to Color.Red
), shape = CircleShape
)
)
}
sweepGradient
var x by remember {
mutableStateOf(0f)
}
var y by remember {
mutableStateOf(0f)
}
var centerType by remember {
mutableStateOf(0)
}
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Slider(modifier = Modifier.padding(horizontal = 30.dp), enabled = centerType==1,value = x, valueRange = 0f..1000f, onValueChange = {
x = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), enabled = centerType==1, value = y, valueRange = 0f..1000f, onValueChange = {
y = it
})
if(centerType == 1) {
Text("x:$x,y:$y")
}
Column(modifier = Modifier.padding(bottom = 20.dp)) {
Row(modifier = Modifier.clickable {
centerType = 0
}) {
RadioButton(selected = centerType == 0, onClick = null)
Text(text = "Offset.Unspecified")
}
Row(modifier = Modifier.clickable {
centerType = 1
}) {
RadioButton(selected = centerType == 1, onClick = null)
Text(text = "Slider")
}
}
Box(
modifier = Modifier
.size(200.dp)
.background(
brush = Brush.sweepGradient(listOf(Color.Red,Color.Blue), center = if(centerType == 0) Offset.Unspecified else Offset(x,y)),
shape = CircleShape
)
)
}
radialGradient
var centerX by remember { mutableStateOf(0f) }
var centerY by remember { mutableStateOf(0f) }
var radius by remember { mutableStateOf(350f) }
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Slider(modifier = Modifier.padding(horizontal = 30.dp),value = centerX, valueRange = 0f..700f, onValueChange = {
centerX = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), value = centerY, valueRange = 0f..700f, onValueChange = {
centerY = it
})
Slider(modifier = Modifier.padding(horizontal = 30.dp), value = radius, valueRange = 0f..700f, onValueChange = {
radius = it
})
Text(text = "x:$centerX")
Text(text = "y:$centerY")
Text(text = "radius:$radius")
Box(
modifier = Modifier
.size(200.dp)
.background(
brush = Brush.radialGradient(listOf(Color.Red,Color.Blue), center = Offset(centerX,centerY), radius = radius),
shape = CircleShape
)
)
}
ShaderBrush
哪个元素使用customBrush则size参数就是哪个元素的大小
val customBrush = remember {
object : ShaderBrush() {
override fun createShader(size: Size): Shader {
return LinearGradientShader(
colors = listOf(Color.Yellow, Color.Red, Color.Blue),
from = Offset.Zero,
to = Offset(size.width, 0f),
tileMode = TilesMode.Clamp
)
// return SweepGradientShader()
// return RadialGradientShader()
}
}
}
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Box(
modifier = Modifier
.size(200.dp)
.background(
brush = customBrush,
shape = CircleShape
)
)
Box(
modifier = Modifier
.size(100.dp)
.background(
brush = customBrush,
shape = CircleShape
)
)
}
ImageBitmap创建ShaderBrush
val a = ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.my_img), tileModeX = TileMode.Mirror, tileModeY = TileMode.Mirror))
Box(
modifier = Modifier
.size(200.dp)
.background(
a
)
)
DrawScope
Modifier.drawWithContent、Modifier.drawBehind、Canvas元素等涉及这个作用域并提供一些方法属性
作用域内可以用dp.toPx转为float(px)而不依赖Destiny
基本属性
size //Size,宽高px
size.center // Offset,中心点
size.width //float,宽度px
size.height //float,高度px
size.toDpSize() //转为DpSize
size.toIntSize() //转为IntSize
density // Float,屏幕密度,density = 1.dp
drawRect
drawRect(
brush = Brush.linearGradient(listOf(Color.Red, Color.Blue), end = Offset(100.dp.toPx(), 100.dp.toPx())),
topLeft = Offset(100f, 100f),
size = Size(100.dp.toPx(), 100.dp.toPx())
)
drawRect(
color = Color.Black,
topLeft = Offset(100f, 100.dp.toPx()+150f),
size = Size(100.dp.toPx(), 100.dp.toPx()),
style = Stroke(width = 3.dp.toPx())
)
drawLine
drawLine(
brush = Brush.linearGradient(listOf(Color.Yellow, Color.Black)),
start = Offset(10.dp.toPx(), 10.dp.toPx()),
end = Offset(300.dp.toPx(), 10.dp.toPx()),
strokeWidth = 8.dp.toPx()
)
drawLine(
color = Color.Blue,
start = Offset(10.dp.toPx(), 22.dp.toPx()),
end = Offset(200.dp.toPx(), 22.dp.toPx()),
strokeWidth = 8.dp.toPx()
)
drawText
String和AnnotatedString
//AnnotatedString
val textMeasurer = rememberTextMeasurer()
Box(modifier = Modifier
.size(width = 400.dp, height = 600.dp)
.background(Color.Gray)
.drawWithContent {
val annotatedString = buildAnnotatedString {
this.append("内容1")
withStyle(SpanStyle(color = Color.Red, fontSize = 25.sp)) {
this.append("修改样式")
this.append("修改样式2")
}
this.append("内容2")
this.append("内容2")
this.append("内容2")
this.append("内容2")
this.append("内容2")
this.append("内容2")
this.append("内容2")
this.append("内容2")
}
drawText(
textMeasurer = textMeasurer,
text = annotatedString,
style = TextStyle(),
topLeft = Offset(0f, 0f),
maxLines = 4,
overflow = TextOverflow.Ellipsis,
placeholders = listOf(),
// size = Size(300f,300f), //约束大小
softWrap = true/* false:文本只渲染一行,多余的按overflow处理(maxLine失效)。true:按照maxLine去渲染对应行数,超出则按overflow处理 */
)
}) {
Text(text = "666")
}
//String
drawText(
textMeasurer = textMeasurer,
text = "",
)
TextLayoutResult
不会检测边界自动换行,用\r\n可以换行
val annotatedString = buildAnnotatedString {
this.append("内容1")
withStyle(SpanStyle(color = Color.Red, fontSize = 50.sp)) {
this.append("修改样式")
this.append("修改样式2")
}
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
this.append("内容1")
}
val m = textMeasurer.measure(annotatedString)
drawText(
textLayoutResult = m,
brush = Brush.linearGradient(listOf(Color.Red, Color.Black)),
drawStyle = Stroke(width = 2.dp.toPx()),
topLeft = Offset(10f,10f),
shadow = Shadow(),
textDecoration = TextDecoration.Underline
)
drawText(
textLayoutResult = m,
color = Color.Blue,
drawStyle = Stroke(width = 2.dp.toPx()),
topLeft = Offset(10f,50.sp.toPx()+10f),
shadow = Shadow(),
textDecoration = TextDecoration.Underline
)
drawArc
startAngle的0f表示的是三点钟,-90f表示的是12点钟。sweepAngle的取值范围是0f~360f
@Composable
fun HomeScreen() {
var sweep by remember { mutableStateOf(0f) }
var useCenter by remember { mutableStateOf(false) }
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Row {
Text("useCenter")
Checkbox(checked = useCenter, onCheckedChange = {
useCenter = it
})
}
Slider(modifier = Modifier.padding(horizontal = 20.dp), valueRange = 0f..360f,value = sweep, onValueChange = {
sweep = it
})
Box(modifier = Modifier
.size(400.dp)
.background(Color.Gray)
.drawWithContent {
drawContent()
drawArc(
color = Color.Blue,
startAngle = -90f,
sweepAngle = sweep,
useCenter = useCenter,
size = Size(200.dp.toPx(), 200.dp.toPx()),
topLeft = Offset(0f, 0f),
style = Stroke(width = 5.dp.toPx())
)
drawArc(
brush = Brush.linearGradient(listOf(Color.Red, Color.Green)),
startAngle = -90f,
sweepAngle = sweep,
useCenter = useCenter,
size = Size(200.dp.toPx(), 200.dp.toPx()),
topLeft = Offset(0f, 210.dp.toPx()),
)
}) {
Text(text = "666")
}
}
}
drawCircle
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Row {
Text("useCenter")
Checkbox(checked = useCenter, onCheckedChange = {
useCenter = it
})
}
Slider(modifier = Modifier.padding(horizontal = 20.dp), valueRange = 0f..360f,value = sweep, onValueChange = {
sweep = it
})
Box(modifier = Modifier
.size(width = 400.dp, height = 600.dp)
.background(Color.Gray)
.drawWithContent {
drawContent()
drawCircle(color = Color.Blue, radius = 100.dp.toPx(), center = Offset(100.dp.toPx(), 100.dp.toPx()))
drawCircle(brush = Brush.linearGradient(listOf(Color.Red, Color.Magenta)), radius = 100.dp.toPx(), center = Offset(100.dp.toPx(), 300.dp.toPx()))
drawCircle(brush = Brush.linearGradient(listOf(Color.Red, Color.Magenta)), radius = 100.dp.toPx(), center = Offset(100.dp.toPx(), 500.dp.toPx()), style = Stroke(width = 8.dp.toPx()))
}) {
Text(text = "666")
}
}
drawOval
绘制椭圆,drawCircle指定大小时是传递半径,所以只能画出圆。如果Size传递相同的宽高也能绘制出圆形
drawOval(
brush = Brush.linearGradient(listOf(Color.Red, Color.Blue)),
size = Size(200.dp.toPx(), 100.dp.toPx()),
style = Stroke(width = 5.dp.toPx())
)
drawOval(
color = Color.Blue,
size = Size(200.dp.toPx(), 100.dp.toPx()),
style = Stroke(width = 5.dp.toPx()),
topLeft = Offset(0f, 110.dp.toPx())
)
drawPoints
绘制一个或多个点
drawPoints(
color = Color.Red,
points = listOf(
Offset(5.dp.toPx(), 5.dp.toPx()),
Offset(19.dp.toPx(), 5.dp.toPx()),
Offset(33.dp.toPx(), 5.dp.toPx())
),
pointMode = PointMode.Points,
strokeWidth = 10.dp.toPx()
)
drawRoundRect
绘制可圆角化的矩形
drawRoundRect(
brush = Brush.linearGradient(listOf(Color.Red, Color.Magenta)),
size = Size(150.dp.toPx(), 150.dp.toPx()),
cornerRadius = CornerRadius(x = 30f, y = 30f)
)
Transform相关
inset(inset = 20.dp.toPx()) { //为绘制内容设置4个方向的相同边距
this@drawWithContent.drawContent()
}
inset(horizontal = 10f, vertical = 20f) //为横竖设置不同量的边距
inset(top = 5f, right = 10f, bottom = 20f, left = 30f) //为4个方向设置不同量的边距
translate(200f,200f) { //平移
this@drawWithContent.drawContent()
}
rotate(degrees = 360f/* 度数,0~360 */, pivot = Offset(0f,0f)) { //以角度为单位旋转
this@drawWithContent.drawContent()
}
rotateRad(radians = sweep/* 弧度,0~2π */, pivot = Offset(0f,0f)) { //以弧度为单位旋转
this@drawWithContent.drawContent()
}
scale(scale = 1.5f, pivot = Offset(0f,0f)) { //缩放(左右和上下相同比例)
this@drawWithContent.drawContent()
}
scale(scaleX = 1.5f, scaleY = 1f, pivot = Offset(0f,0f)) { //缩放(左右和上下不同比例)
this@drawWithContent.drawContent()
}
withTransform({ //多个变换效果为一组
translate(200f,200f)
scale(.5f,1f)
}) {
this@drawWithContent.drawContent()
}
实践:ImageBitmap和Brush的文字
val imageBrush =
ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.my_img), tileModeX = TileMode.Mirror))
val brush = Brush.horizontalGradient(listOf(Color.Red,Color.Yellow,Color.Blue))
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Hello,World!",
style = TextStyle(
brush = brush,
fontWeight = FontWeight.ExtraBold,
fontSize = 36.sp
),
maxLines = 1,
overflow = TextOverflow.Clip,
textDecoration = TextDecoration.Underline
)
Text(
modifier = Modifier.fillMaxWidth(),
text = "Hello,World!",
style = TextStyle(
brush = imageBrush,
fontWeight = FontWeight.ExtraBold,
fontSize = 36.sp
),
maxLines = 1,
overflow = TextOverflow.Clip,
textDecoration = TextDecoration.Underline
)
}
参数类型:DrawStyle
源码:
object Fill : DrawStyle()
class Stroke(
val width: Float = 0.0f,
val miter: Float = DefaultMiter,
val cap: StrokeCap = DefaultCap,
val join: StrokeJoin = DefaultJoin,
val pathEffect: PathEffect? = null
) : DrawStyle()
Fill(默认值):填充实心,Stroke:线段
参数类型:StrokeCap
绘制时线段端点的样式(画距形时看不出来,只有线段才有效果)
StrokeCap.Butt
StrokeCap.Square
StrokeCap.Round
参数类型:PathEffect
定义线段样式
PathEffect.dashPathEffect(floatArrayOf(15f,5f,7f,10f)) //虚线,第一段15f,和下一段之间间隔5f;第二段7f,和下一段之间间隔5f;第三段15f,和下一段之间间隔5f.....以此类推。其中floatArrayOf一般成对出现
PathEffect.cornerPathEffect(pathEffect) //圆角的的幅度,只对有角落的图形生效,如Rect,对单一线段无效
PathEffect.chainPathEffect(PathEffect.dashPathEffect(floatArrayOf(10f,6f), phase = 0f),PathEffect.cornerPathEffect(20f)) //融合二种pathEffect
stampedPathEffect
Path画形状,再用Path作为线条样式
val shapePath = Path().apply {
moveTo(0f, 0f)
lineTo(20f, 10f)
lineTo(0f, 20f)
close()
}
val pathEffect1 = PathEffect.stampedPathEffect(
shape = shapePath,
advance = 30f, // 每个形状之间的间距
phase = 0f, // 初始偏移量
style = StampedPathEffectStyle.Rotate // 形状的排列方式
)
参数类型:ColorFilter
ColorFilter.lighting(multiply = Color.Red, add = Color.Blue) //将原有颜色值的(Red * multiply.Red) * add.Red,其它通道相同的公式
ColorFilter.colorMatrix() //用于通过色彩矩阵对颜色进行处理(如改变亮度、对比度、饱和度等),传入ColorMatrix类型
ColorFilter.tint(Color.Black) // 引入一种纯色,由blendMode决定混合模式
参数类型:ColorMatrix
ColorMatrix().apply {
// setToSaturation(saturation) //设置饱和度,1为原始,0去除颜色的灰度化,大于1则增加鲜艳度
// convertYuvToRgb() //颜色空间转换
// convertRgbToYuv() //颜色空间转换
// setToRotateRed(colorDegrees) // 红色通道(在色轮的)旋转角度
// setToRotateBlue(colorDegrees) // 蓝色通道(在色轮的)旋转角度
// setToRotateGreen(colorDegrees)// 绿色通道(在色轮的)旋转角度
setToScale(redScale = redScale, greenScale = greenScale, blueScale = blueScale, alphaScale = 1f) //对每一个颜色通道进行缩放,1f表示不变,.5f表示减少50%,2f表示增加100%
}
参数类型:StrokeJoin
StrokeJoin.Miter受参数miter : Float的影响
参数类型:BlendMode
这个属性暂不研究!很多个属性的实际效果与描述不一致!
定义二个图层之间的混合模式。
文档链接:https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/BlendMode
另一个库有相似的枚举类,android.graphics.BlendMode:https://developer.android.com/reference/android/graphics/BlendMode
在需要androidx.compose.ui.graphics.BlendMode的地方虽然不能用android.graphics.BlendMode,但是可以android.graphics.BlendMode的文档中看到每个属性的效果图,除了属性大小写有区别,其它都一致。
源图像(src)表示即将新增的这个图层,dst=元素中已有的图像(图层)。如:ColorFilter.tint(Color.Yellow, BlendMode.Src),src表示这个Yellow
参数类型:TileMode
Brush.*和ShaderBrush中都有TileMode,共有4种模式。
可以使用TileMode.Decal.isSupported()
检测在当前环境中是否支持
实践:动态渐变文字(ShaderBrush+infiniteRepeatable)
val infiniteTransition = rememberInfiniteTransition(label = "")
val offset by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 4000, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
label = ""
)
val brush = remember(offset) {
object : ShaderBrush() {
override fun createShader(size: Size): Shader {
val widthOffset = size.width * offset
val heightOffset = size.height * offset
return LinearGradientShader(
colors = listOf(Color.Red,Color.Black),
from = Offset(widthOffset, heightOffset),
to = Offset(widthOffset + size.width, heightOffset + size.height),
tileMode = TileMode.Mirror
)
}
}
}
Column(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
Text(
text = "Hello, World!",
style = TextStyle(
brush = brush,
fontWeight = FontWeight.ExtraBold,
fontSize = 64.sp
)
)
}