ui-component

Maven CentralMaven metadata URLAndroid Min SDK

This is a dependency for UI (user interface) related components.

Configure Dependency

You can add this module to your project using the following method.

Add dependency in your project's gradle/libs.versions.toml.

[versions]
betterandroid-ui-component = "<version>"

[libraries]
betterandroid-ui-component = { module = "com.highcapable.betterandroid:ui-component", version.ref = "betterandroid-ui-component" }

Configure dependency in your project's build.gradle.kts.

implementation(libs.betterandroid.ui.component)

Please change <version> to the version displayed at the top of this document.

Traditional Method

Configure dependency in your project's build.gradle.kts.

implementation("com.highcapable.betterandroid:ui-component:<version>")

Please change <version> to the version displayed at the top of this document.

Function Introduction

You can view the KDoc click hereopen in new window.

Looking for Adapter?

Adapter related features have been separated into an independent module ui-component-adapter, which will be updated separately in the future.

Looking for Insets?

Insets related features have been migrated to ui-extension → Insets Extension, which will be updated following this module in the future.

Activity

Contents of This Section

AppBindingActivityopen in new window

Activity with view binding (inherited from AppCompatActivity).

AppViewsActivityopen in new window

Base view component Activity (inherited from AppCompatActivity).

AppComponentActivityopen in new window

Basic component Activity (inherited from ComponentActivity).

Available for Jetpack Compose projects.

Tips

The preset components below all implement IBackPressedControlleropen in new window, ISystemBarsControlleropen in new window interface.

You can find detailed usage methods in System Event and System Bars (Status Bars, Navigation Bars, etc) below.

When using ViewBinding, you can use AppBindingActivity to quickly create an Activity with view binding.

In AppBindingActivity, you can directly use the binding property to obtain the view binding object without manually calling the setContentView method.

The following example

class MainActivity : AppBindingActivity<ActivityMainBinding>() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.mainText.text = "Hello World!"
    }
}

Tips

If you need to perform some custom operations before the view is loaded, in general, you might do this.

The following example

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        doSomething()
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.main_text).text = "Hello World!"
    }

    private fun doSomething() {
        // Your code here.
    }
}

When using AppBindingActivity, you need to do this.

The following example

class MainActivity : AppBindingActivity<ActivityMainBinding>() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.mainText.text = "Hello World!"
    }

    override fun onPrepareContentView(savedInstanceState: Bundle?): LayoutInflater {
        doSomething()
        // You can return the processed LayoutInflater instance,
        // which will be used to initialize the layout.
        return super.onPrepareContentView(savedInstanceState)
    }

    private fun doSomething() {
        // Your code here.
    }
}

You can also use AppViewsActivity to create a basic Activity and use the findViewById method to get the View.

The following example

class MainActivity : AppViewsActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.main_text).text = "Hello World!"
    }
}

If your project is a Jetpack Compose project, you can use AppComponentActivity to create a basic Activity.

The following example

class MainActivity : AppComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello World!")
        }
    }
}

Tips

For related extensions of Jetpack Compose, you can refer to compose-extension, compose-multiplatform.

BetterAndroid also provides related extensions for Activity, you can refer to ui-extension → Activity Extension.

Fragment

Contents of This Section

AppBindingFragmentopen in new window

Fragment with view binding.

AppViewsFragmentopen in new window

Base view component Fragment.

Tips

The preset components below all implement IBackPressedControlleropen in new window, ISystemBarsControlleropen in new window interfaces.

You can find detailed usage methods in System Event and System Bars (Status Bars, Navigation Bars, etc) below.

When using ViewBinding, you can use AppBindingFragment to quickly create a Fragment with view binding.

In AppBindingFragment, you can directly use the binding property to obtain the view binding object without manually overriding the onCreateView method.

You don’t need to consider the impact of Fragment’s lifecycle on binding, BetterAndroid has already handled these issues for you.

The following example

class MainFragment : AppBindingFragment<FragmentMainBinding>() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.mainText.text = "Hello World!"
    }
}

You can also use AppViewsFragment to create a basic Fragment.

Similarly, you don't need to override the onCreateView method, just fill in the layout resource ID that needs to be bound directly into the constructor.

The following example

class MainFragment : AppViewsFragment(R.layout.fragment_main) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.findViewById<TextView>(R.id.main_text).text = "Hello World!"
    }
}

Tips

BetterAndroid also provides related extensions for Fragment, you can refer to ui-extension → Fragment Extension.

System Event

Contents of This Section

BackPressedControlleropen in new window

Back pressed event controller.

