Skip to main content

Working with Advanced UI

1. Using Custom Attributes

Attribute Type Description Example
app:customAttribute String Custom attribute defined in XML layout <TextView app:customAttribute="value" />
app:customInt Integer Custom integer attribute for configuration <Button app:customInt="42" />
app:customBoolean Boolean Custom boolean attribute to toggle behavior <Switch app:customBoolean="true" />

Example: Defining Custom Attributes in XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:customAttribute="Hello World"
        app:customInt="100"
        app:customBoolean="false" />

</LinearLayout>

Example: Reading Custom Attributes in Kotlin

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    init {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
        val customAttribute = typedArray.getString(R.styleable.CustomView_customAttribute)
        val customInt = typedArray.getInt(R.styleable.CustomView_customInt, 0)
        val customBoolean = typedArray.getBoolean(R.styleable.CustomView_customBoolean, false)

        typedArray.recycle()
    }
}
Note: Custom attributes must be defined in res/values/attrs.xml to be accessible in code.

2. Implementing Custom Drawing

Example: Basic Custom View with Drawing

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class CustomDrawingView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint().apply {
        color = Color.BLUE
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(100f, 100f, 50f, paint)
    }
}
Property Description Values
Canvas Used to draw graphics primitives drawCircle(), drawRect(), drawLine()
Paint Defines visual properties for drawing color, style, strokeWidth
Color Defines color for drawing operations Color.BLUE, Color.RED
Note: Always call super.onDraw() to ensure proper rendering.
Warning: Avoid heavy computations in onDraw() to maintain UI responsiveness.

3. Creating Shape Drawables

Shape drawables are XML files that define geometric shapes such as rectangles, ovals, lines, and arcs. They can be used as backgrounds or for custom UI elements.

Property Values Description
android:shape rectangle, oval, line, ring Defines the basic shape type
android:width 1dp, 50dp Sets the width of the shape (for line and ring)
android:height 1dp, 50dp Sets the height of the shape (for line and ring)
android:innerRadius 10dp Inner radius for ring shape (optional)
android:thickness 5dp Thickness of the ring (optional)

Example: Rectangle Shape Drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#FF5722" />
    <corners android:radius="8dp" />
</shape>

Example: Oval Shape Drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <gradient
        android:startColor="#00FF00"
        android:endColor="#0000FF"
        android:angle="45" />
</shape>

Example: Ring Shape Drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:innerRadius="20dp"
    android:thickness="5dp"
    android:useLevel="false">
    <solid android:color="#000000" />
</shape>

4. Using State List Drawables

State List Drawables are XML resources that define different drawables for different states of a view. They are commonly used to change the appearance of UI elements based on their state (e.g., enabled, pressed, focused).

Property Description Values
android:drawable Specifies the drawable to use for a given state <drawable>
android:state_* Defines the state condition (e.g., state_pressed, state_enabled) true or false

Example: State List Drawable XML

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/button_pressed" android:state_pressed="true" />
    <item android:drawable="@drawable/button_normal" android:state_enabled="true" />
    <item android:drawable="@drawable/button_disabled" android:state_enabled="false" />
</selector>

Example: Applying State List Drawable to Button

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/button_states" />

5. Implementing Layer Drawables

A LayerDrawable is a drawable that manages an array of drawables to be drawn on top of each other. It's useful for creating complex UI elements like badges, overlays, or layered backgrounds.

Property Description Example
drawable Drawable resource or object to be layered <item android:drawable="@drawable/overlay" />
id Identifier for referencing in code <item android:id="@+id/overlay" />
gravity Gravity to align the drawable within the layer android:gravity="center"

Example: Layer Drawable XML

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/background" />
    <item
        android:drawable="@drawable/badge"
        android:gravity="bottom|end"
        android:mipMap="true" />
</layer-list>

Example: Using Layer Drawable in Code

val layerDrawable = ContextCompat.getDrawable(context, R.drawable.layered_drawable) as LayerDrawable
val badgeLayer = layerDrawable.findDrawableByLayerId(R.id.badge)
badgeLayer?.setAlpha(128)
Note: Layer drawables can be animated using AnimationDrawable or ObjectAnimator for dynamic UI effects.
Warning: Ensure that drawables used in layer lists are compatible with each other to avoid unexpected rendering behavior.

6. Using Nine-Patch Images

Nine-patch images are a special type of PNG image that allows you to define stretchable areas and content insets. They are commonly used for UI elements like buttons, backgrounds, and other components that need to scale without losing their visual integrity.

Feature Description Usage
Stretchable Areas Defined by black pixels on the left and top edges of the image. Indicate which parts of the image can be stretched to fit different sizes.
Content Insets Defined by black pixels on the right and bottom edges of the image. Specify where content should be placed inside the image to avoid stretching.

Example: Nine-Patch Image Setup

