New Android 12+ MaterialSwitch and androidx.preference - android

So i'm updating my apps to fully support Monet and Material You guidelines, and the official site mentions a new design for the switches. I used it, and that's the result:
I have a preference screen using Androidx preferences library, latest version available at the time of writing, and the only way i found to theme the switches (except the manual theming, which makes no sense) is to use this line in the app's theme:
<item name="switchStyle">#style/Widget.Material3.CompoundButton.MaterialSwitch</item>
And using SwitchPreferenceCompat (it doesn't work in the regular SwitchPreference) this is what i get:
Regardless of the width (which is different, but can be changed) the disabled state is completely different and doesn't match the rest of the app. Why? and most importantly, why do they suggest to use a library which:
Doesn't support Material You out of the box
Doesn't support any new Material3 component
It's hard to properly customize in general
?
I don't want to be too critical, but this is out of my understanding.
EDIT: at the moment, i'm using switchCompat everywhere, to make the app uniform. Looking at the system apps, i can find 4 different type of switches: a custom switch similar to the second screenshot, the old one and the two types in this question. That's hella confusing.

I understand Google stance on this, they don't want to make androidx.* packages dependent to Material library itself, maybe they should provide a separate preference package but this time with fully Material widgets.
In order to have the brand new MaterialSwitch of Material 1.7.0 with preference, I've overridden its widgetLayout with a custom layout by android:widgetLayout="#layout/preference_material_switch" (in fact I applied that programmatically like .widgetLayoutResource = R.layout.preference_material_switch) and put the following on preference_material_switch.xml layout file,
<?xml version="1.0" encoding="utf-8"?>
<!-- Derived from https://github.com/androidx/androidx/blob/8cb282cc/preference/preference/res/layout/preference_widget_switch_compat.xml -->
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="false"
android:focusable="false" />
And here is the result,

The same problem, After seeing these replies, I'm thinking to build the settings fragment without androidx.preference.
MKevin3 said:
I hate the provided Android preferences setup and look. So many times they changed the rules and broke what I had.
Not that this helps you probably but I just did my own to avoid all the headaches and I am in full control of the look. If push comes to hove you might consider doing this as well instead of fighting the "Android Way".
sc00ty said:
I gave up trying to use their widgets and fragments. It was so much less of a headache to spend a little time making my own compound widgets for each setting type.

Related

HowTo use support.v7.preference with AppCompat and potential drawbacks

I was trying to implement preferences for an AppCompat app, using support.v7.preference. It took me a couple of days to fiddle through it, since support.v7.preference has some significant differences to the native preferences... which isn't too bad once you know, but unfortunately there's little documentation out there. I thought I'd share my findings so others don't have to go through the same pain.
So... question:
How do you best implement Preferences for AppCompat apps (with PreferenceFragment and AppCompatAcitivity being incompatible)?
Here are a couple of related questions:
Preference sub-screen not opening when using support.v7.preference
How to move back from Preferences subscreen to main screen in PreferenceFragmentCompat?
PreferenceFragmentCompat requires preferenceTheme to be set
How do I create custom preferences using android.support.v7.preference library?
Official docs here:
http://developer.android.com/guide/topics/ui/settings.html
http://developer.android.com/reference/android/support/v7/preference/Preference.html
Solution 1: Native PreferenceFragment with AppCompatActivity
In AndroidStudio, choose File > New Project >...> SettingsActivity. This template uses a workaround that retrofits the native PreferenceFragment to work with AppCompatActivity, similar to the support.v4.Fragment or the support.v7.PreferenceFragmentCompat.
Pro: you can now use the native Preference functionality within an
AppCompat app. It's a quick approach when using the AS template, and you can stick to the existing Preference docs and workflows.
Con: the retrofitting isn't very intuitive or clean. Also since it's usually advisable to use support libs where available, I'm not sure how future-proof this approach is.
Solution 2: support.v7.preference.PreferenceFragmentCompat with AppCompatActivity
Pro: maximizes compatibility
Con: a lot of gaps to bridge. Also this might not work with any of the existing preference-extensions-libs out there (eg. ColorPicker or FontPreferences).
Should you choose not to use Solution 1 (I'm still not sure which of the two is more future proof), there are a couple of drawbacks when using support.v7.preference.
Important drawbacks of using Solution 2 are mentioned below.
Dependencies:
dependencies {
...
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:preference-v7:23.1.1'
compile 'com.android.support:support-v4:23.1.1'
}
Theme:
You'll need to define a preferenceTheme in your styles.xml, otherwise running your app will raise an exception.
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- Customize your theme here. -->
<item name="preferenceTheme">#style/PreferenceThemeOverlay</item>
</style>
You might wanna split this into different styles for 7+/14+/21+. A lot of people complain about this being buggy at the time of this writing. There is a very comprehensive answer available here.
Behavior changes: using the native preferences is extremely straight forward: all you need to do is define/maintain your preferences.xml and use addPreferencesFromResource(R.xml.preferences) within your PreferenceFragment. Custom preferences are easily done by sub-classing DialogPreference, and then just referenced to within the preferences.xml... bam, works.
Unfortunately, support.v7.preference has had everything related to dealing with Fragment stripped out, making it loose a lot of it's built-in functionality. Instead of just maintaining an XML, you now have to sub-class and override a lot of stuff, all of which is unfortunately undocumented.
PreferenceScreens: PreferenceScreens are no longer managed by the framework. Defining a PreferenceScreen in your preference.xml (as described in the docs) will display the entry, but clicking on it does nothing. It's now up to you to deal with displaying and navigating sub-screens. Boring.
There is one approach (described here), adding a PreferenceFragmentCompat.OnPreferenceStartScreenCallback to your PreferenceFragmentCompat. While this approach is quickly implemented, it simply swaps the content of the existing preference fragment. Downside is: there is no back navigation, you're always 'at the top', which isn't very intuitive for the user.
In another approach (described here), you'll also have to manage the back stack in order to achieve back navigation as expected. This uses preferenceScreen.getKey() as a root for each newly created/displayed fragment.
When doing so, you might also stumble over the PreferenceFragments being transparent by default and adding up oddly on top of each other. People tend to override PreferenceFragmentCompat.onViewCreated() to add something like
// Set the default white background in the view so as to avoid transparency
view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.background_material_light));
Custom DialogPreference: Making your own preferences has also gone from trivial to boring. DialogPreference now has anything that deals with the actual dialog, removed. That bit now lives in PreferenceDialogFragmentCompat. So you'll have to sub-class both, then deal with creating the dialog and displaying it yourself (explained here).
Looking at the source of PreferenceFragmentCompat.onDisplayPreferenceDialog() shows that it knows how to deal with exactly 2 dialog preferences (EditTextPreference, ListPreference), everything else you'll have to implement yourself using OnPreferenceDisplayDialogCallbacks... one wonders, why there is no functionality to handle sub-class of DialogPreference!
Here is some code that implements most of these workarounds and boxes them in a lib module:
https://github.com/mstummer/extended-preferences-compat.git
Main intentions were:
Remove the need to extend and fiddle with Activity and PreferenceFragment in each app/projects. preference.xml is now again the only per-project file to change/maintain.
Handle and display PreferenceScreens (sub-screens) as expected.
Un-split DialogPreference to restore the native behavior.
Handle and display any sub-class of DialogPreference.
Don't think it's clean enough to be just used out of the box, but it might give you some hints when dealing with similar issues. Give it a spin and let me know if you've got any suggestions.
I have an alternative solution to this, that i'd love feedback on.
I made a custom layout for my preferencefragment, with a "Back" button in the upper left corner.
First, in the "onCreatePreference" i store away the root PreferenceScreen:
root = this.getPreferenceScreen();
Then, I add the OnPreferenceStartScreenCallback as described above and in other threads to make the fragment go to subscreen, but in my "onPreferenceStartScreen" i also set the back button to visible like this:
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
backButton.setVisibility(View.VISIBLE);
return true;
}
Finally, the backButton clickhandler:
setPreferenceScreen(root);
back.setVisibility(View.GONE);
This seem to work fine for me. Obviously the back stack won't work, but i can live with that since there is a Back button.
Not perfect, but given the abysmal API i think i'm happy.
I would love to hear if someone thinks there are any problems with this approach.

