Reading Time: 5 minutes

A colored MediaRouteButton

By default, you have two MediaRoute button styles. A dark and a light variant, which have either a white or ablack icon. But what if you want to style the button to match your app, with a specific color?

The MediaRouteButton allows specifying a custom drawable for the icon, which is great, the only problem is that the default drawable is quite complicated, it has multiple states combined with animations (when it is connecting).

Since the default icon is well-known to most Android users, my idea was to base off my colored icon from that one. If you want the same color, you are still in luck, you just need to add an extra property on the MediaRouteButton in XML:

app:mediaRouteButtonTint=”@color/colorPrimary”

But what if you want different colors for the different states? So for example, grey if there’s not device to cast to, blue if there is, a blueish gradient when connecting, and finally a gradient from blue to green when connected?

For this we need to create a custom remote indicator drawable, based off the default drawable. I’ve started by creating a copy of the default white drawable, called mr_button_dark, and all other referenced drawables which are not bitmaps. As a result we now have 3 drawable XMLs, two for the connecting and connected animations, and one for the remote indicator drawable with all the states, which I named custom_cast_button.

Now we can start adding the colors to each state. First, for the disconnected and disabled state, we will add a tint:

Copy to Clipboard

Then, for the animations, we need to do the same, for each item within the animation:

Copy to Clipboard

Finally, don’t forget to set the remote indicator drawable on the button:

Copy to Clipboard

Now you can see the multi-colored indicator we have created:

advanced styling for android cast widgets

Hiding the button when no devices to connect to

This was a requirement in one of our apps: if the button would be disabled, don’t show it at all. The MediaRouteButton has a property called setAlwaysVisible(), but that’s not exactly what we want. When set to false, the button is still visible if there are no devices on the network (but it is disabled though). To enable automatic hiding, you need to subclass the MediaRouteButton by putting it in the same package as the original MediaRouteButton, so you can override its internal function refreshVisibility(). Here you can do an extra check if there are available routes, and hide the button if there are none. See the source code for the special button here:

Copy to Clipboard

In the animation above, you can see the route selector dialog for a split second. While developing a video player application, I noticed that showing this dialog will reveal the navigation and status bars if the window was in immersive mode. This would overlap the cast button, which does not please the eyes:

route selector in app

After some investigation, this seems to be an issue with dialogs in immersive mode. We need access to the route selector dialog to apply this fix to our app.

Luckily cast allows us to change the dialog factory, which is called when you want to open a cast-related dialog. In the factory, you will get a callback when the dialog should be created. Here we replace the original DialogFragment with our own:

castButton.dialogFactory = object : MediaRouteDialogFactory() {
override fun onCreateChooserDialogFragment(): MediaRouteChooserDialogFragment {
return CustomChooserDialogFragment()
}
}

And in our custom chooser fragment, we apply the fix from the StackOverflow answer:

class CustomChooserDialogFragment : MediaRouteChooserDialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
dialog.window?.decorView?.systemUiVisibility = requireActivity().window.decorView.systemUiVisibility
dialog.setOnShowListener {
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
val wm = requireContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager
wm.updateViewLayout(dialog.window?.decorView, dialog.window?.attributes)
}
return dialog
}
}

By applying this fix, the navigation and status bars don’t show anymore when clicking on the MediaRouteButton:

route selector app

You could even override other things in the DialogFragment, or even change the style of the dialogs themselves, like converting it to a BottomSheetDialog as the Facebook app does it. Customizing has one major drawback though, and that is breaking compatibility. Perhaps the next MediaRouter library version will rename the Cast status icons, and it will break your colored indicator drawable. Or the window flags will change, or the dialog style will be different, which might lead to unique bugs, and countless debugging & fixing hours. So keep in mind, that going with the default version might be better in the long run, and that is what the users are familiar with from other apps.

app:mediaRouteButtonTint=”@color/colorPrimary”

Written by Dániel Zolnai

Software Engineer

Usually, Dániel has already done the job before you have even asked him to start. And he always delivers top quality work. This has made him our in-house psychic. He does not do Toto-forecasts however, which is a bummer.

Published on: May 25th, 2020

Newsletter

Become an app expert? Leave your e-mail.

nederlandse loterij en egeniq
pathe thuis en egeniq
rpo and egeniq
mvw en egeniq
rtl and egeniq