OnBackPressedCallbackopen in new window

Simple back pressed event callback.

IBackPressedControlleropen in new window

Back pressed event controller interface.

An OnBackPressedDispatcher has been provided for developers in the androidx dependency androidx.activity:activity.

However, out of dissatisfaction with the official rash deprecated overwriting of the onBackPressed method, BetterAndroid encapsulated the OnBackPressedDispatcher related functions.

It supports the back pressed event callback function that is more suitable for Kotlin writing, and also adds the function of ignoring all callback events and directly releasing the back pressed event, making it more flexible and easy to use.

AppBindingActivity, AppViewsActivity, AppComponentActivity, AppBindingFragment, AppViewsFragment have implemented the IBackPressedController interface by default, you can directly use backPressed to obtain BackPressedController.

But you can still manually create a BackPressedController in Activity.

The following example

class YourActivity : AppCompatActivity() {

    // Create a lazy object.
    val backPressed by lazy { BackPressedController.from(this) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Call backPressed here to implement related functions.
        backPressed
    }

    override fun onDestroy() {
        super.onDestroy()
        // Destroy backPressed, this will remove all callbacks.
        // Optional, prevent memory leaks.
        backPressed.destroy()
    }
}

The following is the basic usage of BackPressedController.

The following example

// Add a back pressed callback
val callback = backPressed.addCallback {
    // Ignore the current callback within the callback and trigger the back operation.
    // For example, you can pop up a dialog here to ask the user whether to exit
    // and select "Yes" at this time.
    // The object passed in needs to be the backPressed that created this callback.
    trigger(backPressed)
    // Or remove itself at the same time after triggering.
    trigger(backPressed, removed = true)
    // Remove directly. (Not recommended, you should use backPressed.removeCallback)
    remove()
}
// You can also create a callback manually.
// Note: Please make sure to import com.highcapable.betterandroid.ui.component.backpress.callback
//       OnBackPressedCallback under the package name, NOT androidx.activity.OnBackPressedCallback
val callback = OnBackPressedCallback {
    // Your code here.
}
// Then add to backPressed.
backPressed.addCallback(callback)
// Remove a known callback.
backPressed.removeCallback(callback)
// Trigger the back operation of the system.
backPressed.trigger()
// You can set ignored to true to ignore all added callbacks and back directly.
backPressed.trigger(ignored = true)
// Determine whether there is currently an enabled callback
val hasEnabledCallbacks = backPressed.hasEnabledCallbacks
// Destroy, this will remove all callbacks.
backPressed.destroy()

Notice

After using BackPressedController, the current OnBackPressedDispatcher has been automatically taken over by it.

You should not continue to use onBackPressedDispatcher.addCallback(...) as this will expose unknown (wild) callbacks and prevent them from being cleanly removed.

Notification

Contents of This Section

NotificationBuilderopen in new window

System notification builder.

NotificationChannelBuilderopen in new window

System notification channel builder.

NotificationChannelGroupBuilderopen in new window

System notification channel group builder.

NotificationImportanceopen in new window

System notification importance.

NotificationPosteropen in new window

System notification poster.

Notificationopen in new window

Extension methods for the notification builds.

It is not easy to create and send a notification in Android.

The biggest problem is that the creation of system notifications is complicated, management is confusing, and the API is difficult to be easily compatible with older versions.

Especially when developers see the two classes NotificationCompat and NotificationChannelCompat, they will feel at a loss to start.

So BetterAndroid comprehensively encapsulates the system notification-related APIs, basically covering all functions and calls that can be used in system notifications.

So you no longer need to consider compatibility issues with Android 8 and below systems like notification channels, BetterAndroid has already taken care of these issues for you.

In Kotlin you can create a system notification more easily.

The following example

// Assume this is your current Context.
val context: Context
// Create the notification object that needs to be posted.
val notification = context.createNotification(
    // Create and set up notification channels.
    // A notification channel must exist in Android 8 and above systems.
    // In systems lower than Android 8, this feature will be automatically compatible.
    channel = NotificationChannel("my_channel_id") {
        // Set the notification channel name.
        // (this will be displayed in the system's notification settings)
        name = "My Channel"
        // Set notification channel description.
        // (this will be displayed in the system's notification settings)
        description = "My channel description."
        // The rest of the usage is consistent with NotificationChannelCompat.Builder.
    }
) {
    // Set the notification icon. (this will be displayed in the status bar and notification bar)
    // The notification icon must be a monochrome icon. (a vector image is recommended)
    smallIconResId = R.drawable.ic_my_notification
    // Set notification title.
    contentTitle = "My Notification"
    // Set notification content.
    contentText = "Hello World!"
    // The rest of the usage is consistent with NotificationCompat.Builder.
}
// Post notification using default notification ID.
notification.post()
// Post notification using custom notification ID.
val notifyId = 1
notification.post(notifyId)
// Cancel the current notification. (this will clear the notification from the system notification bar)
notification.cancel()
// Determine whether the current notification has been canceled.
val isCanceled = notification.isCanceled