Any way to avoid "android:" statements in Android XML layout file?

Is there any way to avoid hundreds of "android:" statements in an Android XML layout file? I am new to Android and I find the "android:" statements make layouts very hard to read. Plus they are a pain to constantly type.
For example, instead of this:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
I would like to just see this:
layout_width="wrap_content"
layout_height="wrap_content"
layout_column="2"
If you're using Eclipse (and presumably Android Studio has similar functionality, but I've not used it) you can auto complete layout properties, e.g. if you type w then ctrl + space it will auto complete this to android:layout_width in most instances. You never really need to type the android: part.
I'm not aware of a way to remove the namespaces.
No. That is needed to know it is part of the Android framework. Instead of say a custom style or custom View. I've done a little bit of Android coding and it really isn't a big deal.
For example, there is a copy/paste feature which can make it go faster. Also, most IDEs have autocomplete which is very handy.

How to style the children Views from the parent View?

I am currently trying to get the look of my app right. But I am having problems figuring out how to even set up a way to change themes. For one thing, is there even a way to change styles through code? I checked the method list and I saw nothing. This leads me to my actual question; is there a way that, like CSS, in which you style the parent, and then have it trickle down but also changed depending on the View? I looked at the Android docs, and they did not show any examples of this. Hopefully someone can give me an idea as to how to accomplish this, or if its not possible, to let me know that as well. Thanks in advance.
You should be able to do this using styles and themes. I've implemented this using Jake whartons Sherlock action bar. (I'm not certain if it's necessary) It involves using the comparability library which gives you the ability to use fragments and loaders as well. Look at his democode at http://actionbarsherlock.com/download.html. Look for where themes are mentioned and you will find the information you need. In the demo app you can change the theme in the top right corner and see how it affects the activities look and feel. It also shows many of the features available and the code to write them. I have found this an invaluable resource and it should show you how to theme your app.

