Alternatives to Preferencefragment with Android-Support-V4

Alternatives to PreferenceFragment with android-support-v4

UPDATE - 6/11/2015

Support-v7 library now includes PreferenceFragmentCompat. So it will be a better idea to use it.


Add the following project as a library project to your application.

https://github.com/kolavar/android-support-v4-preferencefragment

You can keep everything including your fragment transaction as it is. When importing the PreferenceFragment class, make sure the correct import header is user.

import android.support.v4.preference.PreferenceFragment;

instead of

import android.preference.PreferenceFragment;

Preference Fragment with android.support.v4.app.Fragment

By my knowledge PreferenceFragment is not supported in the android.support.v4 library.

You can however use PreferenceFragmentCompat from the support-v7 library.

If it really has to work with the support-v4 library, I would recommend adding the following project as a library project to your application as suggested by this old thread.

https://github.com/kolavar/android-support-v4-preferencefragment

PreferenceFragment alternative for the Android compatibility API?

See the latest revision of the v7 support library, which introduced a PreferenceFragmentCompat.

Building Compatibility PreferenceFragment on Android

Look at Fr4gg0r's implementation of PreferenceFragment using reflex at
http://forum.xda-developers.com/showthread.php?t=1363906

How to use a PreferenceFragment in a FragmentPagerAdapter (android.support.v4)?

You can use the android.support.v7.preference.PreferenceFragmentCompat as an alternative to PreferenceFragment that works with the android.support.v4.app.FragmentPagerAdapter as discussed in this Google+ post

It is part of the v7 Preference Support Library.

PreferenceFragmentCompat requires preferenceTheme to be set

The sample project can be found here

The bugfix is available as a gradle dependency

Download

Now one can use the library pretty easy. Here are quickest way to do so, but you should check out the README for more info.

1. Update your module's gradle file:

compile 'com.takisoft.fix:preference-v7:27.0.0.0'

2. Use the appropriate class as your fragment's base

You can use either PreferenceFragmentCompat or PreferenceFragmentCompatDividers.

(Watch out for the appropriate package name when importing PreferenceFragmentCompat!)

3. Use the appropriate theme

Set your containing Activity's theme to a variant of @style/PreferenceFixTheme, like NoActionBar, Light, etc.

For more info and usage tips, go to the project's page.


P.S. In this section you could find the detailed solution that led to creation of the library, but I decided to remove it because it might be confusing. If you're curious about the steps, you can still find them in the revision history of this answer.

Was PreferenceFragment intentionally excluded from the compatibility package?

Discovering that PreferenceActivity contains deprecated methods (although these are used in the accompanying sample code)

The deprecated methods are deprecated as of Android 3.0. They are perfectly fine on all versions of Android, but the direction is to use PreferenceFragment on Android 3.0 and higher.

Can anyone tell me whether this was intentional?

My guess is it's a question of engineering time, but that's just a guess.

If so, can I easily target a range of devices (i.e. < 3.0 and >=3.0) or will I have to jump through hoops?

I consider it to be done "easily". Have two separate PreferenceActivity implementations, one using preference headers and PreferenceFragments, the other using the original approach. Choose the right one at the point you need to (e.g., when the user clicks on the options menu item). Here is a sample project demonstrating this. Or, have a single PreferenceActivity that handles both cases, as in this sample project.

If it wasn't intentionally excluded, can we expect a new release of the compatibility package?

You will find out when the rest of us find out, which is to say, if and when it ships.

Or is there another workaround that is safe to use?

See above.

Android ; PreferenceFragment language is not the same as the app

The problem you have is exactly what you mentioned; "my app will use phone locale". Since you haven't specifically set another Locale, it will use the default one. To fix this you can add the following code in onCreate in SettingsActivity, before starting the fragment:

Locale locale = new Locale("en_US");
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
getResources().updateConfiguration(config, getResources().getDisplayMetrics());

Using the en_US locale should be foolproof, since it must always be available. ("locale data is not necessarily available for any of the locales pre-defined as constants in this class except for en_US, which is the only locale Java guarantees is always available" source: Android API).

No ActionBar in PreferenceActivity after upgrade to Support Library v21

Please find the GitHub Repo: Here


Very Similar to your own code but added xml to allow for set title:

Continuing to use PreferenceActivity:

settings_toolbar.xml :

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:navigationContentDescription="@string/abc_action_bar_up_description"
android:background="?attr/colorPrimary"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/action_settings"
/>

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);

LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
bar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}

}

Result :

example



UPDATE (Gingerbread Compatibility) :

As pointed out here, Gingerbread Devices are returning NullPointerException on this line:

LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();

FIX:

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Toolbar bar;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
} else {
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
ListView content = (ListView) root.getChildAt(0);

root.removeAllViews();

bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);


int height;
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}else{
height = bar.getHeight();
}

content.setPadding(0, height, 0, 0);

root.addView(content);
root.addView(bar);
}

bar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}

Any issues with the above let me know!


UPDATE 2: TINTING WORKAROUND

As pointed out in many dev notes PreferenceActivity does not support tinting of elements, however by utilising a few internal classes you CAN achieve this. That is until these classes are removed. (Works using appCompat support-v7 v21.0.3).

Add the following imports:

import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintSpinner;

Then override the onCreateView method:

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// Allow super to try and create a view first
final View result = super.onCreateView(name, context, attrs);
if (result != null) {
return result;
}

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
return new TintEditText(this, attrs);
case "Spinner":
return new TintSpinner(this, attrs);
case "CheckBox":
return new TintCheckBox(this, attrs);
case "RadioButton":
return new TintRadioButton(this, attrs);
case "CheckedTextView":
return new TintCheckedTextView(this, attrs);
}
}

return null;
}

Result:

example 2



AppCompat 22.1

AppCompat 22.1 introduced new tinted elements, meaning that there is no longer a need to utilise the internal classes to achieve the same effect as the last update. Instead follow this (still overriding onCreateView):

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// Allow super to try and create a view first
final View result = super.onCreateView(name, context, attrs);
if (result != null) {
return result;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
// standard framework versions
switch (name) {
case "EditText":
return new AppCompatEditText(this, attrs);
case "Spinner":
return new AppCompatSpinner(this, attrs);
case "CheckBox":
return new AppCompatCheckBox(this, attrs);
case "RadioButton":
return new AppCompatRadioButton(this, attrs);
case "CheckedTextView":
return new AppCompatCheckedTextView(this, attrs);
}
}

return null;
}

NESTED PREFERENCE SCREENS

A lot of people are experiencing issues with including the Toolbar in nested <PreferenceScreen />s however, I have found a solution!! - After a lot of trial and error!

Add the following to your SettingsActivity:

@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
super.onPreferenceTreeClick(preferenceScreen, preference);

// If the user has clicked on a preference screen, set up the screen
if (preference instanceof PreferenceScreen) {
setUpNestedScreen((PreferenceScreen) preference);
}

return false;
}

public void setUpNestedScreen(PreferenceScreen preferenceScreen) {
final Dialog dialog = preferenceScreen.getDialog();

Toolbar bar;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
LinearLayout root = (LinearLayout) dialog.findViewById(android.R.id.list).getParent();
bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
root.addView(bar, 0); // insert at top
} else {
ViewGroup root = (ViewGroup) dialog.findViewById(android.R.id.content);
ListView content = (ListView) root.getChildAt(0);

root.removeAllViews();

bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);

int height;
TypedValue tv = new TypedValue();
if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
}else{
height = bar.getHeight();
}

content.setPadding(0, height, 0, 0);

root.addView(content);
root.addView(bar);
}

bar.setTitle(preferenceScreen.getTitle());

bar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
}

The reason that PreferenceScreen's are such a pain is because they are based as a wrapper dialog, so we need to capture the dialog layout to add the toolbar to it.



Toolbar Shadow

By design importing the Toolbar does not allow for elevation and shadowing in pre-v21 devices, so if you would like to have elevation on your Toolbar you need to wrap it in a AppBarLayout:

`settings_toolbar.xml :

<android.support.design.widget.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<android.support.v7.widget.Toolbar
.../>

</android.support.design.widget.AppBarLayout>

Not forgetting to add the add the Design Support library as a dependency in build.gradle file:

compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'


Android 6.0

I have investigated the reported overlapping issue and I cannot reproduce the issue.

The full code in use as above produces the following:

Sample Image

If I am missing something please let me know via this repo and I will investigate.



Related Topics



Leave a reply



Submit