简体   繁体   中英

MultiSelectListPreference checkboxes not checked when not initially visible in list (API 23)

Given a SettingsActivity with just a MultiSelectListPreference with its entries, values, and default values in array resources, some checkboxes are not drawn as checked even though Android knows they are supposed to be checked. When an unchecked item is clicked, the item remains unchecked (because Android thinks it is unchecking a checked item).

Here's a video of this happening

Project available here: https://github.com/ImmersibleElf/MSLPBug

It seems to work fine in APIs 21 and 22, but not in 23. Is this maybe a bug in the recycling of views? Or what might be the cause?

SettingsActivity.java

package com.immersibleelf.mslpbug;

import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.support.v7.app.AppCompatActivity;

public class SettingsActivity extends AppCompatActivity {

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

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    public static class SettingsFragment extends PreferenceFragment {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // Load the settings from an XML resource
            addPreferencesFromResource(R.xml.settings);
        }
    }
}

settings.xml

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">

        <MultiSelectListPreference
            android:key="mslp_key"
            android:title="MultiSelectListPreference"
            android:entries="@array/mslp_entries"
            android:entryValues="@array/mslp_entry_values"
            android:defaultValue="@array/mslp_default_value"
            android:persistent="true"
            />
</PreferenceScreen>

arrays.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string-array name="mslp_entries">
        <item>Entry 01</item>
        <item>Entry 02</item>
        <item>Entry 03</item>
        <item>Entry 04</item>
        <item>Entry 05</item>
        <item>Entry 06</item>
        <item>Entry 07</item>
        <item>Entry 08</item>
        <item>Entry 09</item>
        <item>Entry 10</item>
        <item>Entry 11</item>
        <item>Entry 12</item>
        <item>Entry 13</item>
        <item>Entry 14</item>
        <item>Entry 15</item>
    </string-array>

    <string-array name="mslp_entry_values">
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
        <item>7</item>
        <item>8</item>
        <item>9</item>
        <item>10</item>
        <item>11</item>
        <item>12</item>
        <item>13</item>
        <item>14</item>
        <item>15</item>
    </string-array>

    <string-array name="mslp_default_value">
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
        <item>7</item>
        <item>8</item>
        <item>9</item>
        <item>10</item>
        <item>11</item>
        <item>12</item>
        <item>13</item>
        <item>14</item>
        <item>15</item>
    </string-array>

</resources>

A workaround is to create a sub class of MultiSelectListPreference and overwrite the method showDialog like this:

@Override
protected void showDialog(Bundle state) {
    super.showDialog(state);

    AlertDialog dialog = (AlertDialog)getDialog();
    if (dialog == null)
        return;

    if (Build.VERSION.SDK_INT >= 23) {
        ListView listView = dialog.getListView();

        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                int size = view.getChildCount();
                for (int i=0; i<size; i++) {
                    View v = view.getChildAt(i);
                    if (v instanceof CheckedTextView)
                        ((CheckedTextView)v).refreshDrawableState();
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                int size = view.getChildCount();
                for (int i=0; i<size; i++) {
                    View v = view.getChildAt(i);
                    if (v instanceof CheckedTextView)
                        ((CheckedTextView)v).refreshDrawableState();
                }
            }
        });
    }
}

Excellent. This code works as mdiener mentioned:

import android.app.AlertDialog;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.preference.MultiSelectListPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.CheckedTextView;
import android.widget.ListView;

/**
 * Android bug: https://code.google.com/p/android/issues/detail?id=205487
 */
public class MultiSelectWrapper extends MultiSelectListPreference {

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

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

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

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

@Override
protected void showDialog(Bundle state) {
    super.showDialog(state);

    AlertDialog dialog = (AlertDialog)getDialog();
    if (dialog == null)
        return;

    if (Build.VERSION.SDK_INT >= 23) {
        ListView listView = dialog.getListView();

        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                int size = view.getChildCount();
                for (int i=0; i<size; i++) {
                    View v = view.getChildAt(i);
                    if (v instanceof CheckedTextView)
                        ((CheckedTextView)v).refreshDrawableState();
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                int size = view.getChildCount();
                for (int i=0; i<size; i++) {
                    View v = view.getChildAt(i);
                    if (v instanceof CheckedTextView)
                        ((CheckedTextView)v).refreshDrawableState();
                }
            }
        });
    }
}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM