CustomLayoutScreen +-----------------------+ | o | | o o | | o o | | | | Center View | +-----------------------+
Custom layouts in Android Kotlin - Mini App: Build & Ship
package com.example.customlayout import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import android.os.Bundle class CustomCircleLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ViewGroup(context, attrs) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // TODO: Measure children and set measured dimensions super.onMeasure(widthMeasureSpec, heightMeasureSpec) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { // TODO: Position children in a circle } } class CustomLayoutScreen : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val customLayout = CustomCircleLayout(this) // TODO: Add 5 child views and a center TextView setContentView(customLayout) } }
package com.example.customlayout import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.graphics.Color import android.view.Gravity class CustomCircleLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : ViewGroup(context, attrs) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { var maxChildWidth = 0 var maxChildHeight = 0 // Measure children for (i in 0 until childCount) { val child = getChildAt(i) measureChild(child, widthMeasureSpec, heightMeasureSpec) if (child.measuredWidth > maxChildWidth) maxChildWidth = child.measuredWidth if (child.measuredHeight > maxChildHeight) maxChildHeight = child.measuredHeight } val desiredWidth = maxChildWidth * 4 val desiredHeight = maxChildHeight * 4 val width = resolveSize(desiredWidth, widthMeasureSpec) val height = resolveSize(desiredHeight, heightMeasureSpec) setMeasuredDimension(width, height) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { val centerX = (r - l) / 2 val centerY = (b - t) / 2 val radius = (Math.min(centerX, centerY) * 0.7).toInt() val childCount = childCount if (childCount == 0) return // Layout center child (assumed last child) val centerChild = getChildAt(childCount - 1) val centerLeft = centerX - centerChild.measuredWidth / 2 val centerTop = centerY - centerChild.measuredHeight / 2 centerChild.layout( centerLeft, centerTop, centerLeft + centerChild.measuredWidth, centerTop + centerChild.measuredHeight ) // Layout other children in circle val angleStep = 2 * Math.PI / (childCount - 1) for (i in 0 until childCount - 1) { val child = getChildAt(i) val angle = i * angleStep - Math.PI / 2 // start at top val childCenterX = (centerX + radius * Math.cos(angle)).toInt() val childCenterY = (centerY + radius * Math.sin(angle)).toInt() val left = childCenterX - child.measuredWidth / 2 val top = childCenterY - child.measuredHeight / 2 child.layout( left, top, left + child.measuredWidth, top + child.measuredHeight ) } } } class CustomLayoutScreen : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val customLayout = CustomCircleLayout(this) // Add 5 child TextViews arranged in a circle for (i in 1..5) { val tv = TextView(this).apply { text = "Item $i" setBackgroundColor(Color.parseColor("#FFBB86FC")) setTextColor(Color.WHITE) gravity = Gravity.CENTER setPadding(20, 20, 20, 20) } customLayout.addView(tv) } // Add center TextView val centerView = TextView(this).apply { text = "Center View" setBackgroundColor(Color.parseColor("#FF6200EE")) setTextColor(Color.WHITE) gravity = Gravity.CENTER setPadding(30, 30, 30, 30) } customLayout.addView(centerView) setContentView(customLayout) } }
We created a custom ViewGroup named CustomCircleLayout that arranges its children in a circle.
In onMeasure, we measure all children and decide the size of the layout based on the largest child size multiplied to give enough space for the circle.
In onLayout, we calculate the center point and radius. We place the last child in the center, and the other children evenly spaced around the circle using simple trigonometry.
In the activity, we add 5 TextViews as circle items and one center TextView labeled "Center View". Each child has background color and padding for visibility.
This example shows how to create a custom layout by overriding measurement and layout behavior in Android Kotlin.
CustomLayoutScreen +-----------------------+ | Item 1 | | Item 5 Item 2| | Item 4 Center View Item 3 | | | +-----------------------+
Practice
Solution
Step 1: Understand the role of custom layouts
Custom layouts allow developers to define how child views are measured and positioned beyond standard layouts.Step 2: Compare with other options
Options A, B, and C describe unrelated features like animations, automatic UI generation, or system UI replacement.Final Answer:
To control exactly how child views are measured and placed -> Option AQuick Check:
Custom layouts = precise child view control [OK]
- Thinking custom layouts generate UI automatically
- Confusing custom layouts with animations
- Believing custom layouts replace system UI
Solution
Step 1: Identify the method for measuring size
TheonMeasuremethod is used to determine the size requirements of the layout and its children.Step 2: Differentiate from other methods
onLayoutpositions children,onDrawhandles drawing, andonCreateis unrelated to layout measurement.Final Answer:
onMeasure -> Option BQuick Check:
Measure size = onMeasure [OK]
- Confusing onLayout with onMeasure
- Using onDraw for size calculation
- Thinking onCreate handles layout measurement
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val child = getChildAt(0)
child.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(50, MeasureSpec.EXACTLY))
setMeasuredDimension(200, 100)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val child = getChildAt(0)
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
}Solution
Step 1: Analyze onMeasure child measurement
The child is measured with width 100 pixels exactly and height 50 pixels exactly.Step 2: Analyze onLayout child placement
The child is laid out from (0,0) to (child.measuredWidth, child.measuredHeight), so width is 100 pixels.Final Answer:
100 pixels -> Option DQuick Check:
Child width = measured width = 100 [OK]
- Using parent width instead of child's measured width
- Confusing layout size with measure specs
- Assuming child's width equals parent's width
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
child.layout(0, 0, 0, 0)
}
}Solution
Step 1: Examine child.layout parameters
Each child is positioned with left=0, top=0, right=0, bottom=0, which means zero width and height.Step 2: Understand effect on visibility
Zero size means children are not visible on screen.Final Answer:
Child views are laid out with zero width and height -> Option AQuick Check:
Zero size layout hides children [OK]
- Assuming loop skips children
- Thinking super.onLayout is mandatory
- Ignoring layout size parameters
onLayout for this?Solution
Step 1: Understand layout requirements
Two children side by side means splitting parent's width equally, full height each.Step 2: Apply correct coordinates in onLayout
First child from left=0 to right=width/2, second from left=width/2 to right=width, both top=0 bottom=height.Final Answer:
Layout first child from (0,0) to (width/2, height), second from (width/2,0) to (width, height) -> Option CQuick Check:
Side by side = split width in onLayout [OK]
- Overlapping children by using full width for both
- Placing second child outside visible bounds
- Stacking children vertically instead of horizontally
Ready to prove it?
Pass the test and this pattern moves unknown