Create a .9.png file in your project's res/drawable directory.

res/drawable/button_background.9.png

The image must have a black pixel border indicating stretchable areas and content insets.

Note: The .9.png extension is crucial for Android to recognize it as a nine-patch image.
Warning: Ensure that the black pixels are placed correctly on the borders to define stretchable and content areas accurately.

Key Points

  • Nine-patch images are used to scale UI elements without distortion.
  • They must have a .9.png extension to be recognized by Android.
  • Black pixels on the borders define stretchable areas and content insets.

7. Creating Ripple Effects

Property Values Description
android:background ?android:attr/selectableItemBackground Applies a ripple effect to the view when touched
android:background ?android:attr/selectableItemBackgroundBorderless Applies a ripple effect that extends beyond the view bounds

Example: Ripple Effect on Button

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Tap Me"
    android:background="?android:attr/selectableItemBackground" />

Example: Ripple with Custom Color

<View
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="?android:attr/selectableItemBackground"
    android:clickable="true" />
Note: Ripple effects are only supported on API level 21+

8. Implementing Clipping

Clipping in Android UI allows you to define a custom shape or region that limits the visible area of a view. This is useful for creating visually appealing layouts and custom UI elements.

Property Description Values
android:clipChildren Controls whether children are clipped to the bounds of their parent view. true or false
android:clipToPadding Controls whether the view clips its content to its padding. true or false

Example: Using clipChildren

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clipChildren="false">
    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/image"
        android:layout_marginStart="50dp" />
</LinearLayout>

Example: Using clipToPadding

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clipToPadding="false">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Content that extends beyond padding" />
    </LinearLayout>
</ScrollView>
Note: Clipping affects performance, especially when used in nested views or during animations. Use it sparingly to maintain smooth UI rendering.

9. Using Elevation and Shadows

Property Values Description
android:elevation 0dp to 24dp Sets the z-position of a view in the UI hierarchy. Higher values appear above lower ones.
android:translationZ 0dp to 24dp Adjusts the z-position of a view during animations or interactions.
android:outlineProvider bounds, paddedBounds, none Determines how the view's shadow is calculated. bounds uses view bounds, paddedBounds includes padding.

Example: Setting Elevation on a Button

<Button
    android:id="@+id/elevatedButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:elevation="8dp"
    android:translationZ="4dp"
    android:text="Elevated Button" />

Example: Using Shadow with CardView

<androidx.cardview.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="120dp"
    app:cardElevation="12dp"
    app:cardCornerRadius="8dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Card with Elevation" />

</androidx.cardview.widget.CardView>
Note: Elevation is supported on API level 21+ (Android 5.0). For older versions, use CardView to simulate elevation effects.

Elevation and Shadows Summary

  • Elevation creates a 3D-like depth effect.
  • TranslationZ allows dynamic elevation changes during animations.
  • CardView is a convenient way to apply elevation on older Android versions.

10. Working with WindowInsets

Property Description Values
WindowInsets.Type.statusBars() Represents the status bar area Int
WindowInsets.Type.navigationBars() Represents the navigation bar area Int
WindowInsets.Type.ime() Represents the on-screen input method area Int
WindowInsets.Type.systemGestures() Represents system gesture areas Int
WindowInsets.Type.displayCutout() Represents display cutouts (notch, camera hole) Int

Example: Requesting Insets in a View

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val view = findViewById<View>(R.id.main_view)
        ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
            // Handle insets here
            insets
        }
    }
}

Example: Using WindowInsetsCompat

val insets = ViewCompat.getRootWindowInsets(view)
insets?.let {
    val systemGesturesInsets = it.getInsets(WindowInsetsCompat.Type.systemGestures())
    val imeInsets = it.getInsets(WindowInsetsCompat.Type.ime())
}
Note: WindowInsets is part of the AndroidX library and provides a more consistent way to handle insets across different Android versions.

11. Implementing Edge-to-Edge Design

Example: Enabling Edge-to-Edge in Android

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Enable edge-to-edge design
            val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView)
            windowInsetsController?.hide(WindowInsetsCompat.Type.systemBars())
        }
    }
}
Property Description Notes
WindowInsetsController Controls how system UI insets are handled Available from API level 30+
systemBars() Represents system bars (status bar, navigation bar) Used to hide or show these UI elements
setDecorFitsSystemWindows() Controls whether the window fits system windows Set to false for edge-to-edge design
Note: Edge-to-edge design allows content to extend under system UI elements like status and navigation bars.
Warning: Ensure that content is still readable and accessible when system UI is hidden.

Key Points for Edge-to-Edge Design

  • Use WindowInsetsController to control system UI visibility
  • Set setDecorFitsSystemWindows(false) to enable full-screen mode
  • Test on devices with different system UI sizes (e.g., notch, gesture navigation)

Was this article helpful?