Skip to content

Commit c10d0e8

Browse files
committed
#
1 parent d335392 commit c10d0e8

File tree

5 files changed

+261
-43
lines changed

5 files changed

+261
-43
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repositories {
1313
maven { url "https://gitee.com/ezy/repo/raw/cosmo/"}
1414
}
1515
dependencies {
16-
implementation "me.reezy.cosmo:shapeable:0.9.0"
16+
implementation "me.reezy.cosmo:shapeable:0.10.0"
1717
}
1818
```
1919

app/src/main/res/layout/activity_main.xml

+25-11
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,31 @@
2121
app:cornerType="rounded"
2222
app:shadowColor="#40ff0000"
2323
app:shadowRadius="10dp"
24-
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Full"
25-
app:strokeColor="@color/teal_700"
26-
app:strokeWidth="1dp" />
24+
app:gradientStartColor="@color/teal_700"
25+
app:gradientEndColor="@color/teal_200"
26+
app:gradientType="sweep"
27+
app:gradientRadius="40dp"
28+
app:gradientCenterX="0.5"
29+
app:gradientCenterY="1"
30+
app:strokeColor="@color/purple_700"
31+
app:strokeWidth="2dp" />
2732

2833
<me.reezy.cosmo.shapeable.ShapeableFrameLayout
2934
android:layout_width="80dp"
3035
android:layout_height="40dp"
3136
android:layout_margin="10dp"
3237
android:clickable="true"
3338
android:gravity="center"
34-
app:cornerSize="5dp"
35-
app:cornerType="cut"
39+
app:cornerSize="15dp"
40+
app:cornerType="concave"
41+
app:cornerPosition="top"
3642
app:shadowColor="#40ff0000"
3743
app:shadowRadius="10dp"
38-
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Full"
44+
app:gradientStartColor="@color/teal_700"
45+
app:gradientEndColor="@color/teal_200"
46+
app:gradientType="radial"
47+
app:gradientRadius="40dp"
48+
app:gradientCenterY="1"
3949
app:strokeColor="@color/teal_700"
4050
app:strokeWidth="0dp" />
4151

@@ -49,7 +59,8 @@
4959
app:cornerType="concave"
5060
app:shadowColor="#40ff0000"
5161
app:shadowRadius="10dp"
52-
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Full"
62+
app:gradientStartColor="@color/teal_700"
63+
app:gradientEndColor="@color/teal_200"
5364
app:strokeColor="@color/teal_700"
5465
app:strokeWidth="1dp" />
5566

@@ -67,7 +78,6 @@
6778
app:cornerType="rounded"
6879
app:shadowColor="#40ffffff"
6980
app:shadowRadius="10dp"
70-
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Full"
7181
app:strokeColor="@color/teal_700"
7282
app:strokeWidth="1dp" />
7383

@@ -86,9 +96,14 @@
8696
app:cornerType="cut"
8797
app:shadowColor="#40ffffff"
8898
app:shadowRadius="10dp"
89-
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Full"
9099
app:strokeColor="@color/teal_700"
91-
app:strokeWidth="1dp" />
100+
app:strokeWidth="1dp"
101+
app:gradientStartColor="@color/teal_700"
102+
app:gradientEndColor="@color/teal_200"
103+
app:gradientType="sweep"
104+
app:gradientRadius="40dp"
105+
app:gradientCenterX="0"
106+
app:gradientCenterY="1"/>
92107

93108
<me.reezy.cosmo.shapeable.ShapeableFrameLayout
94109
android:layout_width="80dp"
@@ -104,7 +119,6 @@
104119
app:cornerType="concave"
105120
app:shadowColor="#40ffffff"
106121
app:shadowRadius="10dp"
107-
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Full"
108122
app:strokeColor="@color/teal_700"
109123
app:strokeWidth="0dp" />
110124

screenshot.png

-6.02 KB
Loading

shapeable/src/main/java/me/reezy/cosmo/shapeable/ShapeableDrawable.kt

+153-27
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import android.content.res.ColorStateList
66
import android.content.res.Resources
77
import android.content.res.TypedArray
88
import android.graphics.Color
9+
import android.graphics.LinearGradient
10+
import android.graphics.Paint
911
import android.graphics.Path
12+
import android.graphics.RadialGradient
1013
import android.graphics.Rect
1114
import android.graphics.RectF
15+
import android.graphics.Shader
16+
import android.graphics.SweepGradient
17+
import android.graphics.drawable.GradientDrawable
1218
import android.util.AttributeSet
1319
import androidx.core.content.res.TypedArrayUtils.obtainAttributes
1420
import androidx.core.graphics.ColorUtils
21+
import androidx.core.graphics.toRectF
1522
import com.google.android.material.shadow.ShadowRenderer
1623
import com.google.android.material.shape.CornerSize
24+
import com.google.android.material.shape.CornerTreatment
1725
import com.google.android.material.shape.CutCornerTreatment
1826
import com.google.android.material.shape.MaterialShapeDrawable
1927
import com.google.android.material.shape.RoundedCornerTreatment
@@ -38,6 +46,7 @@ class ShapeableDrawable(shapeModel: ShapeAppearanceModel) : MaterialShapeDrawabl
3846
val clipPath = Path()
3947

4048
private val clipRect: RectF = RectF()
49+
private val mGradientState = GradientState()
4150

4251
constructor() : this(ShapeAppearanceModel())
4352

@@ -57,53 +66,121 @@ class ShapeableDrawable(shapeModel: ShapeAppearanceModel) : MaterialShapeDrawabl
5766
ta.recycle()
5867
}
5968

60-
private fun initAttrs(ta: TypedArray) {
61-
strokeColor = ta.getColorStateList(R.styleable.ShapeableDrawable_strokeColor)
62-
strokeWidth = ta.getDimensionPixelSize(R.styleable.ShapeableDrawable_strokeWidth, 0).toFloat()
63-
tintList = ta.getColorStateList(R.styleable.ShapeableDrawable_backgroundTint) ?: ColorStateList.valueOf(Color.TRANSPARENT)
69+
private fun initAttrs(a: TypedArray) {
70+
strokeColor = a.getColorStateList(R.styleable.ShapeableDrawable_strokeColor)
71+
strokeWidth = a.getDimensionPixelSize(R.styleable.ShapeableDrawable_strokeWidth, 0).toFloat()
72+
tintList = a.getColorStateList(R.styleable.ShapeableDrawable_backgroundTint) ?: ColorStateList.valueOf(Color.TRANSPARENT)
6473

65-
if (ta.hasValue(R.styleable.ShapeableDrawable_cornerSize)) {
66-
setCornerSize(ta.getDimensionPixelSize(R.styleable.ShapeableDrawable_cornerSize, 0).toFloat())
74+
val cornerSize = a.getDimension(R.styleable.ShapeableDrawable_cornerSize, 0f)
75+
if (cornerSize > 0f) {
76+
val cornerType = a.getInteger(R.styleable.ShapeableDrawable_cornerType, 0)
77+
val cornerPosition = a.getInteger(R.styleable.ShapeableDrawable_cornerPosition, 0)
78+
setCorners(cornerSize, cornerPosition, cornerType)
6779
}
6880

69-
if (ta.hasValue(R.styleable.ShapeableDrawable_cornerType)) {
70-
val cornerTreatment = when(ta.getInteger(R.styleable.ShapeableDrawable_cornerType, 0)) {
71-
0 -> RoundedCornerTreatment()
72-
1 -> CutCornerTreatment()
73-
2 -> ConcaveCornerTreatment()
74-
else -> RoundedCornerTreatment()
75-
}
76-
shapeAppearanceModel = shapeAppearanceModel.toBuilder().setAllCorners(cornerTreatment).build()
81+
// 渐变背景色
82+
mGradientState.gradientColors = a.getGradientColors()
83+
mGradientState.gradientType = a.getInt(R.styleable.ShapeableDrawable_gradientType, GradientDrawable.LINEAR_GRADIENT)
84+
mGradientState.gradientRadius = a.getDimension(R.styleable.ShapeableDrawable_gradientRadius, 0f)
85+
mGradientState.gradientCenterX = a.getFloat(R.styleable.ShapeableDrawable_gradientCenterX, 0.5f)
86+
mGradientState.gradientCenterY = a.getFloat(R.styleable.ShapeableDrawable_gradientCenterY, 0.5f)
87+
mGradientState.gradientOrientation = try {
88+
GradientDrawable.Orientation.values()[a.getInt(R.styleable.ShapeableDrawable_gradientOrientation, 0)]
89+
} catch (e: IndexOutOfBoundsException) {
90+
GradientDrawable.Orientation.TOP_BOTTOM
7791
}
7892

79-
if (ta.hasValue(R.styleable.ShapeableDrawable_shadowRadius)) {
80-
shadowRadius = ta.getDimensionPixelSize(R.styleable.ShapeableDrawable_shadowRadius, 0)
81-
}
82-
if (ta.hasValue(R.styleable.ShapeableDrawable_shadowColor)) {
83-
setShadowColor(ta.getColor(R.styleable.ShapeableDrawable_shadowColor, 0))
93+
if (mGradientState.gradientColors != null) {
94+
setUseTintColorForShadow(false)
95+
tintList = null
8496
}
8597

86-
87-
val shadowOffsetY = ta.getDimensionPixelSize(R.styleable.ShapeableDrawable_shadowOffsetY, 0)
88-
val shadowOffsetX = ta.getDimensionPixelSize(R.styleable.ShapeableDrawable_shadowOffsetX, 0)
89-
setShadowOffset(shadowOffsetX, shadowOffsetY)
98+
// 阴影
99+
if (a.hasValue(R.styleable.ShapeableDrawable_shadowRadius)) {
100+
shadowRadius = a.getDimensionPixelSize(R.styleable.ShapeableDrawable_shadowRadius, 0)
101+
}
102+
if (a.hasValue(R.styleable.ShapeableDrawable_shadowColor)) {
103+
setShadowColor(a.getColor(R.styleable.ShapeableDrawable_shadowColor, 0))
104+
}
105+
if (a.hasValue(R.styleable.ShapeableDrawable_shadowOffsetX) || a.hasValue(R.styleable.ShapeableDrawable_shadowOffsetY)) {
106+
val shadowOffsetY = a.getDimensionPixelSize(R.styleable.ShapeableDrawable_shadowOffsetY, 0)
107+
val shadowOffsetX = a.getDimensionPixelSize(R.styleable.ShapeableDrawable_shadowOffsetX, 0)
108+
setShadowOffset(shadowOffsetX, shadowOffsetY)
109+
}
90110

91111

92-
val arrowSize = ta.getDimension(R.styleable.ShapeableDrawable_arrowSize, 0f)
112+
// 汽泡箭头
113+
val arrowSize = a.getDimension(R.styleable.ShapeableDrawable_arrowSize, 0f)
93114
if (arrowSize > 0f) {
94-
val arrowOffset = ta.getDimension(R.styleable.ShapeableDrawable_arrowOffset, 0f)
95-
val arrowEdge = ta.getInteger(R.styleable.ShapeableDrawable_arrowEdge, ArrowEdgeTreatment.EDGE_BOTTOM)
96-
val arrowAlign = ta.getInteger(R.styleable.ShapeableDrawable_arrowAlign, ArrowEdgeTreatment.ALIGN_CENTER )
115+
val arrowOffset = a.getDimension(R.styleable.ShapeableDrawable_arrowOffset, 0f)
116+
val arrowEdge = a.getInteger(R.styleable.ShapeableDrawable_arrowEdge, ArrowEdgeTreatment.EDGE_BOTTOM)
117+
val arrowAlign = a.getInteger(R.styleable.ShapeableDrawable_arrowAlign, ArrowEdgeTreatment.ALIGN_CENTER )
97118
setArrow(arrowSize, arrowOffset, arrowEdge, arrowAlign)
98119
}
99120
}
100121

122+
private fun TypedArray.getGradientColors(): IntArray? {
123+
124+
val hasStartColor = hasValue(R.styleable.ShapeableDrawable_gradientStartColor)
125+
val hasCenterColor = hasValue(R.styleable.ShapeableDrawable_gradientCenterColor)
126+
val hasEndColor = hasValue(R.styleable.ShapeableDrawable_gradientEndColor)
127+
128+
if (hasStartColor || hasCenterColor || hasEndColor) {
129+
val start = getColor(R.styleable.ShapeableDrawable_gradientStartColor, 0)
130+
val center = getColor(R.styleable.ShapeableDrawable_gradientCenterColor, 0)
131+
val end = getColor(R.styleable.ShapeableDrawable_gradientEndColor, 0)
132+
return if (hasCenterColor) intArrayOf(start, center, end) else intArrayOf(start, end)
133+
}
134+
return null
135+
}
136+
101137
@Suppress("OVERRIDE_DEPRECATION")
102138
override fun setShadowRadius(radius: Int) {
103139
@Suppress("DEPRECATION")
104140
super.setShadowRadius(radius)
105141
}
106142

143+
private fun setCorners(size: Float, position: Int, type: Int) {
144+
val corner = when(type) {
145+
0 -> RoundedCornerTreatment()
146+
1 -> CutCornerTreatment()
147+
2 -> ConcaveCornerTreatment()
148+
else -> RoundedCornerTreatment()
149+
}
150+
val builder = shapeAppearanceModel.toBuilder()
151+
shapeAppearanceModel = when (position) {
152+
1 -> builder.tl(corner, size).build()
153+
2 -> builder.tr(corner, size).build()
154+
3 -> builder.bl(corner, size).build()
155+
4 -> builder.br(corner, size).build()
156+
157+
5 -> builder.tl(corner, size).tr(corner, size).build()
158+
6 -> builder.bl(corner, size).br(corner, size).build()
159+
7 -> builder.tl(corner, size).bl(corner, size).build()
160+
8 -> builder.tr(corner, size).br(corner, size).build()
161+
162+
9 -> builder.tl(corner, size).br(corner, size).build()
163+
10 -> builder.bl(corner, size).tr(corner, size).build()
164+
165+
11 -> builder.tr(corner, size).bl(corner, size).br(corner, size).build()
166+
12 -> builder.tl(corner, size).bl(corner, size).br(corner, size).build()
167+
13 -> builder.tl(corner, size).tr(corner, size).br(corner, size).build()
168+
14 -> builder.tl(corner, size).tr(corner, size).bl(corner, size).build()
169+
170+
else -> builder.setAllCorners(corner).setAllCornerSizes(size).build()
171+
}
172+
173+
}
174+
175+
private inline fun ShapeAppearanceModel.Builder.tl(cornerTreatment: CornerTreatment, size: Float) =
176+
setTopLeftCorner(cornerTreatment).setTopLeftCornerSize(size)
177+
private inline fun ShapeAppearanceModel.Builder.tr(cornerTreatment: CornerTreatment, size: Float) =
178+
setTopRightCorner(cornerTreatment).setTopRightCornerSize(size)
179+
private inline fun ShapeAppearanceModel.Builder.bl(cornerTreatment: CornerTreatment, size: Float) =
180+
setBottomLeftCorner(cornerTreatment).setBottomLeftCornerSize(size)
181+
private inline fun ShapeAppearanceModel.Builder.br(cornerTreatment: CornerTreatment, size: Float) =
182+
setBottomRightCorner(cornerTreatment).setBottomRightCornerSize(size)
183+
107184
fun setShadowOffset(offsetX: Int, offsetY: Int) {
108185
if (offsetX != 0) {
109186
shadowCompatRotation = (atan(offsetY / offsetX.toFloat()) * 180 / PI).toInt()
@@ -137,6 +214,11 @@ class ShapeableDrawable(shapeModel: ShapeAppearanceModel) : MaterialShapeDrawabl
137214
override fun onBoundsChange(bounds: Rect) {
138215
super.onBoundsChange(bounds)
139216
updateClipPath(bounds.width(), bounds.height())
217+
218+
mGradientState.createFillShader(bounds.toRectF()) ?.let {
219+
(fieldFillPaint.get(this) as Paint).shader = it
220+
}
221+
invalidateSelf()
140222
}
141223

142224

@@ -152,6 +234,49 @@ class ShapeableDrawable(shapeModel: ShapeAppearanceModel) : MaterialShapeDrawabl
152234
clipPathProvider.calculatePath(newModel, 1f, clipRect, clipPath)
153235
}
154236

237+
private class GradientState {
238+
239+
var gradientColors: IntArray? = null
240+
var gradientType: Int = GradientDrawable.LINEAR_GRADIENT
241+
var gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.TL_BR
242+
var gradientCenterX: Float = 0.5f
243+
var gradientCenterY: Float = 0.5f
244+
var gradientRadius: Float = 0.5f
245+
246+
fun createFillShader(rect: RectF): Shader? = when {
247+
gradientColors == null -> null
248+
gradientType == GradientDrawable.LINEAR_GRADIENT -> {
249+
val r = when (gradientOrientation) {
250+
GradientDrawable.Orientation.TOP_BOTTOM -> RectF(rect.left, rect.top, rect.left, rect.bottom)
251+
GradientDrawable.Orientation.TR_BL -> RectF(rect.right, rect.top, rect.left, rect.bottom)
252+
GradientDrawable.Orientation.RIGHT_LEFT -> RectF(rect.right, rect.top, rect.left, rect.top)
253+
GradientDrawable.Orientation.BR_TL -> RectF(rect.right, rect.bottom, rect.left, rect.top)
254+
GradientDrawable.Orientation.BOTTOM_TOP -> RectF(rect.left, rect.bottom, rect.left, rect.top)
255+
GradientDrawable.Orientation.BL_TR -> RectF(rect.left, rect.bottom, rect.right, rect.top)
256+
GradientDrawable.Orientation.LEFT_RIGHT -> RectF(rect.left, rect.top, rect.right, rect.top)
257+
else -> RectF(rect.left, rect.top, rect.right, rect.bottom)
258+
}
259+
LinearGradient(r.left, r.top, r.right, r.bottom, gradientColors!!, null, Shader.TileMode.CLAMP)
260+
}
261+
gradientType == GradientDrawable.RADIAL_GRADIENT -> {
262+
if (gradientRadius > 0) {
263+
val x0 = rect.left + (rect.right - rect.left) * gradientCenterX
264+
val y0 = rect.top + (rect.bottom - rect.top) * gradientCenterY
265+
RadialGradient(x0, y0, gradientRadius, gradientColors!!, null, Shader.TileMode.CLAMP)
266+
} else {
267+
null
268+
}
269+
}
270+
gradientType == GradientDrawable.SWEEP_GRADIENT -> {
271+
val x0 = rect.left + (rect.right - rect.left) * gradientCenterX
272+
val y0 = rect.top + (rect.bottom - rect.top) * gradientCenterY
273+
SweepGradient(x0, y0, gradientColors!!, null)
274+
}
275+
else -> null
276+
}
277+
278+
}
279+
155280
private class RealShadowRenderer : ShadowRenderer() {
156281
override fun setShadowColor(shadowColor: Int) {
157282
val alpha = min(Color.alpha(shadowColor), 0x44)
@@ -172,6 +297,7 @@ class ShapeableDrawable(shapeModel: ShapeAppearanceModel) : MaterialShapeDrawabl
172297
private val clipPathProvider = ShapeAppearancePathProvider()
173298

174299
private val fieldShadowRenderer by field(MaterialShapeDrawable::class.java, "shadowRenderer")
300+
private val fieldFillPaint by field(MaterialShapeDrawable::class.java, "fillPaint")
175301

176302
private val fieldShadowStartColor by field(ShadowRenderer::class.java, "shadowStartColor")
177303
private val fieldShadowMiddleColor by field(ShadowRenderer::class.java, "shadowMiddleColor")

0 commit comments

Comments
 (0)