When developing Android applications using Kotlin and XML, creating a consistent and customizable user interface is crucial. Android’s theming system, combined with theme attributes (?attr/
and ?android:attr/
), provides a powerful mechanism for achieving this goal. This blog post will explore how to leverage theme attributes to build dynamic and maintainable themes in your Android applications.
What are Theme Attributes?
Theme attributes are placeholders defined in your application’s theme that refer to specific styling properties such as colors, dimensions, or even complex resources like drawables. They act as variables within your XML layouts, allowing you to reference and modify the visual aspects of your UI components easily through theme changes. There are two main types of theme attributes:
- Custom Attributes (
?attr/
): These are attributes that you define in yourattrs.xml
file, allowing you to create custom theming options tailored to your application’s design system. - Android Attributes (
?android:attr/
): These are pre-defined attributes provided by the Android framework that cover a wide range of common styling properties.
Why Use Theme Attributes?
- Centralized Styling: Manage all style-related properties in a single place (the theme).
- Dynamic Theming: Easily switch between different themes (light, dark, custom) by changing theme attribute values.
- Consistency: Ensure UI components across your application maintain a consistent look and feel.
- Maintainability: Updates to the theme automatically propagate to all components referencing the attributes.
How to Use Theme Attributes in Kotlin XML Development
Let’s explore how to define and use both custom and Android theme attributes in an Android project using Kotlin and XML.
Step 1: Define Custom Attributes in attrs.xml
Create a file named attrs.xml
in your res/values/
directory (or modify it if it already exists) and define your custom attributes.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomThemeAttributes">
<attr name="customBackgroundColor" format="color"/>
<attr name="customTextColor" format="color"/>
<attr name="customButtonRadius" format="dimension"/>
</declare-styleable>
</resources>
In this example, we’ve declared a styleable named CustomThemeAttributes
with three custom attributes: customBackgroundColor
, customTextColor
, and customButtonRadius
.
Step 2: Define Theme Styles in styles.xml
Open your res/values/styles.xml
file and define your themes, setting the values for your custom attributes.
<resources>
<style name="BaseTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
<style name="LightTheme" parent="BaseTheme">
<item name="customBackgroundColor">#FFFFFF</item>
<item name="customTextColor">#000000</item>
<item name="customButtonRadius">8dp</item>
</style>
<style name="DarkTheme" parent="BaseTheme">
<item name="customBackgroundColor">#303030</item>
<item name="customTextColor">#FFFFFF</item>
<item name="customButtonRadius">12dp</item>
</style>
</resources>
Here, we’ve defined two themes: LightTheme
and DarkTheme
, both inheriting from a base theme BaseTheme
. We set different values for the custom attributes in each theme to achieve a distinct look and feel.
Step 3: Apply the Theme in AndroidManifest.xml
Set the application’s theme in your AndroidManifest.xml
file.
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/LightTheme"> <!-- Set default theme here -->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
By default, the LightTheme
is applied to the entire application. You can programmatically change the theme at runtime (covered later).
Step 4: Use Theme Attributes in Layout XML
In your layout XML files, reference the theme attributes using the ?attr/
syntax.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="?attr/customBackgroundColor">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, Theme!"
android:textColor="?attr/customTextColor"/>
<Button
android:id="@+id/myButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
android:backgroundTint="?attr/customBackgroundColor"
android:textColor="?attr/customTextColor" />
</LinearLayout>
The android:background
of the LinearLayout
and android:textColor
of the TextView
and Button
are dynamically determined by the customBackgroundColor
and customTextColor
attributes defined in the applied theme.
Step 5: Using Android Attributes (?android:attr/
)
Android provides a rich set of pre-defined attributes you can use in a similar way. For example, to reference the colorPrimary attribute from your theme, you would use ?android:attr/colorPrimary
.
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?android:attr/colorPrimary"
android:theme="?attr/actionBarTheme" />
Step 6: Programmatically Changing Themes in Kotlin
You can change the application theme programmatically at runtime using the setTheme()
method.
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
class MainActivity : AppCompatActivity() {
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = getSharedPreferences("ThemePrefs", Context.MODE_PRIVATE)
val isDarkMode = sharedPreferences.getBoolean("isDarkMode", false)
if (isDarkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
setTheme(R.style.DarkTheme)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
setTheme(R.style.LightTheme)
}
setContentView(R.layout.activity_main)
}
// Method to toggle theme
fun toggleTheme() {
val editor = sharedPreferences.edit()
val isDarkMode = sharedPreferences.getBoolean("isDarkMode", false)
if (isDarkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
setTheme(R.style.LightTheme)
editor.putBoolean("isDarkMode", false)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
setTheme(R.style.DarkTheme)
editor.putBoolean("isDarkMode", true)
}
editor.apply()
// Recreate activity to apply changes (consider alternatives for smoother transition)
recreate()
}
}
Explanation:
- Theme Preference: Stores the user’s theme preference in
SharedPreferences
to persist across app restarts. - Applying Theme: Sets the appropriate theme using
setTheme()
before callingsetContentView()
. This ensures the correct theme is applied before the UI is drawn. - Toggling Theme: The
toggleTheme()
function toggles between the light and dark themes, saves the preference, and recreates the activity. Recreating the activity applies the theme changes, but consider alternative approaches for smoother transitions.
Best Practices for Using Theme Attributes
- Consistency is Key: Ensure your custom attributes are named consistently throughout your application.
- Comment Your Attributes: Add comments to your
attrs.xml
file explaining the purpose of each attribute. - Use Android Attributes When Appropriate: Leverage Android’s pre-defined attributes when they meet your styling needs.
- Create Base Themes: Define a base theme and extend it for different variations (light, dark, etc.).
- Test Thoroughly: Test your themes on different devices and screen sizes to ensure a consistent experience.
Conclusion
Using theme attributes (?attr/
and ?android:attr/
) is a powerful technique for creating themable and maintainable Android applications using Kotlin and XML. By leveraging custom attributes, predefined Android attributes, and proper theming practices, you can build apps that provide a consistent, customizable, and delightful user experience. Understanding and applying these concepts will greatly improve the architecture and quality of your Android projects.