Xavier Rubio Jansana
Views
")ViewGroup
(e.g. LinearLayout
, ConstraintLayout
...)
class MyCompoundControlView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
init {
View.inflate(context, R.layout.my_compount_control_layout,
this)
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:text="Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/subtitle"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:text="Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
tool:parentTag="android.widget.LinearLayout"
tool:orientation="vertical"
tool:layout_width="wrap_content"
tool:layout_height="wrap_content">
<TextView .../>
<TextView .../>
</merge>
Notice <merge>
and tool
class MyCompoundControlView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
init {
View.inflate(context, R.layout.my_compount_control_layout,
this)
this.orientation = VERTICAL
}
}
Initialization of root tag moved here
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.xrubio.customcontrols.MyCompoundControlView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Notice we're using the fully qualified class name
Canvas
to draw
val specMode: Int = MeasureSpec.getMode(measureSpec)
val specSize: Int = MeasureSpec.getSize(measureSpec)
We have a measureSpec per axis (X & Y)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val minW: Int = (paddingStart + paddingEnd + _radius * 2.0f).toInt()
val w: Int = resolveSizeAndState(minW, widthMeasureSpec, 0)
val minH: Int = (paddingTop + paddingBottom + _radius * 2.0f).toInt()
val h: Int = resolveSizeAndState(minH, heightMeasureSpec, 0)
setMeasuredDimension(w, h)
}
setMeasuredDimension()
resolveSizeAndState(size, measureSpec, childMeasuredState)
as helperAfter changind a property, we need to either invalidate or relayout:
invalidate()
: triggers a redraw. Use when property doesn't changes size.requestLayout()
: triggers the whole cycle (measure, layout, draw). Use when property changes size.
implementation 'androidx.core:core-ktx:1.1.0'
withClip()
, withMatrix()
, withRotation
, etc.View#resolveSizeAndState(int, int, int)
in AOSP
https://android.googlesource.com/platform/frameworks/base/+/Xavier Rubio Jansana
@teknik_tdr
https://xrubio.com
https://github.com/xrubioj/
This talk is available at:
https://xrubio.com/talks/talk-android-custom-controls-and-canvas/