Notice

In Android 13 and above, you need to define and add runtime permission for notifications.

When this permission is not defined correctly, calling the post method will automatically ask you to add the permission to AndroidManifest.xml.

Please refer to Notification runtime permissionopen in new window.

You can set importance for notifications in notification channels using the following methods.

The following example

// Assume this is your current Context.
val context: Context
// Create the notification object that needs to be posted.
val notification = context.createNotification(
    // Create and set up notification channels.
    // In systems lower than Android 8, this feature will be automatically compatible.
    // Importance determines the importance of the notification,
    // which affects how the notification is displayed.
    // BetterAndroid sets the importance static variable in NotificationManager.
    // Encapsulated into NotificationImportance, you can set the
    // importance of notifications more conveniently.
    // Here we set NotificationImportance.HIGH (high importance),
    // this will display the notification as a banner in the system notification with a ring reminder.
    channel = NotificationChannel("my_channel_id", importance = NotificationImportance.HIGH) {
        name = "My Channel"
        description = "My channel description."
    }
) {
    smallIconResId = R.drawable.ic_my_notification
    contentTitle = "My Notification"
    contentText = "Hello World!"
}
// Post notification using default notification ID.
notification.post()

When encountering multiple groups of notifications, you can use the following methods to create a group of notification channels.

The following example

// Assume this is your current Context.
val context: Context
// Create a notification channel group.
// In systems lower than Android 8, this feature will have no effect.
val channelGroup = NotificationChannelGroup("my_channel_group_id") {
    //Set the notification channel group name.
    // (this will be displayed in the system's notification settings)
    name = "My Channel Group"
    // Set notification channel group description.
    // (this will be displayed in the system's notification settings)
    description = "My channel group description."
}
// Create the first notification channel and specify the notification channel group.
val channel1 = NotificationChannel("my_channel_id_1", channelGroup) {
    name = "My Channel 1"
    description = "My channel description."
}
// Create the second notification channel and specify the notification channel group.
val channel2 = NotificationChannel("my_channel_id_2", channelGroup) {
    name = "My Channel 2"
    description = "My channel description."
}
// Use channel1 to create the first notification and post it.
context.createNotification(channel1) {
    smallIconResId = R.drawable.ic_my_notification
    contentTitle = "My Notification 1"
    contentText = "Hello World!"
}.post(1)
// Use channel2 to create the second notification and post it.
context.createNotification(channel2) {
    smallIconResId = R.drawable.ic_my_notification
    contentTitle = "My Notification 2"
    contentText = "Hello World!"
}.post(2)

The above will create a notification channel group and add two notification channels to it.

After the notification is posted, the system will automatically create a group classification for these two notification channels.

Notice

The settings in the notification channel will only take effect when the notification channel is first created.

If the settings of the notification channel are modified by the user, these settings will not be overwritten again.

You cannot modify the settings of a notification channel that has already been created, but you can reassign it a new notification channel ID, which will create a new notification channel.

Please refer to Create and manage notification channelsopen in new window.

In the above example, the notification object is managed automatically, if you want to manually create a notification object without relying on the context.createNotification method, please refer to the following example.

The following example

// Assume this is your current Context.
val context: Context
// Create the notification object that needs to be posted.
val notification = Notification(
    // Set Context.
    context = context,
    // Create and set up notification channels.
    channel = NotificationChannel("my_channel_id") {
        name = "My Channel"
        description = "My channel description."
    }
) {
    smallIconResId = R.drawable.ic_my_notification
    contentTitle = "My Notification"
    contentText = "Hello World!"
}
// Use the current notification as a post object.
val poster = notification.asPoster()
// Post notification using default notification ID.
poster.post()
// Post notification using custom notification ID.
val notifyId = 1
poster.post(notifyId)
// Cancel the current notification. (this will clear the notification from the system notification bar)
poster.cancel()
// Determine whether the current notification has been canceled.
val isCanceled = poster.isCanceled

Tips

Objects created through Notification, NotificationChannel, NotificationChannelGroup are a wrapper for NotificationCompat, NotificationChannelCompat, and NotificationChannelGroupCompat.

You can use instance to get the actual object to perform some of your own operations.

