布景
家人们,太卷啦,谁懂呀,今日老板给让咱们完成如下作用的一个东东。 我看的第一眼,这不是很简单吗?作为一个优异的Androider,这种工作当然是让设计师直接给我一张完好的图,然后放到页面就好了,竣工!
可是,当我让设计师这姿态给我切图的时分,做iOS开发的小伙伴问我:你这样做的话要是后续内容有更新咋办呢?
我:那还不简单,换张图就好了呀。
iOS的小伙伴: 那上面有两个能够点击的当地咋办?
我:我。。。凉拌
iOS的小伙伴:小张(设计师)把上面的左面中间的图标给我,右下角的图给我,其他当地我自己画了。
我的心里OS:能不能不要这么卷呀,直接放张图多好,要啥按钮。。。。
这个时分产品经理说:这个内容随时都要变的,要后端能够直接操控这个内容。
我:。。。。要不你来?
当然这句话怂怂的我没敢说出来。这个作用表面看起来没啥难度,可是其实暗藏玄。
- 左面的“会员特权”那一块,全体布景是一个不规则图画,并且是从左到右的一个渐变作用。右边的布景全体是一个带圆角的纯色布景,右下角有个图标。
- 右边的布景却是不杂乱,可是文本后边或许存在(也或许不存在)一个能够点击的文本按钮,当文本最终一行能够按钮共存(长度能放得下两个内容)的时分,按钮在文本最终一行后边(如第2点),当最终一行长度不能和按钮共存的时分,按钮需求换行(如第5点)。
有的小伙伴或许说,这有啥难的,第2点上 FlexboxLayout 不就好了。可是仔细想想 FlexboxLayout 其实不行。因为 FlexboxLayout 核算的时分控件整个的宽度,而不是 TextView 最终一行的宽度。因此 FlexboxLayout 没有办法把文本按钮放到 TextView 的最终一行,除非 TextView 只要一行。
没办法了,只能选择自定义 View 来打破。咱们来把上面的问题逐个打破。作为一名合格的 Androider ,咱们不惹事,可是咱们不能怕事儿。
全体思路
- 最外层的自定义View继承 ConstraintLayout ,经过重写其 onDraw 办法自定义制作其中的左右部分的布景,左右部分的布景均经过 Canvas.drawPath() 办法进行制作,所以需求提前准备好两个部分的Path。
- 左半部分的图标经过TextView + drawableTop 放到自定义的 ConstraintLayout 的左面,右下角的图标同理经过 ImageView 放上去,为减少工作量,这两个当地的图标+文字均在 Xml 布局时放上去。
- 右边的文本内容+文本按钮全体为一个 RecyclerView,RecyclerView 的 Item 则经过自定义 ViewGroup 完成对位置进行为所欲为的操控。
完成最外层的自定义View
作为一个996的Androider,看图写 Path 简直是信手拈来。而我,在 ChatGPT 的协助下,只用了2个小时就把 Path 给搞定了,是不是很厉害。(此处应有狗头)因为没有什么技术难度,我就直接贴代码了。
// 构建左半部分的布景的 Path
val triangleWidth = 6.dp
val triangleHeight = 16.dp
val radius = 16.dp
val leftRectWidth = 100.dp
shaderPath.reset()
shaderPath.moveTo(radius, 0f)
// 最上面的那条线
shaderPath.lineTo(leftRectWidth, 0f)
// 最终边的线+三角形
shaderPath.lineTo(leftRectWidth, (viewHeight - triangleHeight) / 2f)
shaderPath.lineTo(leftRectWidth + triangleWidth, viewHeight / 2f)
shaderPath.lineTo(leftRectWidth, (viewHeight + triangleHeight) / 2f)
// 剩余的线+圆角
shaderPath.lineTo(leftRectWidth, viewHeight.toFloat())
shaderPath.lineTo(radius, viewHeight.toFloat())
shaderPath.arcTo(0f, viewHeight - radius, radius, viewHeight.toFloat(), 90f, 90f, false)
shaderPath.lineTo(0f, radius)
shaderPath.arcTo(0f, 0f, radius, radius, 180f, 90f, false)
左面的部分搞定了,该右边了。
val radius = 16.dp
val leftRectWidth = 100.dp
rightPath.reset()
rightPath.moveTo(leftRectWidth, 0f)
rightPath.lineTo(viewWidth.toFloat() - radius, 0f)
// 右上圆角
rightPath.arcTo(
viewWidth.toFloat() - radius,
0f,
viewWidth.toFloat(),
radius,
-90f,
90f,
false
)
rightPath.lineTo(viewWidth.toFloat(), viewHeight.toFloat() - radius)
// 右下圆角
rightPath.arcTo(
viewWidth.toFloat() - radius,
viewHeight.toFloat() -radius,
viewWidth.toFloat(),
viewHeight.toFloat(),
0f,
90f,
false
)
rightPath.lineTo(100.dp, viewHeight.toFloat())
rightPath.close()
咱们需求在 onSizeChange 办法中调用以上代码从头构建以上两个 Path,这样咱们的自定义 View 就能够跟着用户屏幕的改变而随之改变。
除此之外,为了完成渐变作用,咱们还应该创立一个 Shader。
private fun createShader(): Shader {
val startColor = Color.parseColor("#FEDD83")
val endColor = Color.parseColor("#F2CA5C")
return LinearGradient(
0f,
0f,
106.dp,
0f,
startColor,
endColor,
Shader.TileMode.CLAMP
)
}
OK,万事俱备,只欠东风。接下来咱们只需求在 onDraw 里边把这两个 Path 画出来就好啦。
override fun onDraw(canvas: Canvas) {
// 先制作右边的Path
canvas.drawPath(rightPath, paint)
// 渐变作用的Shader
paint.shader = shader
// 制作左面的Path
canvas.drawPath(shaderPath, paint)
// 渐变作用撤销,避免重复调用时影响到右边的Path
paint.shader = null
}
好了,咱们来运行看一哈。咦?啥也没有!为啥会这样?我几个小时的尽力呀!!!别慌,我来问下 ChatGPT。原来ViewGroup以及其子类为了更好的功能,默认情况下都不会触发其 onDraw 办法,那样咋办呢?咱们能够在自定义 View 的结构函数中调用 setWillNotDraw(false) 来接触这个约束。加上这行代码之后咱们在来看看作用。
嗯嗯,没毛病,布景图现已竣工。接下来就要自定义右边那个类似 FlexboxLayout 的控件了。因为篇幅关系,我把接下来的完成放到了下一篇《被迫内卷之 Android 自定义View进阶(二)》。