Android ListView themes

There are lots of way to style ListViews to give them elegant look, but all of them involve modyfying the adapter or writing additional code.
With the release of Android 4.0, unfortunetely things have to change. Google polished their Holo theme and gave it new look. All of the developers are now encouraged to use it, in order to make all apps look the same.
And here's the problem. Google rolled out 4.0, but there are still people using older Android versions. We can't just leave our previous custom application themes and use Holo, because it will ruin visual experience for users with older devices. And we can't force 4.0 users just to use Holo, because let's be honest - it's still not perfect.
The goal is to use builtin themes system and prepare some alternatives for Holo, which will look great on all devices. Then we can just switch between Holo and our themes with just setTheme() and no additional problems. Unfortunetely it's not that simple. We are limited to the capabilities of existing theme system and some things are just hard to do. And here comes my question.
Taking everything I've mentioned into consideration, how can we control ListView look? I'm not able to figure out, how to:
create list with rounded corners and make sure the selector background doesn't ruin it when selecting first/last element
create rounded corners not for the list but sections separated by headers, something like here:
The solution should affect ListViews created by PreferenceActivity without any additional lines of code. Everything should be contained in the theme:
<theme name="SampleTheme" parent="android:Theme">
...
</theme>
I kindly ask not to post solutions that do not use styles & themes. They can be easily found in another questions, here on Stack Overflow.
Thanks in advance.
I can see two ways to solve this.
One is simply to use a theme for your listviews specifying the background, which in turn is a 9 patch with rounded corners or an xml shape you specify (with rounded corners as well). This will have the side-effect of the listview row selector appearing 'over' the background you specified, therefore kind of spoiling the effect. It is quite straightforward to implement though.
The second option is to simply always add headers and footers to your listviews, which have backgrounds that are selectors with rounded corners on top (and bottom). You can specify styles for these as well if you really want to.
Sorry for this last comment, but I had to say it. Please don't try to make your app look like an iPhone app :)

how to change android spinner popupBackground

I was trying to change the android spinner popup window background by setting the android:popupBackground, but it didn't have any effect. Is there any way I can change it?
<Spinner
android:id="#+id/eventNameSpinner"
android:layout_width="160dp"
android:layout_height="30dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="6dp"
android:background="#drawable/btn_name"
android:paddingBottom="2dp"
android:paddingTop="2dp"
android:popupBackground="#drawable/bkg">
I presume you are trying to change the "outer" backgroud of a Spinner's popup, not the background of Spinner "pupup items". Also, I presume that by popup, you mean a Spinner's dialog mode, where a floating dialog window appears on top of your activity, as opposed to the new dropdown mode.
Strategy
For a clean, sustainable approach which behaves well across multiple Android platforms and reduces the need for redundancy in the App, I believe it is essential to understand what the official docs don't tell us. So follow me on a short journey.
The tricky part about Spinners is that an Adapter is used to connect them to data. While it is relatively easy to identify the hooks for changing the appearance of android.R.layout.simple_spinner_item and its friends, those only determine the style of the Spinner's currently displayed item as well as each single popup item. But it is the Adapter which is responsible for creating the ListView and other widgets which frame that ListView.
This complexity is probably the reason why Android has introduced some attributes which can be specified on-the-fly "for" the Spinner although they are then applied to the Spnner's children, such as android:popupBackground. This is not necessarily a clean approach, rather a limited set of convenience functions. Regarding popupBackground, btw, this was introduced in API level 1, but Spinners respect it only in spinnerMode=dropdown, which was introduced in API level 11. That's the reason why you'll never be notified if you use it wrongly.
Older Android Versions (such as 2.2)
ListView
Knowing that the Adapter creates a ListView, it's not a bad idea to change the ListView appearance in one's theme, so there's one single place for the design change and the styling straightforward, like so:
<style name="MyTheme" parent="#android:style/[reference to original theme]" >
<item name="android:listViewStyle">#style/myListView</item>
[...]
</style>
<style name="myListView" parent="#android:style/Widget.ListView">
[check Android's Widget.ListView to understand what you can change here]
</style>
AlertDialog
Unfortunately, there's more work ahead. Because android:prompt can be used to create a headline for the popup, the popup really consists of more than just the ListView.
Android uses an AlertDialog
Recent Android Versions (such as 4.2)
Now that the AlertDialogs are styled, we still have to address the fact that more recent versions of Android don't use AlertDialogs for Spinner dialogs any more. That doesn't hurt, because for those, the AlertDialog style shouldd be kept anyways. It just means we need to style the new popup as well.
To do so, create version specific theme XML files to pull the additional styles into your customized theme, and provide version specific style XML files.
Feel like trying it yourself, starting here?
android:popupBackground is only valid when using android:spinnerMode = "dropdown" , thats probably why it wasnt any effect in your code. You need to tell that spinner which mode its in with some XML.
android:spinnerMode = "dropdown"
Links
http://developer.android.com/reference/android/widget/Spinner.html#attr_android:popupBackground
http://developer.android.com/reference/android/widget/Spinner.html

Categories

Resources