You can also get the NotificationManagerCompat object through Context.notificationManager to perform some of your own operations.

System Bars (Status Bars, Navigation Bars, etc)

Contents of This Section

SystemBarsControlleropen in new window

System bars controller.

ISystemBarsControlleropen in new window

System bars controller interface.

SystemBarStyleopen in new window

System bar style.

SystemBarsopen in new window

System bars type.

SystemBarBehavioropen in new window

System bars behavior.

The serious adaptation problem of Android development lies in the confusion of terminal devices without unified development specifications.

In order to provide users with a better experience, when should the status bars and navigation bars be displayed or hidden, the color and background of the status bars and navigation bars, etc.

These are all issues that developers need to consider during the development process.

So BetterAndroid docks and encapsulates the system bars adaptation solution provided by androidx and integrates it into SystemBarsController.

Now, you can call it very conveniently to easily implement a series of solutions for the operating system bars.

SystemBarsController supports at least Android 5.0 and solves compatibility issues in some manufacturers' customized systems.

AppBindingActivity, AppViewsActivity, AppComponentActivity, AppBindingFragment, AppViewsFragment have implemented the ISystemBarsController interface by default, you can directly use systemBars to obtain SystemBarsController.

But you can still create a SystemBarsController manually using the Activity.getWindow object in Activity.

The following example

class YourActivity : AppCompatActivity() {

    // Create a lazy object.
    val systemBars by lazy { SystemBarsController.from(window) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Create your binding.
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // Initialize systemBars using the current root view.
        systemBars.init(binding.root)
    }

    override fun onDestroy() {
        super.onDestroy()
        // Destroy systemBars, which will restore the state before initialization.
        // Optional, to prevent memory leaks.
        systemBars.destroy()
    }
}

Notice

When using the init method, it is recommended and recommended to pass in your own root view, otherwise android.R.id.content will be used by default as the root view.

You should avoid using it as the root view, this is uncontrollable, you should be able to maintain your own root view at any time in Activity.

If you are not using ViewBinding, AppViewsActivity and AppComponentActivity have overridden the setContentView method for you by default.

It will automatically load your root view into SystemBarsController when you use this method.

You can also manually override the setContentView method to achieve this functionality.

The following example

override fun setContentView(layoutResID: Int) {
    super.setContentView(layoutResID)
    // The first child view is your root view.
    val rootView = findViewById<ViewGroup>(android.R.id.content).getChildAt(0)
    // Initialize systemBars using the current root view.
    systemBars.init(rootView)
}

The following is a detailed usage introduction of SystemBarsController.

Initialize SystemBarsController and handle window insets padding of root view.

The following example

// Assume this is your current root view.
val rootView: ViewGroup
// Initialize SystemBarsController.
// Your root view must have been set to a parent layout, otherwise an exception will be thrown.
systemBars.init(rootView)
// You can customize the window insets that handle the root view.
systemBars.init(rootView, edgeToEdgeInsets = { systemBars })
// If you don't want SystemBarsController to automatically handle the root view's window insets for you,
// you can directly set edgeToEdgeInsets to null.
systemBars.init(rootView, edgeToEdgeInsets = null)

Notice

SystemBarsController will automatically set Window.setDecorFitsSystemWindows(false) during initialization (on cutout display devices, layoutInDisplayCutoutMode will also be set to LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES), you just need to set edgeToEdgeInsets in init (the default setting), then your root view will have a window insets padding controlled by safeDrawingIgnoringIme, which is why you should be able to maintain your own root view at any time in Activity.

If you set edgeToEdgeInsets to null in init, your root view will fully expand to full screen.

The above effect is equivalent to enableEdgeToEdge provided in androidx.activity:activity.

Without any action, your layout will be blocked by system bars or dangerous areas of the system (such as cutout displays), which will affect the user experience.

If you want to maintain and manage the padding of the current root view yourself, you must ensure that your interface elements can correctly adapt to the spacing provided by window insets.

You can go to the ui-extension → Insets section of the previous section learn more about window insets.

You no longer need to use enableEdgeToEdge, SystemBarsController will hold this effect by default after initialization, you should use edgeToEdgeInsets to control the window insets padding of the root view.

Tips

In Jetpack Compose, you can use AppComponentActivity to get a SystemBarsController initialized with edgeToEdgeInsets = null, then use Jetpack Compose to set window insets.

BetterAndroid also provides extension support for it, for more functions, you can refer to compose-multiplatform.

Set the behavior of system bars.

This determines the system-controlled behavior when showing or hiding system bars.

The following example

