androidpreferencescreen

Preference Screen Custom Layout only works with second click


I have a preference screen using a 'custom layout':

android:layout="@layout/currencycodes"

This issue is it fails to bring up the layout on the first attempt. As you see from the animated gif below, I have to retreat and try again a second time for the layout to come up. Now why is that?

enter image description here

preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:key="application_preferences">
    <PreferenceCategory>
        <PreferenceScreen
                android:key="url_settings"
                android:title="@string/url_settings"
                android:summary="@string/summary_url_settings">
        </PreferenceScreen>

        <PreferenceScreen
                android:key="currency_codes"
                android:title="@string/left_handed"
                android:summary="@string/summary_left_handed">
            <Preference
                    android:key="currency_exchanges"
                    android:layout="@layout/currencycodes"/>
        </PreferenceScreen>
        <EditTextPreference
                android:key="url_exchange_rate"
                android:title="@string/url_exchange_rate_settings"
                android:summary="@string/summary_url_exchange_rate"
                android:enabled = "false"/>
    </PreferenceCategory>
</PreferenceScreen>

What follows is the custom layout used. It contains a GridView.

currencycodes.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true"
        android:focusableInTouchMode="true"
        tools:context=".MainActivity">

    <GridView
            android:layout_width="328dp"
            android:layout_height="479dp"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toTopOf="parent" android:layout_marginStart="16dp"
            app:layout_constraintLeft_toLeftOf="parent" android:layout_marginEnd="16dp"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="16dp"
            android:id="@+id/grdExchanges" android:scrollbars="horizontal|vertical"
            android:smoothScrollbar="true" tools:textAlignment="center"
            android:verticalScrollbarPosition="defaultPosition"/>
</android.support.constraint.ConstraintLayout>

Solution

  • I came up with a workaround. I noted when inflating a layout in LayoutInflater.java, a static hashmap is used to collect view objects:

    private static final HashMap<String, Constructor<? extends View>> sConstructorMap
    

    What's happening is that the first click calls the LayoutInflater, but that first click only adds the gridview view in the static hashmap. You'll see that, with the first click, the command, sConstructorMap.get(name), returns only null:

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
    
        Constructor<? extends View> constructor = sConstructorMap.get(name);
    
        Class<? extends View> clazz = null;
    
            try {
    
                if (constructor == null) {
    

    In the LayoutInflater, the variable, mPrivateFactory, is in fact your Main Activity. It is there my gridview would be initialized in mPrivateFactory.onCreateView(). However, with the first click, mPrivateFactory.onCreateView() returns null because the static hashmap doesn't have the gridview's view....yet. It's further below in the command line, onCreateView(parent, name, attrs); where it is finally added:

        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
    
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs); <-- ADD TO STATIC HASHMAP
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
    

    So until I came up with a more elegant solution, I merely call the method, onCreateView(parent), twice the first time that particular preference is clicked on. The first call adds the gridview to the static hashmap, the second call then instantiates the layout view (again) but, this time, initializes the gridview in my own Activity's own onCreateView method, mPrivateFactory.onCreateView():

    protected View onCreateView(ViewGroup parent) {
    
    View layout = super.onCreateView(parent);
    
    if(firstCall){
    
        firstCall = false;
    
        layout = super.onCreateView(parent);
    }
    
    return layout;
    }