Dynamically changing android preference's widgetlayout resource
Asked Answered
G

4

7

I created a preference entry inside preference screen, which looks like this:

<PreferenceScreen>
    <Preference
        android:title="title"
        android:key="key"
        android:widgetLayout="@layout/someview"/>
</PreferenceScreen>

Here I set a widgetlayout resource, which should be shown to the right of the preference item (like a checkbox for a checkbox preference). I can also set this resource in code of my PreferenceActivity.onCreate() like this:

Preference myPreference = findPreference("key");
myPreference.setWidgetLayoutResource(R.layout.someview);

Both approaches work fine, so I can see my resource to the right of the preference item. However, I cannot access this resource (someview) to alter its properties on runtime.

Neither manually setting resource id, inflating it from resource or findViewById seem to work - I have invalid resource/resource id not found exceptions. Seems like preference activity inflates the resource sometime later.

Has anybody run into the same problem? Any ideas about how to alter widgetlayout resource's properties on runtime?

Here is a similar question, but it was not answered

Griff answered 24/3, 2012 at 9:22 Comment(0)
A
13

I seem to have the same problem, and I tried like this:

1) make a custom Preference and override onBindView():

public class ProgressBarPreference extends Preference {

    private ProgressBar mProgressBar;

    // constructors

    @Override
    protected void onBindView(View view) {
        super.onBindView(view);
        mProgressBar = (ProgressBar) view.findViewById(R.id.progressBar);
    }

    public ProgressBar getProgressBar() {
        return mProgressBar;
    }

}

2) add this preference to the screen xml:

<package.ProgressBarPreference
    android:key="key"
    android:summary="summary"
    android:title="title"
    android:widgetLayout="@layout/preference_widget_progressbar" />

preference_widget_progressbar.xml

<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/progressBar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />

or call setWidgetLayoutResource().

3) get the ProgressBar when onPreferenceTreeClick():

if (preference instanceof ProgressBarPreference) {
    ProgressBar progressBar = ((ProgressBarPreference) preference).getProgressBar();
    // TODO something
}

However, over a year now!

Alleluia answered 20/4, 2013 at 16:43 Comment(0)
S
2

I have a partial workaround for joinAero's case. If the goal is to simply show or hide an indeterminate progress bar in the widget, the trick is to set and clear the widget layout resource in code.

1) Define the widget layout containing the ProgressBar. No need to assign an ID. preference_widget_progressbar.xml:

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

2) Don't specify the widget in settings.xml

<package.ProgressBarPreference
    android:key="key"
    android:summary="summary"
    android:title="title" />

3) When you want to show the progress bar, set the widget resource to preference_widget_progressbar. To clear it, set it to 0. The call to notifyChanged() is needed to update the screen.

public void showProgressBar(boolean isVisible) {
    // Show or hide the entire widget (which contains only a ProgressBar)
    setWidgetLayoutResource(isVisible ? R.layout.preference_widget_progressbar: 0);
    notifyChanged();    // Update screen
}

Getting a pointer to the actual ProgressBar is surprisingly hard, especially if the preference is in a library project. I had some success using getIdentifier() as per this post, but this approach actually hides the entire widget, whereas setting the visibility on the ProgressBar leaves a blank widget visible.

Seedy answered 22/11, 2013 at 16:1 Comment(0)
T
1

Here's a work around:

Create your widget layout setting the onClick attribute,

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/icon"
    android:layout_width="48dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:background="?android:attr/selectableItemBackground"
    *android:onClick="onMyKeyClicked"*
    android:scaleType="centerInside"
    android:src="@drawable/ic_action_info" />

Then set your widget layout on your Preference,

<ListPreference
    android:defaultValue="1"
    android:entries="@array/my_entries"
    android:entryValues="@array/my_values"
    android:key="my_key"
    android:title="@string/pref_my_key_title"
    android:widgetLayout="@layout/pref_layout_my_key" />