systemBars.behavior = SystemBarBehavior.SHOW_TRANSIENT_BARS_BY_SWIPE

The following are all behaviors provided in SystemBarBehavior, those marked with * are the default behaviors.

BehaviorDescription
DEFAULTDefault behavior controlled by the system.
*SHOW_TRANSIENT_BARS_BY_SWIPEA system bar that can be popped up by gesture sliding in full screen and displayed as a translucent system bar, and continues to hide after a period of time.

Show, hide system bars.

The following example

// Enter immersive mode (full screen mode).
// Hide status bars and navigation bars at the same time.
systemBars.hide(SystemBars.ALL)
// Separately control the status bars and navigation bars.
systemBars.hide(SystemBars.STATUS_BARS)
systemBars.hide(SystemBars.NAVIGATION_BARS)
// Exit immersive mode (full screen mode).
// Show status bars and navigation bars at the same time.
systemBars.show(SystemBars.ALL)
// Separately control the status bars and navigation bars.
systemBars.show(SystemBars.STATUS_BARS)
systemBars.show(SystemBars.NAVIGATION_BARS)

Tips

If you need to control the showing and hiding of the input method (IME), you can refer to ui-extension → View Extension.

Set the style of the system bars.

You can customize the appearance of the status bars and navigation bars.

Notice

In systems below Android 6.0, the content of the status bars does not support inversion, if you set a bright color, it will be automatically processed as a semi-transparent mask.

However, for MIUI and Flyme systems that have added the inverse color function themselves, they will use their own private solutions to achieve the inverse color effect.

In systems below Android 8, the content of the navigation bars does not support inversion, and the processing method is the same as above.

The following example

// Set the style of the status bars.
// Note: Please make sure to import com.highcapable.betterandroid.ui.component.systembar.style
//       SystemBarStyle under the package name, not androidx.activity.SystemBarStyle.
systemBars.statusBarStyle = SystemBarStyle(
    // Set background color.
    color = Color.WHITE,
    // Set content color.
    darkContent = true
)
// Set the style of the navigation bars.
systemBars.navigationBarStyle = SystemBarStyle(
    // Set background color.
    color = Color.WHITE,
    // Set content color.
    darkContent = true
)
// You can set the style of the status bars and navigation bars at once.
systemBars.setStyle(
    statusBar = SystemBarStyle(
        color = Color.WHITE,
        darkContent = true
    ),
    navigationBar = SystemBarStyle(
        color = Color.WHITE,
        darkContent = true
    )
)
// You can also set the style of the status bars and navigation bars at the same time.
systemBars.setStyle(
    style = SystemBarStyle(
        color = Color.WHITE,
        darkContent = true
    )
)

The following are the preset styles provided in SystemBarStyle, the ones marked with * are the default styles.

StyleDescription
AutoThe system dark mode is a pure black background + light content color, and the light mode is a pure white background + dark content color.
*AutoTransparentThe light content color is used in the system dark mode, and the dark content color is used in the light mode, with a transparent background.
LightPure white background + dark content color.
LightScrimTranslucent pure white background + dark content color.
LightTransparentTransparent background + dark content color.
DarkPure black background + light content color.
DarkScrimTranslucent solid black background + light content color.
DarkTransparentTransparent background + light content color.

Tips

When the app is first cold-launched, the color of the system bars will be determined by the attributes you set in styles.xml.

In order to provide a better user experience during cold launch, you can refer to the following examples.

The following example

<style name="Theme.MyApp.Demo" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <!-- Set the status bar color. -->
    <item name="android:statusBarColor">@color/colorPrimary</item>
    <!-- Set the navigation bar color. -->
    <item name="android:navigationBarColor">@color/colorPrimary</item>
    <!-- Set the status bar content color. -->
    <item name="android:windowLightStatusBar">true</item>
    <!-- Set the navigation bar content color. -->
    <item name="android:windowLightNavigationBar">true</item>
</style>

Destroy SystemBarsController.

This will restore the state before initialization, including the status bars, navigation bars color, etc.

The following example

// Destroy SystemBarsController, which will restore the state before initialization.
systemBars.destroy()
// You can use isDestroyed at any time to determine whether
// the current SystemBarsController has been destroyed.
val isDestroyed = systemBars.isDestroyed

Notice

After using SystemBarsController, the WindowInsetsController of the current root view rootView has been automatically taken over by it.

Please do not manually set parameters such as isAppearanceLightStatusBars and isAppearanceLightNavigationBars in WindowInsetsController, this may cause the actual effects of statusBarStyle, navigationBarStyle, setStyle and other functions to display abnormally.