package com.example.myapp.ui.custom
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import kotlin.math.max
class FlowLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
private var horizontalSpacing = 0
private var verticalSpacing = 0
init {
context.obtainStyledAttributes(attrs, R.styleable.FlowLayout).apply {
horizontalSpacing = getDimensionPixelSize(
R.styleable.FlowLayout_horizontalSpacing, 0
)
verticalSpacing = getDimensionPixelSize(
R.styleable.FlowLayout_verticalSpacing, 0
)
recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
var currentWidth = paddingLeft
var currentHeight = paddingTop
var maxLineHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child.visibility == GONE) continue
measureChildWithMargins(
child,
widthMeasureSpec,
0,
heightMeasureSpec,
0
)
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
val lp = child.layoutParams as MarginLayoutParams
val childWidthWithMargins = childWidth + lp.leftMargin + lp.rightMargin
val childHeightWithMargins = childHeight + lp.topMargin + lp.bottomMargin
if (currentWidth + childWidthWithMargins + paddingRight > widthSize) {
// Move to next line
currentWidth = paddingLeft
currentHeight += maxLineHeight + verticalSpacing
maxLineHeight = 0
}
currentWidth += childWidthWithMargins + horizontalSpacing
maxLineHeight = max(maxLineHeight, childHeightWithMargins)
}
currentHeight += maxLineHeight + paddingBottom
val finalWidth = if (widthMode == MeasureSpec.EXACTLY) widthSize else currentWidth
val finalHeight = if (heightMode == MeasureSpec.EXACTLY) heightSize else currentHeight
setMeasuredDimension(finalWidth, finalHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var currentX = paddingLeft
var currentY = paddingTop
var maxLineHeight = 0
val width = r - l
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child.visibility == GONE) continue
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
val lp = child.layoutParams as MarginLayoutParams
val childWidthWithMargins = childWidth + lp.leftMargin + lp.rightMargin
if (currentX + childWidthWithMargins + paddingRight > width) {
// Move to next line
currentX = paddingLeft
currentY += maxLineHeight + verticalSpacing
maxLineHeight = 0
}
val left = currentX + lp.leftMargin
val top = currentY + lp.topMargin
val right = left + childWidth
val bottom = top + childHeight
child.layout(left, top, right, bottom)
currentX += childWidthWithMargins + horizontalSpacing
maxLineHeight = max(maxLineHeight, childHeight + lp.topMargin + lp.bottomMargin)
}
}
override fun generateDefaultLayoutParams(): LayoutParams {
return MarginLayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
return MarginLayoutParams(p)
}
override fun checkLayoutParams(p: LayoutParams?): Boolean {
return p is MarginLayoutParams
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout">
<attr name="horizontalSpacing" format="dimension" />
<attr name="verticalSpacing" format="dimension" />
</declare-styleable>
</resources>
Custom ViewGroups enable specialized layout behavior beyond standard containers. I extend ViewGroup and override onMeasure() to measure children and onLayout() to position them. measureChild() or measureChildWithMargins() determines child sizes. Layout params define child-specific configuration. requestLayout() triggers remeasure when properties change. Custom attributes use attrs.xml and obtainStyledAttributes(). Performance matters—minimize allocations in measure/layout passes. Custom ViewGroups power complex UIs like carousels, flow layouts, or masonry grids. They provide precise control over child positioning, enabling designs impossible with standard layouts while maintaining proper Android measurement and drawing contracts.
Related snips
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
Dependency injection with Hilt
package com.example.myapp.ui
import androidx.lifecycle.*
import kotlinx.coroutines.launch
class UserViewModel(
LiveData transformations and mediators
<h1>Products</h1>
<%= form_with url: products_path, method: :get,
data: { turbo_frame: "products_list", turbo_action: "advance" } do |f| %>
<div class="filters">
<%= f.text_field :q, value: params[:q], placeholder: "Search products" %>
Frame navigation that targets a specific frame via form_with
package com.example.myapp.utils
import android.os.Build
import android.os.StrictMode
import android.os.Trace
import timber.log.Timber
Performance optimization and profiling
import SwiftUI
struct CardModifier: ViewModifier {
var backgroundColor: Color = .white
var cornerRadius: CGFloat = 12
var shadowRadius: CGFloat = 5
Custom SwiftUI view modifiers for reusability
package com.example.myapp.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
App widgets for home screen
Share this code
Here's the card — post it anywhere.