Last active
April 18, 2019 01:38
-
-
Save ephemient/c07585572c6938d38f925e59380e00f3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="CutoutBackgroundLayout_Layout"> | |
<attr name="layout_cutoutBackground" format="boolean"/> | |
</declare-styleable> | |
</resources> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.graphics.Canvas | |
import android.graphics.Outline | |
import android.graphics.Path | |
import android.graphics.Rect | |
import android.graphics.Region | |
import android.graphics.drawable.Drawable | |
import android.graphics.drawable.LevelListDrawable | |
import android.os.Build | |
import android.util.AttributeSet | |
import android.view.View | |
import android.view.ViewGroup | |
import androidx.annotation.Px | |
import androidx.constraintlayout.widget.ConstraintLayout | |
import androidx.core.graphics.withSave | |
import androidx.core.view.forEach | |
class CutoutBackgroundLayout : ConstraintLayout { | |
constructor(context: Context) : super(context) | |
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) | |
constructor( | |
context: Context, | |
attrs: AttributeSet?, | |
defStyleAttr: Int | |
) : super(context, attrs, defStyleAttr) | |
init { | |
viewTreeObserver.addOnGlobalLayoutListener { refreshOutlines() } | |
} | |
private val outline: Outline = Outline() | |
private val rect = Rect() | |
private fun refreshOutlines() { | |
val background = this.background as? CutoutDrawable ?: return | |
val path = background.cutoutPath | |
path.rewind() | |
forEach { view -> | |
if (view.visibility != View.VISIBLE || (view.layoutParams as? LayoutParams)?.isCutoutBackground != true) { | |
return@forEach | |
} | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | |
view.outlineProvider.getOutline(view, outline) | |
if (!outline.isEmpty) { | |
outline.offset(view.left, view.top) | |
if (outline.getRect(rect) && !rect.isEmpty) { | |
path.addRoundRect( | |
rect.left.toFloat(), | |
rect.top.toFloat(), | |
rect.right.toFloat(), | |
rect.bottom.toFloat(), | |
outline.radius, | |
outline.radius, | |
Path.Direction.CCW | |
) | |
return@forEach | |
} | |
} | |
// else... [Outline.mPath] is `@hide` :( | |
} | |
view.getHitRect(rect) | |
if (!rect.isEmpty) { | |
path.addRect( | |
rect.left.toFloat(), | |
rect.top.toFloat(), | |
rect.right.toFloat(), | |
rect.bottom.toFloat(), | |
Path.Direction.CCW | |
) | |
} | |
} | |
background.invalidateSelf() | |
} | |
override fun setBackground(background: Drawable?) { | |
super.setBackground( | |
if (background is CutoutDrawable?) background else CutoutDrawable( | |
background | |
) | |
) | |
refreshOutlines() | |
} | |
override fun generateDefaultLayoutParams(): ConstraintLayout.LayoutParams = | |
LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) | |
override fun generateLayoutParams(p: ViewGroup.LayoutParams): ViewGroup.LayoutParams = | |
LayoutParams(p) | |
override fun generateLayoutParams(attrs: AttributeSet): LayoutParams = | |
LayoutParams(context, attrs) | |
override fun checkLayoutParams(p: ViewGroup.LayoutParams): Boolean = p is LayoutParams | |
class LayoutParams : ConstraintLayout.LayoutParams { | |
@JvmField | |
var isCutoutBackground: Boolean = false | |
constructor(source: LayoutParams) : super(source) { | |
isCutoutBackground = source.isCutoutBackground | |
} | |
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { | |
val ta = | |
context.obtainStyledAttributes(attrs, R.styleable.CutoutBackgroundLayout_Layout) | |
isCutoutBackground = | |
ta.getBoolean( | |
R.styleable.CutoutBackgroundLayout_Layout_layout_cutoutBackground, | |
false | |
) | |
ta.recycle() | |
} | |
constructor(@Px width: Int, @Px height: Int) : super(width, height) | |
constructor(source: ViewGroup.LayoutParams) : super(source) | |
} | |
} | |
class CutoutDrawable() : LevelListDrawable() { | |
constructor(drawable: Drawable?) : this() { | |
addLevel(0, 0, drawable) | |
} | |
val cutoutPath = Path() | |
override fun draw(canvas: Canvas) { | |
canvas.withSave { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | |
clipOutPath(cutoutPath) | |
} else { | |
@Suppress("DEPRECATION") | |
clipPath(cutoutPath, Region.Op.DIFFERENCE) | |
} | |
super.draw(this) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<CutoutBackgroundLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:aapt="http://schemas.android.com/aapt" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<aapt:attr name="android:background"> | |
<layer-list android:paddingLeft="8dp" android:paddingTop="0dp" android:paddingRight="8dp" android:paddingBottom="0dp"> | |
<item android:left="2dp" android:top="8sp" android:right="2dp" android:bottom="8sp"> | |
<shape android:shape="rectangle"> | |
<corners android:radius="4dp" /> | |
<stroke android:width="2dp" /> | |
</shape> | |
</item> | |
</layer-list> | |
</aapt:attr> | |
<TextView | |
android:id="@android:id/text1" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" | |
app:layout_cutoutBackground="true" | |
android:minHeight="8sp" | |
android:gravity="start|top" | |
tools:text="@tools:sample/full_names" /> | |
<TextView | |
android:id="@android:id/text2" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_cutoutBackground="true" | |
android:minHeight="8sp" | |
android:gravity="end|bottom" | |
tools:text="@tools:sample/date/ddmmyy" /> | |
<TextView | |
android:layout_width="0dp" | |
android:layout_height="wrap_content" | |
android:gravity="center" | |
app:layout_constraintBottom_toTopOf="@android:id/text2" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintHorizontal_bias="0.5" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toBottomOf="@android:id/text1" | |
tools:text="@tools:sample/lorem/random" /> | |
</CutoutBackgroundLayout> |
Author
ephemient
commented
Apr 10, 2019
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment