Skip to content

Commit

Permalink
Merge pull request #45 from fornewid/issues/#38_corner_size
Browse files Browse the repository at this point in the history
Library: Supports different radii depending on the corner (#38)
  • Loading branch information
fornewid authored Sep 25, 2020
2 parents a43f17a + b8e7859 commit 758e00f
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 38 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ There is a [sample](https://github.com/fornewid/neumorphism/tree/master/sample)
<style name="CustomShapeAppearance">
<item name="neumorph_cornerFamily">{rounded|oval}</item>
<item name="neumorph_cornerSize">32dp</item>

<!-- Or if wants different radii depending on the corner. -->
<item name="neumorph_cornerSizeTopLeft">16dp</item>
<item name="neumorph_cornerSizeTopRight">16dp</item>
<item name="neumorph_cornerSizeBottomLeft">16dp</item>
<item name="neumorph_cornerSizeBottomRight">16dp</item>
</style>
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.Dimension
import androidx.annotation.StyleRes
import kotlin.math.min

class NeumorphShapeAppearanceModel {

class Builder {

@CornerFamily
var cornerFamily: Int = CornerFamily.ROUNDED
var cornerSize: Float = 0f
var topLeftCornerSize: Float = 0f
var topRightCornerSize: Float = 0f
var bottomLeftCornerSize: Float = 0f
var bottomRightCornerSize: Float = 0f

fun setAllCorners(
@CornerFamily cornerFamily: Int,
@Dimension cornerSize: Float
): Builder {
return setAllCorners(cornerFamily)
.setAllCornerSizes(cornerSize)
.setCornerRadius(cornerSize)
}

fun setAllCorners(@CornerFamily cornerFamily: Int): Builder {
Expand All @@ -30,9 +34,34 @@ class NeumorphShapeAppearanceModel {
}
}

fun setAllCornerSizes(cornerSize: Float): Builder {
fun setCornerRadius(cornerRadius: Float): Builder {
return setTopLeftCornerSize(cornerRadius)
.setTopRightCornerSize(cornerRadius)
.setBottomLeftCornerSize(cornerRadius)
.setBottomRightCornerSize(cornerRadius)
}

fun setTopLeftCornerSize(topLeftCornerSize: Float): Builder {
return apply {
this.topLeftCornerSize = topLeftCornerSize
}
}

fun setTopRightCornerSize(topRightCornerSize: Float): Builder {
return apply {
this.topRightCornerSize = topRightCornerSize
}
}

fun setBottomLeftCornerSize(bottomLeftCornerSize: Float): Builder {
return apply {
this.bottomLeftCornerSize = bottomLeftCornerSize
}
}

fun setBottomRightCornerSize(bottomRightCornerSize: Float): Builder {
return apply {
this.cornerSize = cornerSize
this.bottomRightCornerSize = bottomRightCornerSize
}
}

Expand All @@ -43,25 +72,63 @@ class NeumorphShapeAppearanceModel {

@CornerFamily
private val cornerFamily: Int
private val cornerSize: Float
private val topLeftCornerSize: Float
private val topRightCornerSize: Float
private val bottomLeftCornerSize: Float
private val bottomRightCornerSize: Float

private constructor(builder: Builder) {
cornerFamily = builder.cornerFamily
cornerSize = builder.cornerSize
topLeftCornerSize = builder.topLeftCornerSize
topRightCornerSize = builder.topRightCornerSize
bottomLeftCornerSize = builder.bottomLeftCornerSize
bottomRightCornerSize = builder.bottomRightCornerSize
}

constructor() {
cornerFamily = CornerFamily.ROUNDED
cornerSize = 0f
topLeftCornerSize = 0f
topRightCornerSize = 0f
bottomLeftCornerSize = 0f
bottomRightCornerSize = 0f
}

@CornerFamily
fun getCornerFamily(): Int {
return cornerFamily
}

fun getCornerSize(): Float {
return cornerSize
fun getTopLeftCornerSize(): Float {
return topLeftCornerSize
}

fun getTopRightCornerSize(): Float {
return topRightCornerSize
}

fun getBottomLeftCornerSize(): Float {
return bottomLeftCornerSize
}

fun getBottomRightCornerSize(): Float {
return bottomRightCornerSize
}

internal fun getCornerRadii(maximum: Float): FloatArray {
val topLeftCornerSize = min(maximum, getTopLeftCornerSize())
val topRightCornerSize = min(maximum, getTopRightCornerSize())
val bottomLeftCornerSize = min(maximum, getBottomLeftCornerSize())
val bottomRightCornerSize = min(maximum, getBottomRightCornerSize())
return floatArrayOf(
topLeftCornerSize,
topLeftCornerSize,
topRightCornerSize,
topRightCornerSize,
bottomLeftCornerSize,
bottomLeftCornerSize,
bottomRightCornerSize,
bottomRightCornerSize
)
}

companion object {
Expand Down Expand Up @@ -105,26 +172,40 @@ class NeumorphShapeAppearanceModel {
R.styleable.NeumorphShapeAppearance_neumorph_cornerFamily,
CornerFamily.ROUNDED
)
val cornerSize =
getCornerSize(
a,
R.styleable.NeumorphShapeAppearance_neumorph_cornerSize,
defaultCornerSize
)
val cornerSize = a.getCornerSize(
R.styleable.NeumorphShapeAppearance_neumorph_cornerSize,
defaultCornerSize
)
val cornerSizeTopLeft = a.getCornerSize(
R.styleable.NeumorphShapeAppearance_neumorph_cornerSizeTopLeft,
cornerSize
)
val cornerSizeTopRight = a.getCornerSize(
R.styleable.NeumorphShapeAppearance_neumorph_cornerSizeTopRight,
cornerSize
)
val cornerSizeBottomRight = a.getCornerSize(
R.styleable.NeumorphShapeAppearance_neumorph_cornerSizeBottomLeft, cornerSize
)
val cornerSizeBottomLeft = a.getCornerSize(
R.styleable.NeumorphShapeAppearance_neumorph_cornerSizeBottomRight, cornerSize
)
return Builder()
.setAllCorners(cornerFamily, cornerSize)
.setAllCorners(cornerFamily)
.setTopLeftCornerSize(cornerSizeTopLeft)
.setTopRightCornerSize(cornerSizeTopRight)
.setBottomRightCornerSize(cornerSizeBottomRight)
.setBottomLeftCornerSize(cornerSizeBottomLeft)
} finally {
a.recycle()
}
}

private fun getCornerSize(
a: TypedArray, index: Int, defaultValue: Float
): Float {
val value = a.peekValue(index) ?: return defaultValue
private fun TypedArray.getCornerSize(index: Int, defaultValue: Float): Float {
val value = peekValue(index) ?: return defaultValue
return if (value.type == TypedValue.TYPE_DIMENSION) {
TypedValue.complexToDimensionPixelSize(
value.data, a.resources.displayMetrics
value.data, resources.displayMetrics
).toFloat()
} else {
defaultValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import soup.neumorphism.internal.shape.BasinShape
import soup.neumorphism.internal.shape.FlatShape
import soup.neumorphism.internal.shape.PressedShape
import soup.neumorphism.internal.shape.Shape
import kotlin.math.min

class NeumorphShapeDrawable : Drawable {

Expand Down Expand Up @@ -310,20 +311,20 @@ class NeumorphShapeDrawable : Drawable {
}

private fun calculateOutlinePath(bounds: RectF, path: Path) {
val shapeAppearanceModel = drawableState.shapeAppearanceModel
val left = drawableState.inset.left.toFloat()
val top = drawableState.inset.top.toFloat()
val right = left + bounds.width()
val bottom = top + bounds.height()
path.reset()
when (drawableState.shapeAppearanceModel.getCornerFamily()) {
when (shapeAppearanceModel.getCornerFamily()) {
CornerFamily.OVAL -> {
path.addOval(left, top, right, bottom, Path.Direction.CW)
}
CornerFamily.ROUNDED -> {
val cornerSize = drawableState.shapeAppearanceModel.getCornerSize()
path.addRoundRect(
left, top, right, bottom,
cornerSize, cornerSize,
shapeAppearanceModel.getCornerRadii(min(bounds.width() / 2f, bounds.height() / 2f)),
Path.Direction.CW
)
}
Expand All @@ -332,13 +333,14 @@ class NeumorphShapeDrawable : Drawable {
}

override fun getOutline(outline: Outline) {
when (drawableState.shapeAppearanceModel.getCornerFamily()) {
val shapeAppearanceModel = drawableState.shapeAppearanceModel
when (shapeAppearanceModel.getCornerFamily()) {
CornerFamily.OVAL -> {
outline.setOval(getBoundsInternal())
}
CornerFamily.ROUNDED -> {
val cornerSize = drawableState.shapeAppearanceModel.getCornerSize()
outline.setRoundRect(getBoundsInternal(), cornerSize)
//TODO: How to setRoundRect() with cornerRadii???
outline.setRect(getBoundsInternal())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import soup.neumorphism.NeumorphShapeDrawable.NeumorphShapeDrawableState
import soup.neumorphism.internal.util.onCanvas
import soup.neumorphism.internal.util.withClipOut
import soup.neumorphism.internal.util.withTranslation
import kotlin.math.min
import kotlin.math.roundToInt

internal class FlatShape(
Expand All @@ -39,13 +40,11 @@ internal class FlatShape(
lightShadowBitmap?.let {
val offsetX = if (LightSource.isLeft(lightSource)) -elevation - z else -elevation + z
val offsetY = if (LightSource.isTop(lightSource)) -elevation - z else -elevation + z
val offset = -elevation - z
drawBitmap(it, offsetX + left, offsetY + top, null)
}
darkShadowBitmap?.let {
val offsetX = if (LightSource.isLeft(lightSource)) -elevation + z else -elevation - z
val offsetY = if (LightSource.isTop(lightSource)) -elevation + z else -elevation - z
val offset = -elevation + z
drawBitmap(it, offsetX + left, offsetY + top, null)
}
}
Expand All @@ -59,9 +58,9 @@ internal class FlatShape(
}
CornerFamily.ROUNDED -> {
shape = GradientDrawable.RECTANGLE
cornerRadii = shapeAppearanceModel.getCornerSize().let {
floatArrayOf(it, it, it, it, it, it, it, it)
}
cornerRadii = shapeAppearanceModel.getCornerRadii(
min(bounds.width() / 2f, bounds.height() / 2f)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal class PressedShape(
CornerFamily.ROUNDED -> {
val cornerSize = min(
min(w / 2f, h / 2f),
drawableState.shapeAppearanceModel.getCornerSize()
getCornerSizeForLightShadow()
)
shape = GradientDrawable.RECTANGLE
cornerRadii = getCornerRadiiForLightShadow(cornerSize)
Expand All @@ -74,7 +74,7 @@ internal class PressedShape(
CornerFamily.ROUNDED -> {
val cornerSize = min(
min(w / 2f, h / 2f),
drawableState.shapeAppearanceModel.getCornerSize()
getCornerSizeForDarkShadow()
)
shape = GradientDrawable.RECTANGLE
cornerRadii = getCornerRadiiForDarkShadow(cornerSize)
Expand All @@ -89,6 +89,18 @@ internal class PressedShape(
shadowBitmap = generateShadowBitmap(w, h)
}

private fun getCornerSizeForLightShadow(): Float {
return drawableState.shapeAppearanceModel.run {
when (drawableState.lightSource) {
LightSource.LEFT_TOP -> getBottomLeftCornerSize()
LightSource.LEFT_BOTTOM -> getTopRightCornerSize()
LightSource.RIGHT_TOP -> getBottomRightCornerSize()
LightSource.RIGHT_BOTTOM -> getTopLeftCornerSize()
else -> throw IllegalStateException("LightSource ${drawableState.lightSource} is not supported.")
}
}
}

private fun getCornerRadiiForLightShadow(cornerSize: Float): FloatArray {
return when (drawableState.lightSource) {
LightSource.LEFT_TOP -> floatArrayOf(0f, 0f, 0f, 0f, cornerSize, cornerSize, 0f, 0f)
Expand All @@ -99,6 +111,18 @@ internal class PressedShape(
}
}

private fun getCornerSizeForDarkShadow(): Float {
return drawableState.shapeAppearanceModel.run {
when (drawableState.lightSource) {
LightSource.LEFT_TOP -> getTopLeftCornerSize()
LightSource.LEFT_BOTTOM -> getBottomLeftCornerSize()
LightSource.RIGHT_TOP -> getTopRightCornerSize()
LightSource.RIGHT_BOTTOM -> getBottomRightCornerSize()
else -> throw IllegalStateException("LightSource ${drawableState.lightSource} is not supported.")
}
}
}

private fun getCornerRadiiForDarkShadow(cornerSize: Float): FloatArray {
return when (drawableState.lightSource) {
LightSource.LEFT_TOP -> floatArrayOf(cornerSize, cornerSize, 0f, 0f, 0f, 0f, 0f, 0f)
Expand Down
4 changes: 4 additions & 0 deletions neumorphism/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@

<declare-styleable name="NeumorphShapeAppearance">
<attr name="neumorph_cornerSize" format="dimension" />
<attr name="neumorph_cornerSizeTopLeft" format="dimension" />
<attr name="neumorph_cornerSizeTopRight" format="dimension" />
<attr name="neumorph_cornerSizeBottomLeft" format="dimension" />
<attr name="neumorph_cornerSizeBottomRight" format="dimension" />
<attr name="neumorph_cornerFamily" format="enum">
<enum name="rounded" value="0" />
<enum name="oval" value="1" />
Expand Down
16 changes: 13 additions & 3 deletions sample/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@
app:layout_constraintTop_toBottomOf="@id/card_barrier"
app:neumorph_shapeType="basin" />

<soup.neumorphism.NeumorphFloatingActionButton
style="@style/Widget.Neumorph.FloatingActionButton"
android:layout_width="88dp"
android:layout_height="88dp"
android:layout_margin="24dp"
android:scaleType="centerInside"
android:src="@drawable/ic_filter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:neumorph_shapeAppearance="@style/CustomShapeAppearance" />

<soup.neumorphism.NeumorphButton
android:id="@+id/button"
style="@style/Widget.Neumorph.Button"
Expand All @@ -117,8 +128,8 @@
android:layout_marginBottom="36dp"
android:drawablePadding="8dp"
android:text="Button"
app:drawableStartCompat="@drawable/ic_left"
app:drawableEndCompat="@drawable/ic_right"
app:drawableStartCompat="@drawable/ic_left"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
Expand All @@ -132,7 +143,6 @@
android:scaleType="centerInside"
android:src="@drawable/ic_filter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:neumorph_shapeAppearance="@style/CustomShapeAppearance" />
app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
4 changes: 3 additions & 1 deletion sample/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<resources>
<style name="CustomShapeAppearance">
<item name="neumorph_cornerFamily">rounded</item>
<item name="neumorph_cornerSize">32dp</item>
<item name="neumorph_cornerSize">16dp</item>
<item name="neumorph_cornerSizeTopLeft">64dp</item>
<item name="neumorph_cornerSizeTopRight">64dp</item>
</style>
</resources>

0 comments on commit 758e00f

Please sign in to comment.