Android: Creating Custom Preference

Custom Preference Android Kotlin

Try to set your layout resource in initialization of class, instead of onBindViewHolder. Also you should create the layout using normal widget elements (not Preference).

CustomPreference.kt

import android.content.Context
import android.support.v7.preference.Preference
import android.support.v7.preference.PreferenceViewHolder
import android.util.AttributeSet
import kotlinx.android.synthetic.main.custom_preference_layout.view.*

class CustomPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet,
defStyleAttr: Int = 0
) : Preference(context, attrs, defStyleAttr) {

init {
widgetLayoutResource = R.layout.custom_preference_layout
}

override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
with(holder.itemView) {
// do the view initialization here...

textView.text = "Another Text"
}
}

}

res/layout/custom_preference_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="This is a custom preference" />

</FrameLayout>

preference_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<com.example.CustomPreference
app:key="custom_preference_key"
app:title="This is a custom preference" />

</android.support.v7.preference.PreferenceScreen>

Android Set Custom Preference Layout

Obviously, if you are hard-coding the color then you can just do it in your XML:

android:background="@android:color/red"

If you want to do it in code then unfortunately it's trickier than it might seem. You can't just set the color of the preference view in onCreate() because the preference views are stored in a list and are created and recycled dynamically as you scroll the list.

You need to set the background color when the view is being created. To do that you'll need to implement a custom preference class and override getView():

public class CustomColorPreference extends Preference
{
int backgroundColor = Color.BLACK;

public CustomColorPreference(Context context) {
super(context);
}

public CustomColorPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}

public void setCustomBackgroundColor(int color)
{
backgroundColor = color;
}

@Override
public View getView(View convertView, ViewGroup parent)
{
View v = super.getView(convertView, parent);

// v.setBackgroundColor(backgroundColor); // set background color of whole view
ImageView ivNameTextColor = (ImageView)v.findViewById(R.id.ivNameTextColor);
ivNameTextColor.setBackgroundColor(backgroundColor);

return v;
}
}

Change your XML to use the CustomColorPreference class:

<com.example.yourapp.CustomColorPreference
android:key="pref_name_color_picker"
android:title="Colour"
android:summary="Colour of the name"
android:defaultValue="#FFFFFF"
android:layout="@layout/custom_name_setting_layout" />

Then in your onCreate you can get the CustomColorPreference and set the color on it using the public method setCustomBackgroundColor():

CustomColorPreference picker = (CustomColorPreference)findPreference("pref_name_color_picker");
picker.setCustomBackgroundColor(Color.RED);

How to change the custom preference layout's imageview programmatically?

You can create a custom Preference which extends from CheckBoxPreference and use it just like the CheckBoxPreference:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<your.package.name.CustomCheckBoxPreference
android:layout="@layout/custom_checkbox_preference"
android:key="custom_checkbox_pref"
android:title="CustomCheckBoxPreferenceTitle"
android:defaultValue="false"/>
</PreferenceScreen>

Please note that according to the documentation for the android:layout atttribute one has to use specific resource id's for the layout's root ViewGroup as well as the TextViews for title and summary. This will ensure that the customized Preference behaves just like any stock Preference.

By overriding onBindViewHolder(PreferenceViewHolder) you can "find" the ImageView and assign it to a corresponding field ivSourceSelector. And by overriding setChecked() you can swap the drawables when the checked state of the Preference changes.

Having said that, here's the code for CustomCheckBoxPreference:

public class CustomCheckBoxPreference extends CheckBoxPreference {

private ImageView ivSourceSelector;

public CustomCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public CustomCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

public CustomCheckBoxPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CustomCheckBoxPreference(Context context) {
super(context);
}

@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
if(ivSourceSelector != null) {
ivSourceSelector.setImageResource(getResourceId(checked));
}
}

@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
ivSourceSelector = holder.itemView.findViewById(R.id.iv_source_selector);
if(ivSourceSelector != null){
ivSourceSelector.setImageResource(getResourceId(isChecked()));
}
}

private int getResourceId(boolean checked) {
return checked ? R.drawable.custom_checkbox_checked : R.drawable.custom_checkbox_unchecked;
}
}

Creating a custom layout for preferences

You can always create a custom preference layout item and use it in the PreferenceActivity. For example, I did this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">

<TextView android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />

<TextView android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignLeft="@android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="2" />

<ImageView android:id="@+id/ImageView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/go"
android:layout_alignParentRight="true" />

</RelativeLayout>

<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:orientation="vertical" />

</LinearLayout>

It has some waste, but basically creates 2 lines (heading, subtitle) with an image on the right. You could replace the ImageView with a Button and you'd be all set. You then set the layout for the individual preference in your xml/prefs.xml file like so:

<Preference
android:title="@string/label_pref_version"
android:key="@string/pref_version"
android:layout="@layout/pref" />

After that just fire findViewById in your Activity code and attach a listener to the button. Might have to do some more work if you have multiple buttons in the Activity, but shouldn't be unreasonable.

You can find the source code here : https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/layout/preference.xml

How do I create custom preferences using android.support.v7.preference library?


Important note:
Currently (v23.0.1 of the v7 library) there are still a lot of theme-issues with the 'PreferenceThemeOverlay'(see this issue). On Lollipop for example, you end up with Holo-styled category headers.

After some frustrating hours, I finally succeeded to create a custom v7 Preference. Creating your own Preference appears to be harder than you might think is needed. So make sure to take some time.

At first you might be wondering why you will find both a DialogPreference and a PreferenceDialogFragmentCompat for each preference type. As it turns out, the first one is the actual preference, the second is the DialogFragment where the preference would be displayed in. Sadly, you are required to subclass both of them.

Don't worry, you won't need to change any piece of code. You only need to relocate some methods:

  • All preference-editing methods (like setTitle() or persist*()) can be found in the DialogPreference class.
  • All dialog (-editing) methods (onBindDialogView(View) & onDialogClosed(boolean)) have been moved to PreferenceDialogFragmentCompat.

You might want your existing class to extend the first one, that way you don't have to change to much I think. Autocomplete should help you find missing methods.

When you have completed the above steps, it is time to bind these two classes together. In your xml file, you will refer to the preference-part. However, Android doesn't know yet which Fragment it must inflate when your custom preference needs to be. Therefore, you need to override onDisplayPreferenceDialog(Preference):

@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment fragment;
if (preference instanceof LocationChooserDialog) {
fragment = LocationChooserFragmentCompat.newInstance(preference);
fragment.setTargetFragment(this, 0);
fragment.show(getFragmentManager(),
"android.support.v7.preference.PreferenceFragment.DIALOG");
} else super.onDisplayPreferenceDialog(preference);
}

and also your DialogFragment needs to handle the 'key':

public static YourPreferenceDialogFragmentCompat newInstance(Preference preference) {
YourPreferenceDialogFragmentCompat fragment = new YourPreferenceDialogFragmentCompat();
Bundle bundle = new Bundle(1);
bundle.putString("key", preference.getKey());
fragment.setArguments(bundle);
return fragment;
}

That should do the trick. If you encounter problems, try taking a look at existing subclasses and see how Android solved it (in Android Studio: type a class' name and press Ctrl+b to see the decompiled class). Hope it helps.



Related Topics



Leave a reply



Submit