Then in your activity have a method like,

public void onMyKeyClicked(View v) {
    Toast.makeText(this, "My key widget clicked",
       Toast.LENGTH_SHORT).show();
}

So, for each preference with a widget you'd have a layout that has the onClick attribute set to a unique method in the Activity. What do you think?

Truism answered 6/7, 2015 at 21:29 Comment(0)
T
0

I found a work around for this without having to create custom preferences.

One benefit with this solution is that you can use the same widget resource with different implementations of listeners i.e. the 'setOnClickListener'.

The example below creates two EditTextPreferecnes, each pointing to the same widget layout. Then an addOnChildAttachStateChangeListener is used to set the widgets as desired. The listener is needed because the view bindings of the preferences in the preference Recycler view is one of the last things that happen or after onResume().

Also, make sure to use non zero Id's.

Strings:

<resources>
    <!-- Preference Titles -->
    <string name="prefs_header">Prefs With Widgets</string>

    <!-- Preferences -->
    <string name="pref_key_a">pref_key_a</string>
    <string name="pref_key_b">pref_key_b</string>
</resources>

Widget Layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <ImageButton
        android:id="@+id/imageButton"
        style="@android:style/Widget.Holo.ImageButton"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:contentDescription="@string/messages_header"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@android:drawable/stat_sys_download" />
</androidx.constraintlayout.widget.ConstraintLayout>

Preferecne Layout:

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
    <PreferenceCategory
        app:iconSpaceReserved="false"
        app:title="Prefs With Widgets">
        <EditTextPreference
            app:iconSpaceReserved="false"
            app:key="@string/pref_key_a"
            app:title="Test Button A"
            app:useSimpleSummaryProvider="true"
            app:widgetLayout="@layout/my_widget" />
        <EditTextPreference
            app:iconSpaceReserved="false"
            app:key="@string/pref_key_b"
            app:title="Test Button B"
            app:useSimpleSummaryProvider="true"
            app:widgetLayout="@layout/my_widget" />
    </PreferenceCategory>
</PreferenceScreen>

Kotlin:

class SettingsFragment : PreferenceFragmentCompat() {
    private lateinit var _onChildAttachListener : RecyclerView.OnChildAttachStateChangeListener
    private lateinit var _editTextA : EditTextPreference
    private lateinit var _editTextB : EditTextPreference

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

        setPreferencesFromResource(R.xml.root_preferences, rootKey)

        _editTextA = preferenceScreen.findPreference(getString(R.string.pref_key_a))!!
        _editTextB = preferenceScreen.findPreference(getString(R.string.pref_key_b))!!

        _editTextA.setViewId(PREF_A_ID)
        _editTextB.setViewId(PREF_B_ID)

        _onChildAttachListener = object : RecyclerView.OnChildAttachStateChangeListener {
            override fun onChildViewAttachedToWindow(view: View) {
                if(PREF_A_ID == view.id){
                    view.findViewById<ImageButton>(R.id.imageButton).setOnClickListener {
                        Toast.makeText(context, "Button A", Toast.LENGTH_SHORT).show()
                    }
                }
                if(PREF_B_ID == view.id){
                    view.findViewById<ImageButton>(R.id.imageButton).setOnClickListener {
                        Toast.makeText(context, "Button B", Toast.LENGTH_SHORT).show()
                    }
                }
            }
            override fun onChildViewDetachedFromWindow(view: View) {
                // Do nothing..
            }
        }
    }

    override fun onResume() {
        listView.addOnChildAttachStateChangeListener(_onChildAttachListener)
        super.onResume()
    }

    override fun onDestroy() {
        listView.removeOnChildAttachStateChangeListener(_onChildAttachListener)
        super.onDestroy()
    }

    companion object {
        // Note: Set ID's to anything but 0
        const val PREF_A_ID = 1
        const val PREF_B_ID = 2
    }
}
Tumbleweed answered 17/4 at 21:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.