When it comes to building apps for Android TV, things get pretty different when compared to building apps for phones. The experience completely changes for the user – in both the way that content is consumed and how the device is interacted with. Luckily for us we have the Leanback library from Android Jetpack which aims to make Android TV app development simpler for developers, providing ways to create consistent experiences for our users with the apps that we’re building.
In this first article I want to dive into the core part of the leanback library, the Browse Fragment. This fragment is used to display content to our users which can be navigated in order to find content to consume. There are two parts that make up the Browse Fragement – the branding and the content. In this article we’ll look at the branding side of things – so with that in mind, let’s jump in and take a look at how we can handle the styling of this screen for our brand within our Android TV apps.
When it comes to displaying browsable content within our leanback applications, the Browse Support Fragment is our starting point. This fragment provides us with the foundations for displaying sectioned content on screen to users, allowing them to easily navigate to content for consumption. Along with this means of displaying content to our users there is also a selection of functionality that can be used to give a sense of branding to this screen, this functionality is contained inside of the parent fragment class known as the Branded Support Fragment.
In this first part we’re going to take a look at how we can utilise the offerings of the Branded Support Fragment so that we can tweak the look and feel of the browse screen. To begin with, let’s take a quick look at what an example browse screen looks like using the leanback library.
As we can see, there are a collection of different UI components that make up this screen. For now we’ll focus on the branding and how we can control that for our application. Let’s begin with our brand color, as this can be a pretty big part of our branding and play an important role in our application. The branding color of our application is used on the starting side of our browse fragment, this is essentially a sidebar used to contain the navigation components of the screen and is known as the Headers Fragment.
Customising branding colors
With the leanback library, we can control a couple of colors that are used within our browse fragment. To begin with, the color of the headers fragment can be set by using the brandColor property of the the Branded Support Fragment. The setter method for this property takes a @ColorInt reference, which you can also retrieve using its getter method should you require the reference in future.
brandColor = ContextCompat.getColor(context, R.color.color_primary)
In this screenshot we can also see a search icon – when this is selected, a search screen is displayed which allows the user to search for content which may usually have been browsed for. The color of this can view can be controlled via the setter method which again takes a @ColorInt reference, also offering a getter method should it need accessing.
searchAffordanceColor = ContextCompat.getColor(context, color_accent)
Customising the title view content
As well as providing us with the ability to control some of the colours used for the screen, we have access to a few other things also. Looking back at the screenshot above, we have a title which is displayed at the top and aligned to the end:
This text is a simple character sequence stored with the Branded Support Fragment – we can set this to something that suits the branding of our application via the provided setter:
title = getString(R.string.browse_title)
The title can also be retrieved using its provided getter method. When it comes to setting the title, it’s important to be aware that using a long title will cause some visual oddities. In the example below you can see how the title is now overlapping with the menu section and is also hidden behind the search button. Other than those points, it’s important to note that the title is limited to one line, as we can see here that the title has become truncated at the end. Regardless of these oddities, you should try and limit this title to a short length to avoid any unexpected behaviours.
In some cases, applications will want to display a title or may want to hide it given certain conditions – it might even be the case that shortening the title for your brand is just not feasible. Here, the showTitle() method can be called, passing in a boolean value which represents whether or not you want the title to be displayed within the browse screen.
When not showing a title, it may make sense for you to display a more visual kind of branding, such as a logo, in place of where the title is usually displayed.
If you wish to do so, the badgeDrawable can be set and retrieved using the provided setter and getter methods. When setting the drawable, pass a Drawable reference that you wish to be displayed here.
badgeDrawable = ContextCompat.getDrawable(context, R.drawable.ic_android_24dp)
It’s important to note that you cannot set both a badge drawable and title with the Browse Fragment – you can only use one or the other at any given time.
Creating a custom title view
If the ability to set textual or visual content for branding here isn’t quite enough, the Branded Support Fragment offers a final option which can be used to assign a view in position of the title. Now, because the title is classified as the whole top area of the browse screen we may need to manually provide all views that are required – this includes the search orb and title content (title text / drawable) which are initially included in the default title layout. This will depend on the use case of the screen, but it’s important to be sure that the removal of things such as the search orb is not going to hinder the experience for the user. Let’s begin by creating our custom title view:
class TitleView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RelativeLayout(context, attrs, defStyleAttr), TitleViewAdapter.Provider { }
You’ll notice here that so far this mostly looks like any other custom view that we might create – with one difference of the class implementing TitleViewAdapter.Provider. When we pass our custom title view to the Branded Support Fragment it requires the view to have implemented this interface, hence why our custom view is doing so. Before we go and do anything else in this class we’ll go ahead an d put together a simple layout for our title view. We’re going to display the search orb, a textview and then an icon for our branding – this gives us a layout that looks a little like so:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <androidx.leanback.widget.SearchOrbView android:id="@+id/search_orb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" android:layout_marginTop="8dp" android:layout_marginStart=“48dp" /> <TextView android:id="@+id/text_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toStartOf="@+id/image_icon" android:textAllCaps="true" android:textColor=“@color/text_primary” android:textSize="20sp" android:visibility="visible" /> <ImageView android:id="@+id/image_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_marginStart="16dp" android:src="@drawable/ic_android_24dp" /> </merge>
We’ll then inflate this layout inside of our view so that these view components are displayed within the content of our title view.
init { LayoutInflater.from(context).inflate(R.layout.view_title, this) }
At this point we’re almost ready to use our custom title view, but you may remember that we still need to satisfy the implementation of the TitleViewAdapter.Provider. To satisfy this requirement in our class we need to override the getTitleViewAdapter() method and return an implementation of the TitleViewAdapter class. This class is responsible for handling the setting of the different branding options available within the title – so if a developer calls any methods that handle the display of the title, badge or orb then our custom title view will still handle these calls using the adapter. Let’s take a look at what a simple implementation of this would look like in our class:
override fun getTitleViewAdapter() = object : TitleViewAdapter() { override fun getSearchAffordanceView(): View { return search_orb } override fun setTitle(titleText: CharSequence?) { text_title.text = titleText } override fun setBadgeDrawable(drawable: Drawable?) { } override fun setOnSearchClickedListener(listener: View.OnClickListener) { } override fun updateComponentsVisibility(flags: Int) { val visibility = if (flags and SEARCH_VIEW_VISIBLE == SEARCH_VIEW_VISIBLE) View.VISIBLE else View.INVISIBLE search_orb.visibility = visibility } }
- We’ll begin with getSearchAffordanceView(), this is used to return a reference to the search orb within our title layout. You’ll notice that this does not return a nullable type, which reinforces this importance of this view in our layout. Here we just need to return a reference to the search orb component that is defined within our layout.
- Next we have setTitle(). As mentioned above, when calling setTitle from our browse fragment the branded support fragment will propagate the call down to this adapter so that our custom view can set the title to be displayed. In our layout we have a text_title view defined, so here we just set the text of that view.
- The setBadgeDrawable() is again propagated from the call made within our Browse Fragment. I’ve omitted this here for brevity, but again similar to setTitle() we would set the drawable used for the ImageView of our title view layout.
- The setOnSearchClickedListener() is also propagated from the call made within our Browse Fragment. I’ve omitted this here for brevity, but when a click listener for the search orb is set from the browse fragment, this will be propagated to our title view and the search orb within here will be assigned the listener.
- Finally, the updateComponentsVisibility() method is used internally to update the visibility of our title views, depending on the flags which have been set. For brevity here I’m just controlling the search orb visibility. There are also the BRANDING_VIEW_VISIBLE and FULL_VIEW_VISIBLE flags, these can be used to display only branding content or show all of the content within the title view.
The last thing we need to do here is assign our custom title layout to the browse title component. We’re going to assign this via a custom style for our activity, who’s parent is that of the Theme.Leanback.Browse theme. Here we set the browseTitleViewLayout to the reference that we created above for our custom title layout, along with giving some margin the the top of our browse rows using browseRowsMarginTop to account for the required spacing at the top of our screen.
<style name="AppTheme.Leanback.CustomTitle" parent="Theme.Leanback.Browse"> <item name="browseTitleViewLayout">@layout/layout_browse_title</item> <item name="browseRowsMarginTop">160dp</item> </style>
Alongside this style attributes there a collection of other things that can be styled within our browse screen:
browsePaddingStart – assign padding to the start of the browse fragment
browsePaddingEnd – assign padding to the end of the browse fragment
browsePaddingTop – assign padding to the top of the browse fragment
browsePaddingBottom – assign padding to the bottom of the browse fragment
browseRowsMarginStart – assign margin to the start of the browse area, used when the headers fragment is visible on screen
browseRowsMarginTop – assign margin to the top of the browse area, used when the title of the browse fragment is visible on screen
browseRowsFadingEdgeLength – declare the length of the fading edge for the start of the browse row when the headers fragment is visible on screen
browseTitleTextStyle – style used for the title of the browse fragment
browseTitleIconStyle – style used for the icon of the browse fragment
browseTitleViewStyle – style used for the layout view of the browse fragment
browseTitleViewLayout – layout reference used for the title of the browse fragment
headersVerticalGridStyle – style used for the headers within the headers fragment, extending from Widget.Leanback.Headers.VerticalGridView
headerStyle – style used for the overall theme of the headers within the headers fragment, extending from Widget.Leanback.Header
sectionHeaderStyle – style used for the the individual header sections with the headers fragment, extending from Widget.Leanback.Header.Section
rowsVerticalGridStyle – style used for the rows of a vertical grid, extending from Widget.Leanback.Rows.VerticalGridView
rowHorizontalGridStyle – style used for the rows of a horizontal grid, extending from Widget.Leanback.Row.HorizontalGridView
rowHeaderStyle – style used for the headers of the browse row, extending from Widget.Leanback.Row.Header
rowHeaderDescriptionStyle – style used for the description of a header item, extending from Widget.Leanback.Row.Header.Description
rowHeaderDockStyle – style used for the docked display if row headers, extending from Widget.Leanback.Row.HeaderDock
In this post we’ve looked at how we can setup the Browse screen of our android TV to be customised to our branding requirements. Whilst the screen itself aims to bring a consistent experience across applications, we can still give the screen our own individual touch that helps to make it feel on brand for our users.
In the next post we’ll take a look at how we can populate this screen with content to display to our users. But in the meantime, if there are any questions so far then please feel free to reach out!
[twitter-follow screen_name=’hitherejoe’ show_count=’yes’]