mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-09-10 20:23:18 +02:00
Added Local fork of the preferences controller
Because it didnt work correctly and I dont wanna upload the changes to git
This commit is contained in:
parent
7f7acfc55a
commit
2681ff3c9a
@ -112,7 +112,6 @@ dependencies {
|
||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||
|
||||
// Android support library
|
||||
implementation 'com.android.support:preference-v7:28.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
@ -219,7 +218,7 @@ dependencies {
|
||||
implementation ("com.bluelinelabs:conductor-support:2.1.5") {
|
||||
exclude group: "com.android.support"
|
||||
}
|
||||
implementation 'com.github.inorichi:conductor-support-preference:78e2344'
|
||||
implementation project(":j2k-preference")
|
||||
|
||||
// RxBindings
|
||||
final rxbindings_version = '1.0.1'
|
||||
|
29
j2k-preference/build.gradle
Normal file
29
j2k-preference/build.gradle
Normal file
@ -0,0 +1,29 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
encoding = 'UTF-8'
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
compileOnly 'com.bluelinelabs:conductor:2.1.5'
|
||||
}
|
25
j2k-preference/proguard-rules.pro
vendored
Normal file
25
j2k-preference/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:\Users\len\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
3
j2k-preference/src/main/AndroidManifest.xml
Normal file
3
j2k-preference/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<manifest package="androidx.preference.conductor">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.preference;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
|
||||
public class EditTextPreferenceDialogController extends PreferenceDialogController {
|
||||
|
||||
private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogController.text";
|
||||
|
||||
private EditText mEditText;
|
||||
|
||||
private CharSequence mText;
|
||||
|
||||
public static EditTextPreferenceDialogController newInstance(String key) {
|
||||
EditTextPreferenceDialogController
|
||||
controller = new EditTextPreferenceDialogController();
|
||||
controller.getArgs().putString(ARG_KEY, key);
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState == null) {
|
||||
mText = getEditTextPreference().getText();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putCharSequence(SAVE_STATE_TEXT, mText);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
mEditText = view.findViewById(android.R.id.edit);
|
||||
mEditText.requestFocus();
|
||||
|
||||
if (mEditText == null) {
|
||||
throw new IllegalStateException("Dialog view must contain an EditText with id" +
|
||||
" @android:id/edit");
|
||||
}
|
||||
|
||||
mEditText.setText(mText);
|
||||
// Place cursor at the end
|
||||
mEditText.setSelection(mEditText.getText().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyView(@NonNull View view) {
|
||||
super.onDestroyView(view);
|
||||
mEditText = null;
|
||||
}
|
||||
|
||||
private EditTextPreference getEditTextPreference() {
|
||||
return (EditTextPreference) getPreference();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@Override
|
||||
protected boolean needInputMethod() {
|
||||
// We want the input method to show, if possible, when dialog is displayed
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
if (positiveResult) {
|
||||
String value = mEditText.getText().toString();
|
||||
if (getEditTextPreference().callChangeListener(value)) {
|
||||
getEditTextPreference().setText(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.preference;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class ListPreferenceDialogController extends PreferenceDialogController {
|
||||
|
||||
private static final String SAVE_STATE_INDEX = "ListPreferenceDialogController.index";
|
||||
private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogController.entries";
|
||||
private static final String SAVE_STATE_ENTRY_VALUES =
|
||||
"ListPreferenceDialogController.entryValues";
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
private int mClickedDialogEntryIndex;
|
||||
private CharSequence[] mEntries;
|
||||
private CharSequence[] mEntryValues;
|
||||
|
||||
public static ListPreferenceDialogController newInstance(String key) {
|
||||
ListPreferenceDialogController controller =
|
||||
new ListPreferenceDialogController();
|
||||
controller.getArgs().putString(ARG_KEY, key);
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState == null) {
|
||||
final ListPreference preference = getListPreference();
|
||||
|
||||
if (preference.getEntries() == null || preference.getEntryValues() == null) {
|
||||
throw new IllegalStateException(
|
||||
"ListPreference requires an entries array and an entryValues array.");
|
||||
}
|
||||
|
||||
mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
|
||||
mEntries = preference.getEntries();
|
||||
mEntryValues = preference.getEntryValues();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
|
||||
outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
|
||||
outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
|
||||
mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
|
||||
mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
|
||||
}
|
||||
|
||||
private ListPreference getListPreference() {
|
||||
return (ListPreference) getPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
|
||||
builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mClickedDialogEntryIndex = which;
|
||||
|
||||
/*
|
||||
* Clicking on an item simulates the positive button
|
||||
* click, and dismisses the dialog.
|
||||
*/
|
||||
ListPreferenceDialogController.this.onClick(dialog,
|
||||
DialogInterface.BUTTON_POSITIVE);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* The typical interaction for list-based dialogs is to have
|
||||
* click-on-an-item dismiss the dialog instead of the user having to
|
||||
* press 'Ok'.
|
||||
*/
|
||||
builder.setPositiveButton(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
final ListPreference preference = getListPreference();
|
||||
if (positiveResult && mClickedDialogEntryIndex >= 0) {
|
||||
String value = mEntryValues[mClickedDialogEntryIndex].toString();
|
||||
if (preference.callChangeListener(value)) {
|
||||
preference.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.preference;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("RestrictedApi")
|
||||
public class MultiSelectListPreferenceDialogController extends PreferenceDialogController {
|
||||
|
||||
private static final String SAVE_STATE_VALUES =
|
||||
"MultiSelectListPreferenceDialogController.values";
|
||||
private static final String SAVE_STATE_CHANGED =
|
||||
"MultiSelectListPreferenceDialogController.changed";
|
||||
private static final String SAVE_STATE_ENTRIES =
|
||||
"MultiSelectListPreferenceDialogController.entries";
|
||||
private static final String SAVE_STATE_ENTRY_VALUES =
|
||||
"MultiSelectListPreferenceDialogController.entryValues";
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
Set<String> mNewValues = new HashSet<>();
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
boolean mPreferenceChanged;
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
CharSequence[] mEntries;
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
CharSequence[] mEntryValues;
|
||||
|
||||
public static MultiSelectListPreferenceDialogController newInstance(String key) {
|
||||
MultiSelectListPreferenceDialogController controller =
|
||||
new MultiSelectListPreferenceDialogController();
|
||||
controller.getArgs().putString(ARG_KEY, key);
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
final MultiSelectListPreference preference = getListPreference();
|
||||
|
||||
if (preference.getEntries() == null || preference.getEntryValues() == null) {
|
||||
throw new IllegalStateException(
|
||||
"MultiSelectListPreference requires an entries array and " +
|
||||
"an entryValues array.");
|
||||
}
|
||||
|
||||
mNewValues.clear();
|
||||
mNewValues.addAll(preference.getValues());
|
||||
mPreferenceChanged = false;
|
||||
mEntries = preference.getEntries();
|
||||
mEntryValues = preference.getEntryValues();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putStringArrayList(SAVE_STATE_VALUES, new ArrayList<>(mNewValues));
|
||||
outState.putBoolean(SAVE_STATE_CHANGED, mPreferenceChanged);
|
||||
outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
|
||||
outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
mNewValues.clear();
|
||||
mNewValues.addAll(savedInstanceState.getStringArrayList(SAVE_STATE_VALUES));
|
||||
mPreferenceChanged = savedInstanceState.getBoolean(SAVE_STATE_CHANGED, false);
|
||||
mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
|
||||
mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
|
||||
}
|
||||
|
||||
private MultiSelectListPreference getListPreference() {
|
||||
return (MultiSelectListPreference) getPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
|
||||
final int entryCount = mEntryValues.length;
|
||||
final boolean[] checkedItems = new boolean[entryCount];
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
checkedItems[i] = mNewValues.contains(mEntryValues[i].toString());
|
||||
}
|
||||
builder.setMultiChoiceItems(mEntries, checkedItems,
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
mPreferenceChanged |= mNewValues.add(
|
||||
mEntryValues[which].toString());
|
||||
} else {
|
||||
mPreferenceChanged |= mNewValues.remove(
|
||||
mEntryValues[which].toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
final MultiSelectListPreference preference = getListPreference();
|
||||
if (positiveResult && mPreferenceChanged) {
|
||||
final Set<String> values = mNewValues;
|
||||
if (preference.callChangeListener(values)) {
|
||||
preference.setValues(values);
|
||||
}
|
||||
}
|
||||
mPreferenceChanged = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,813 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.XmlRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
|
||||
/**
|
||||
* Shows a hierarchy of {@link Preference} objects as
|
||||
* lists. These preferences will
|
||||
* automatically save to {@link android.content.SharedPreferences} as the user interacts with
|
||||
* them. To retrieve an instance of {@link android.content.SharedPreferences} that the
|
||||
* preference hierarchy in this fragment will use, call
|
||||
* {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
|
||||
* with a context in the same package as this fragment.
|
||||
* <p>
|
||||
* Furthermore, the preferences shown will follow the visual style of system
|
||||
* preferences. It is easy to create a hierarchy of preferences (that can be
|
||||
* shown on multiple screens) via XML. For these reasons, it is recommended to
|
||||
* use this fragment (as a superclass) to deal with preferences in applications.
|
||||
* <p>
|
||||
* A {@link PreferenceScreen} object should be at the top of the preference
|
||||
* hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
|
||||
* denote a screen break--that is the preferences contained within subsequent
|
||||
* {@link PreferenceScreen} should be shown on another screen. The preference
|
||||
* framework handles this by calling {@link #onNavigateToScreen(PreferenceScreen)}.
|
||||
* <p>
|
||||
* The preference hierarchy can be formed in multiple ways:
|
||||
* <li> From an XML file specifying the hierarchy
|
||||
* <li> From different {@link android.app.Activity Activities} that each specify its own
|
||||
* preferences in an XML file via {@link android.app.Activity} meta-data
|
||||
* <li> From an object hierarchy rooted with {@link PreferenceScreen}
|
||||
* <p>
|
||||
* To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
|
||||
* root element should be a {@link PreferenceScreen}. Subsequent elements can point
|
||||
* to actual {@link Preference} subclasses. As mentioned above, subsequent
|
||||
* {@link PreferenceScreen} in the hierarchy will result in the screen break.
|
||||
* <p>
|
||||
* To specify an object hierarchy rooted with {@link PreferenceScreen}, use
|
||||
* {@link #setPreferenceScreen(PreferenceScreen)}.
|
||||
* <p>
|
||||
* As a convenience, this fragment implements a click listener for any
|
||||
* preference in the current hierarchy, see
|
||||
* {@link #onPreferenceTreeClick(Preference)}.
|
||||
*
|
||||
* <div class="special reference">
|
||||
* <h3>Developer Guides</h3>
|
||||
* <p>For information about using {@code PreferenceFragment},
|
||||
* read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
|
||||
* guide.</p>
|
||||
* </div>
|
||||
*
|
||||
* <a name="SampleCode"></a>
|
||||
* <h3>Sample Code</h3>
|
||||
*
|
||||
* <p>The following sample code shows a simple preference fragment that is
|
||||
* populated from a resource. The resource it loads is:</p>
|
||||
*
|
||||
* {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
|
||||
*
|
||||
* <p>The fragment implementation itself simply populates the preferences
|
||||
* when created. Note that the preferences framework takes care of loading
|
||||
* the current values out of the app preferences and writing them when changed:</p>
|
||||
*
|
||||
* {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCompat.java
|
||||
* support_fragment_compat}
|
||||
*
|
||||
* @see Preference
|
||||
* @see PreferenceScreen
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "HandlerLeak", "JavaDoc", "RestrictedApi"})
|
||||
public abstract class PreferenceController extends RestoreViewOnCreateController implements
|
||||
PreferenceManager.OnDisplayPreferenceDialogListener,
|
||||
DialogPreference.TargetFragment {
|
||||
|
||||
/**
|
||||
* Fragment argument used to specify the tag of the desired root
|
||||
* {@link androidx.preference.PreferenceScreen} object.
|
||||
*/
|
||||
public static final String ARG_PREFERENCE_ROOT =
|
||||
"androidx.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
|
||||
|
||||
private static final String PREFERENCES_TAG = "android:preferences";
|
||||
|
||||
private static final String DIALOG_FRAGMENT_TAG =
|
||||
"androidx.preference.PreferenceFragment.DIALOG";
|
||||
|
||||
private PreferenceManager mPreferenceManager;
|
||||
RecyclerView mList;
|
||||
private boolean mHavePrefs;
|
||||
private boolean mInitDone;
|
||||
|
||||
private Context mStyledContext;
|
||||
|
||||
private int mLayoutResId = R.layout.preference_list_fragment;
|
||||
|
||||
private DividerDecoration mDividerDecoration = null;
|
||||
|
||||
private static final int MSG_BIND_PREFERENCES = 1;
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
|
||||
case MSG_BIND_PREFERENCES:
|
||||
bindPreferences();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final private Runnable mRequestFocus = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mList.focusableViewAvailable(mList);
|
||||
}
|
||||
};
|
||||
|
||||
private Runnable mSelectPreferenceRunnable;
|
||||
|
||||
/**
|
||||
* Interface that PreferenceFragment's containing activity should
|
||||
* implement to be able to process preference items that wish to
|
||||
* switch to a specified fragment.
|
||||
*/
|
||||
public interface OnPreferenceStartFragmentCallback {
|
||||
/**
|
||||
* Called when the user has clicked on a Preference that has
|
||||
* a fragment class name associated with it. The implementation
|
||||
* should instantiate and switch to an instance of the given
|
||||
* fragment.
|
||||
* @param caller The fragment requesting navigation.
|
||||
* @param pref The preference requesting the fragment.
|
||||
* @return true if the fragment creation has been handled
|
||||
*/
|
||||
boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that PreferenceFragment's containing activity should
|
||||
* implement to be able to process preference items that wish to
|
||||
* switch to a new screen of preferences.
|
||||
*/
|
||||
public interface OnPreferenceStartScreenCallback {
|
||||
/**
|
||||
* Called when the user has clicked on a PreferenceScreen item in order to navigate to a new
|
||||
* screen of preferences.
|
||||
* @param caller The fragment requesting navigation.
|
||||
* @param pref The preference screen to navigate to.
|
||||
* @return true if the screen navigation has been handled
|
||||
*/
|
||||
boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref);
|
||||
}
|
||||
|
||||
public interface OnPreferenceDisplayDialogCallback {
|
||||
|
||||
/**
|
||||
* @param caller The fragment containing the preference requesting the dialog.
|
||||
* @param pref The preference requesting the dialog.
|
||||
* @return true if the dialog creation has been handled.
|
||||
*/
|
||||
boolean onPreferenceDisplayDialog(PreferenceController caller, Preference pref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor for use when no arguments are needed.
|
||||
*/
|
||||
public PreferenceController() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that takes arguments that need to be retained across restarts.
|
||||
*
|
||||
* @param args Any arguments that need to be retained.
|
||||
*/
|
||||
public PreferenceController(@Nullable Bundle args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment.
|
||||
* Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either
|
||||
* directly or via helper methods such as {@link #addPreferencesFromResource(int)}.
|
||||
*
|
||||
* @param savedInstanceState If the fragment is being re-created from
|
||||
* a previous saved state, this is the state.
|
||||
* @param rootKey If non-null, this preference fragment should be rooted at the
|
||||
* {@link androidx.preference.PreferenceScreen} with this key.
|
||||
*/
|
||||
public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey);
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
mInitDone = false;
|
||||
mHavePrefs = false;
|
||||
|
||||
final TypedValue tv = new TypedValue();
|
||||
getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
|
||||
int theme = tv.resourceId;
|
||||
if (theme == 0) {
|
||||
// Fallback to default theme.
|
||||
theme = R.style.PreferenceThemeOverlay;
|
||||
}
|
||||
mStyledContext = new ContextThemeWrapper(getActivity(), theme);
|
||||
|
||||
mPreferenceManager = new PreferenceManager(mStyledContext);
|
||||
final String rootKey = getArgs().getString(ARG_PREFERENCE_ROOT);
|
||||
onCreatePreferences(savedInstanceState, rootKey);
|
||||
|
||||
TypedArray a = mStyledContext.obtainStyledAttributes(null,
|
||||
R.styleable.PreferenceFragmentCompat,
|
||||
R.attr.preferenceFragmentCompatStyle,
|
||||
0);
|
||||
|
||||
mLayoutResId = a.getResourceId(R.styleable.PreferenceFragmentCompat_android_layout,
|
||||
mLayoutResId);
|
||||
|
||||
mDividerDecoration = new DividerDecoration();
|
||||
final Drawable divider = a.getDrawable(
|
||||
R.styleable.PreferenceFragmentCompat_android_divider);
|
||||
final int dividerHeight = a.getDimensionPixelSize(
|
||||
R.styleable.PreferenceFragmentCompat_android_dividerHeight, -1);
|
||||
final boolean allowDividerAfterLastItem = a.getBoolean(
|
||||
R.styleable.PreferenceFragmentCompat_allowDividerAfterLastItem, true);
|
||||
|
||||
a.recycle();
|
||||
|
||||
final LayoutInflater themedInflater = inflater.cloneInContext(mStyledContext);
|
||||
|
||||
final View view = themedInflater.inflate(mLayoutResId, container, false);
|
||||
|
||||
final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
|
||||
if (!(rawListContainer instanceof ViewGroup)) {
|
||||
throw new RuntimeException("Content has view with id attribute "
|
||||
+ "'android.R.id.list_container' that is not a ViewGroup class");
|
||||
}
|
||||
|
||||
final ViewGroup listContainer = (ViewGroup) rawListContainer;
|
||||
|
||||
final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer,
|
||||
savedInstanceState);
|
||||
if (listView == null) {
|
||||
throw new RuntimeException("Could not create RecyclerView");
|
||||
}
|
||||
|
||||
mList = listView;
|
||||
|
||||
listView.addItemDecoration(mDividerDecoration);
|
||||
setDivider(divider);
|
||||
if (dividerHeight != -1) {
|
||||
setDividerHeight(dividerHeight);
|
||||
}
|
||||
mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem);
|
||||
|
||||
// If mList isn't present in the view hierarchy, add it. mList is automatically inflated
|
||||
// on an Auto device so don't need to add it.
|
||||
if (mList.getParent() == null) {
|
||||
listContainer.addView(mList);
|
||||
}
|
||||
mHandler.post(mRequestFocus);
|
||||
|
||||
onViewCreated(view, savedInstanceState);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the drawable that will be drawn between each item in the list.
|
||||
* <p>
|
||||
* <strong>Note:</strong> If the drawable does not have an intrinsic
|
||||
* height, you should also call {@link #setDividerHeight(int)}.
|
||||
*
|
||||
* @param divider the drawable to use
|
||||
* @attr ref R.styleable#PreferenceFragmentCompat_android_divider
|
||||
*/
|
||||
public void setDivider(Drawable divider) {
|
||||
if (mDividerDecoration != null) {
|
||||
mDividerDecoration.setDivider(divider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of the divider that will be drawn between each item in the list. Calling
|
||||
* this will override the intrinsic height as set by {@link #setDivider(Drawable)}
|
||||
*
|
||||
* @param height The new height of the divider in pixels.
|
||||
* @attr ref R.styleable#PreferenceFragmentCompat_android_dividerHeight
|
||||
*/
|
||||
public void setDividerHeight(int height) {
|
||||
if (mDividerDecoration != null) {
|
||||
mDividerDecoration.setDividerHeight(height);
|
||||
}
|
||||
}
|
||||
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
|
||||
if (container != null) {
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
preferenceScreen.restoreHierarchyState(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mHavePrefs) {
|
||||
bindPreferences();
|
||||
if (mSelectPreferenceRunnable != null) {
|
||||
mSelectPreferenceRunnable.run();
|
||||
mSelectPreferenceRunnable = null;
|
||||
}
|
||||
}
|
||||
|
||||
mInitDone = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
mPreferenceManager.setOnDisplayPreferenceDialogListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach(@NonNull View view) {
|
||||
super.onDetach(view);
|
||||
mPreferenceManager.setOnDisplayPreferenceDialogListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(@NonNull View view) {
|
||||
mHandler.removeCallbacks(mRequestFocus);
|
||||
mHandler.removeMessages(MSG_BIND_PREFERENCES);
|
||||
if (mHavePrefs) {
|
||||
unbindPreferences();
|
||||
}
|
||||
mList = null;
|
||||
mPreferenceManager = null;
|
||||
mStyledContext = null;
|
||||
mDividerDecoration = null;
|
||||
|
||||
super.onDestroyView(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
|
||||
super.onSaveViewState(view, outState);
|
||||
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
Bundle container = new Bundle();
|
||||
preferenceScreen.saveHierarchyState(container);
|
||||
outState.putBundle(PREFERENCES_TAG, container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PreferenceManager} used by this fragment.
|
||||
* @return The {@link PreferenceManager}.
|
||||
*/
|
||||
public PreferenceManager getPreferenceManager() {
|
||||
return mPreferenceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root of the preference hierarchy that this fragment is showing.
|
||||
*
|
||||
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
|
||||
*/
|
||||
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
|
||||
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
|
||||
onUnbindPreferences();
|
||||
mHavePrefs = true;
|
||||
if (mInitDone) {
|
||||
postBindPreferences();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root of the preference hierarchy that this fragment is showing.
|
||||
*
|
||||
* @return The {@link PreferenceScreen} that is the root of the preference
|
||||
* hierarchy.
|
||||
*/
|
||||
public PreferenceScreen getPreferenceScreen() {
|
||||
return mPreferenceManager.getPreferenceScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the given XML resource and adds the preference hierarchy to the current
|
||||
* preference hierarchy.
|
||||
*
|
||||
* @param preferencesResId The XML resource ID to inflate.
|
||||
*/
|
||||
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
|
||||
requirePreferenceManager();
|
||||
|
||||
setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext,
|
||||
preferencesResId, getPreferenceScreen()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the given XML resource and replaces the current preference hierarchy (if any) with
|
||||
* the preference hierarchy rooted at {@code key}.
|
||||
*
|
||||
* @param preferencesResId The XML resource ID to inflate.
|
||||
* @param key The preference key of the {@link androidx.preference.PreferenceScreen}
|
||||
* to use as the root of the preference hierarchy, or null to use the root
|
||||
* {@link androidx.preference.PreferenceScreen}.
|
||||
*/
|
||||
public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) {
|
||||
requirePreferenceManager();
|
||||
|
||||
final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext,
|
||||
preferencesResId, null);
|
||||
|
||||
final Preference root;
|
||||
if (key != null) {
|
||||
root = xmlRoot.findPreference(key);
|
||||
if (!(root instanceof PreferenceScreen)) {
|
||||
throw new IllegalArgumentException("Preference object with key " + key
|
||||
+ " is not a PreferenceScreen");
|
||||
}
|
||||
} else {
|
||||
root = xmlRoot;
|
||||
}
|
||||
|
||||
setPreferenceScreen((PreferenceScreen) root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a {@link Preference} based on its key.
|
||||
*
|
||||
* @param key The key of the preference to retrieve.
|
||||
* @return The {@link Preference} with the key, or null.
|
||||
* @see androidx.preference.PreferenceGroup#findPreference(CharSequence)
|
||||
*/
|
||||
@Override
|
||||
public Preference findPreference(CharSequence key) {
|
||||
if (mPreferenceManager == null) {
|
||||
return null;
|
||||
}
|
||||
return mPreferenceManager.findPreference(key);
|
||||
}
|
||||
|
||||
private void requirePreferenceManager() {
|
||||
if (mPreferenceManager == null) {
|
||||
throw new RuntimeException("This should be called after super.onCreate.");
|
||||
}
|
||||
}
|
||||
|
||||
private void postBindPreferences() {
|
||||
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
|
||||
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
|
||||
}
|
||||
|
||||
private void bindPreferences() {
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
getListView().setAdapter(onCreateAdapter(preferenceScreen));
|
||||
preferenceScreen.onAttached();
|
||||
}
|
||||
onBindPreferences();
|
||||
}
|
||||
|
||||
private void unbindPreferences() {
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
preferenceScreen.onDetached();
|
||||
}
|
||||
onUnbindPreferences();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
protected void onBindPreferences() {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
protected void onUnbindPreferences() {
|
||||
}
|
||||
|
||||
public final RecyclerView getListView() {
|
||||
return mList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link RecyclerView} used to display the preferences.
|
||||
* Subclasses may override this to return a customized
|
||||
* {@link RecyclerView}.
|
||||
* @param inflater The LayoutInflater object that can be used to inflate the
|
||||
* {@link RecyclerView}.
|
||||
* @param parent The parent {@link android.view.View} that the RecyclerView will be attached to.
|
||||
* This method should not add the view itself, but this can be used to generate
|
||||
* the LayoutParams of the view.
|
||||
* @param savedInstanceState If non-null, this view is being re-constructed from a previous
|
||||
* saved state as given here
|
||||
* @return A new RecyclerView object to be placed into the view hierarchy
|
||||
*/
|
||||
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
|
||||
Bundle savedInstanceState) {
|
||||
// If device detected is Auto, use Auto's custom layout that contains a custom ViewGroup
|
||||
// wrapping a RecyclerView
|
||||
if (mStyledContext.getPackageManager().hasSystemFeature(PackageManager
|
||||
.FEATURE_AUTOMOTIVE)) {
|
||||
RecyclerView recyclerView = parent.findViewById(R.id.recycler_view);
|
||||
if (recyclerView != null) {
|
||||
return recyclerView;
|
||||
}
|
||||
}
|
||||
RecyclerView recyclerView = (RecyclerView) inflater
|
||||
.inflate(R.layout.preference_recyclerview, parent, false);
|
||||
|
||||
recyclerView.setLayoutManager(onCreateLayoutManager());
|
||||
recyclerView.setAccessibilityDelegateCompat(
|
||||
new PreferenceRecyclerViewAccessibilityDelegate(recyclerView));
|
||||
|
||||
return recyclerView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from {@link #onCreateRecyclerView} to create the
|
||||
* {@link RecyclerView.LayoutManager} for the created
|
||||
* {@link RecyclerView}.
|
||||
* @return A new {@link RecyclerView.LayoutManager} instance.
|
||||
*/
|
||||
public RecyclerView.LayoutManager onCreateLayoutManager() {
|
||||
return new LinearLayoutManager(getActivity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the root adapter.
|
||||
*
|
||||
* @param preferenceScreen Preference screen object to create the adapter for.
|
||||
* @return An adapter that contains the preferences contained in this {@link PreferenceScreen}.
|
||||
*/
|
||||
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
||||
return new PreferenceGroupAdapter(preferenceScreen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a preference in the tree requests to display a dialog. Subclasses should
|
||||
* override this method to display custom dialogs or to handle dialogs for custom preference
|
||||
* classes.
|
||||
*
|
||||
* @param preference The Preference object requesting the dialog.
|
||||
*/
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
boolean handled = false;
|
||||
if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) {
|
||||
handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment())
|
||||
.onPreferenceDisplayDialog(this, preference);
|
||||
}
|
||||
if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) {
|
||||
handled = ((OnPreferenceDisplayDialogCallback) getActivity())
|
||||
.onPreferenceDisplayDialog(this, preference);
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if dialog is already showing
|
||||
if (getRouter().getControllerWithTag(DIALOG_FRAGMENT_TAG) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PreferenceDialogController f;
|
||||
if (preference instanceof EditTextPreference) {
|
||||
f = EditTextPreferenceDialogController.newInstance(preference.getKey());
|
||||
} else if (preference instanceof ListPreference) {
|
||||
f = ListPreferenceDialogController.newInstance(preference.getKey());
|
||||
} else if (preference instanceof MultiSelectListPreference) {
|
||||
f = MultiSelectListPreferenceDialogController.newInstance(preference.getKey());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Tried to display dialog for unknown " +
|
||||
"preference type. Did you forget to override onDisplayPreferenceDialog()?");
|
||||
}
|
||||
f.setTargetController(this);
|
||||
f.showDialog(getRouter(), DIALOG_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib.
|
||||
* @return Fragment to possibly use as a callback
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
public Fragment getCallbackFragment() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void scrollToPreference(final String key) {
|
||||
scrollToPreferenceInternal(null, key);
|
||||
}
|
||||
|
||||
public void scrollToPreference(final Preference preference) {
|
||||
scrollToPreferenceInternal(preference, null);
|
||||
}
|
||||
|
||||
private void scrollToPreferenceInternal(final Preference preference, final String key) {
|
||||
final Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final RecyclerView.Adapter adapter = mList.getAdapter();
|
||||
if (!(adapter instanceof
|
||||
PreferenceGroup.PreferencePositionCallback)) {
|
||||
if (adapter != null) {
|
||||
throw new IllegalStateException("Adapter must implement "
|
||||
+ "PreferencePositionCallback");
|
||||
} else {
|
||||
// Adapter was set to null, so don't scroll I guess?
|
||||
return;
|
||||
}
|
||||
}
|
||||
final int position;
|
||||
if (preference != null) {
|
||||
position = ((PreferenceGroup.PreferencePositionCallback) adapter)
|
||||
.getPreferenceAdapterPosition(preference);
|
||||
} else {
|
||||
position = ((PreferenceGroup.PreferencePositionCallback) adapter)
|
||||
.getPreferenceAdapterPosition(key);
|
||||
}
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
mList.scrollToPosition(position);
|
||||
} else {
|
||||
// Item not found, wait for an update and try again
|
||||
adapter.registerAdapterDataObserver(
|
||||
new ScrollToPreferenceObserver(adapter, mList, preference, key));
|
||||
}
|
||||
}
|
||||
};
|
||||
if (mList == null) {
|
||||
mSelectPreferenceRunnable = r;
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver {
|
||||
private final RecyclerView.Adapter mAdapter;
|
||||
private final RecyclerView mList;
|
||||
private final Preference mPreference;
|
||||
private final String mKey;
|
||||
|
||||
public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list,
|
||||
Preference preference, String key) {
|
||||
mAdapter = adapter;
|
||||
mList = list;
|
||||
mPreference = preference;
|
||||
mKey = key;
|
||||
}
|
||||
|
||||
private void scrollToPreference() {
|
||||
mAdapter.unregisterAdapterDataObserver(this);
|
||||
final int position;
|
||||
if (mPreference != null) {
|
||||
position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
|
||||
.getPreferenceAdapterPosition(mPreference);
|
||||
} else {
|
||||
position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
|
||||
.getPreferenceAdapterPosition(mKey);
|
||||
}
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
mList.scrollToPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
scrollToPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
scrollToPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
scrollToPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
scrollToPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
scrollToPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
scrollToPreference();
|
||||
}
|
||||
}
|
||||
|
||||
private class DividerDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private Drawable mDivider;
|
||||
private int mDividerHeight;
|
||||
private boolean mAllowDividerAfterLastItem = true;
|
||||
|
||||
DividerDecoration() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
if (mDivider == null) {
|
||||
return;
|
||||
}
|
||||
final int childCount = parent.getChildCount();
|
||||
final int width = parent.getWidth();
|
||||
for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
|
||||
final View view = parent.getChildAt(childViewIndex);
|
||||
if (shouldDrawDividerBelow(view, parent)) {
|
||||
int top = (int) view.getY() + view.getHeight();
|
||||
mDivider.setBounds(0, top, width, top + mDividerHeight);
|
||||
mDivider.draw(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state) {
|
||||
if (shouldDrawDividerBelow(view, parent)) {
|
||||
outRect.bottom = mDividerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
|
||||
final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
|
||||
final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
|
||||
&& ((PreferenceViewHolder) holder).isDividerAllowedBelow();
|
||||
if (!dividerAllowedBelow) {
|
||||
return false;
|
||||
}
|
||||
boolean nextAllowed = mAllowDividerAfterLastItem;
|
||||
int index = parent.indexOfChild(view);
|
||||
if (index < parent.getChildCount() - 1) {
|
||||
final View nextView = parent.getChildAt(index + 1);
|
||||
final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
|
||||
nextAllowed = nextHolder instanceof PreferenceViewHolder
|
||||
&& ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
|
||||
}
|
||||
return nextAllowed;
|
||||
}
|
||||
|
||||
public void setDivider(Drawable divider) {
|
||||
if (divider != null) {
|
||||
mDividerHeight = divider.getIntrinsicHeight();
|
||||
} else {
|
||||
mDividerHeight = 0;
|
||||
}
|
||||
mDivider = divider;
|
||||
mList.invalidateItemDecorations();
|
||||
}
|
||||
|
||||
public void setDividerHeight(int dividerHeight) {
|
||||
mDividerHeight = dividerHeight;
|
||||
mList.invalidateItemDecorations();
|
||||
}
|
||||
|
||||
public void setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem) {
|
||||
mAllowDividerAfterLastItem = allowDividerAfterLastItem;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.preference;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.*;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
|
||||
/**
|
||||
* Abstract base class which presents a dialog associated with a
|
||||
* {@link androidx.preference.DialogPreference}. Since the preference object may
|
||||
* not be available during fragment re-creation, the necessary information for displaying the dialog
|
||||
* is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
|
||||
* instance state. Custom subclasses should also follow this pattern.
|
||||
*/
|
||||
public abstract class PreferenceDialogController extends RestoreViewOnCreateController implements
|
||||
DialogInterface.OnClickListener {
|
||||
|
||||
protected static final String ARG_KEY = "key";
|
||||
|
||||
private static final String SAVE_DIALOG_STATE_TAG = "android:savedDialogState";
|
||||
private static final String SAVE_STATE_TITLE = "PreferenceDialogController.title";
|
||||
private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogController.positiveText";
|
||||
private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogController.negativeText";
|
||||
private static final String SAVE_STATE_MESSAGE = "PreferenceDialogController.message";
|
||||
private static final String SAVE_STATE_LAYOUT = "PreferenceDialogController.layout";
|
||||
private static final String SAVE_STATE_ICON = "PreferenceDialogController.icon";
|
||||
|
||||
private DialogPreference mPreference;
|
||||
|
||||
private CharSequence mDialogTitle;
|
||||
private CharSequence mPositiveButtonText;
|
||||
private CharSequence mNegativeButtonText;
|
||||
private CharSequence mDialogMessage;
|
||||
private @LayoutRes int mDialogLayoutRes;
|
||||
|
||||
private BitmapDrawable mDialogIcon;
|
||||
|
||||
/** Which button was clicked. */
|
||||
private int mWhichButtonClicked;
|
||||
|
||||
private Dialog dialog;
|
||||
private boolean dismissed;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
final protected View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@NonNull ViewGroup container,
|
||||
@Nullable Bundle savedViewState) {
|
||||
|
||||
onCreate(savedViewState);
|
||||
|
||||
dialog = onCreateDialog(savedViewState);
|
||||
//noinspection ConstantConditions
|
||||
dialog.setOwnerActivity(getActivity());
|
||||
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
PreferenceDialogController.this.dismissDialog();
|
||||
}
|
||||
});
|
||||
if (savedViewState != null) {
|
||||
Bundle dialogState = savedViewState.getBundle(SAVE_DIALOG_STATE_TAG);
|
||||
if (dialogState != null) {
|
||||
dialog.onRestoreInstanceState(dialogState);
|
||||
}
|
||||
}
|
||||
return new View(getActivity());//stub view
|
||||
}
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
final Controller rawController = getTargetController();
|
||||
if (!(rawController instanceof DialogPreference.TargetFragment)) {
|
||||
throw new IllegalStateException("Target controller must implement TargetFragment" +
|
||||
" interface");
|
||||
}
|
||||
|
||||
final DialogPreference.TargetFragment controller =
|
||||
(DialogPreference.TargetFragment) rawController;
|
||||
|
||||
final String key = getArgs().getString(ARG_KEY);
|
||||
if (savedInstanceState == null) {
|
||||
mPreference = (DialogPreference) controller.findPreference(key);
|
||||
mDialogTitle = mPreference.getDialogTitle();
|
||||
mPositiveButtonText = mPreference.getPositiveButtonText();
|
||||
mNegativeButtonText = mPreference.getNegativeButtonText();
|
||||
mDialogMessage = mPreference.getDialogMessage();
|
||||
mDialogLayoutRes = mPreference.getDialogLayoutResource();
|
||||
|
||||
final Drawable icon = mPreference.getDialogIcon();
|
||||
if (icon == null || icon instanceof BitmapDrawable) {
|
||||
mDialogIcon = (BitmapDrawable) icon;
|
||||
} else {
|
||||
final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
|
||||
icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
icon.draw(canvas);
|
||||
mDialogIcon = new BitmapDrawable(getResources(), bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
|
||||
outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
|
||||
outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
|
||||
outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
|
||||
outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
|
||||
if (mDialogIcon != null) {
|
||||
outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
|
||||
mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
|
||||
mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
|
||||
mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
|
||||
mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
|
||||
final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
|
||||
if (bitmap != null) {
|
||||
mDialogIcon = new BitmapDrawable(getResources(), bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Context context = getActivity();
|
||||
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(mDialogTitle)
|
||||
.setIcon(mDialogIcon)
|
||||
.setPositiveButton(mPositiveButtonText, this)
|
||||
.setNegativeButton(mNegativeButtonText, this);
|
||||
|
||||
View contentView = onCreateDialogView(context);
|
||||
if (contentView != null) {
|
||||
onBindDialogView(contentView);
|
||||
builder.setView(contentView);
|
||||
} else {
|
||||
builder.setMessage(mDialogMessage);
|
||||
}
|
||||
|
||||
onPrepareDialogBuilder(builder);
|
||||
|
||||
// Create the dialog
|
||||
final Dialog dialog = builder.create();
|
||||
if (needInputMethod()) {
|
||||
requestInputMethod(dialog);
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
|
||||
super.onSaveViewState(view, outState);
|
||||
Bundle dialogState = dialog.onSaveInstanceState();
|
||||
outState.putBundle(SAVE_DIALOG_STATE_TAG, dialogState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(@NonNull View view) {
|
||||
super.onDetach(view);
|
||||
dialog.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroyView(@NonNull View view) {
|
||||
super.onDestroyView(view);
|
||||
dialog.setOnDismissListener(null);
|
||||
dialog.dismiss();
|
||||
dialog = null;
|
||||
mPreference = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dialog, create a transaction and pushing the controller.
|
||||
*
|
||||
* @param router The router on which the transaction will be applied
|
||||
*/
|
||||
public void showDialog(@NonNull Router router) {
|
||||
showDialog(router, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dialog, create a transaction and pushing the controller.
|
||||
*
|
||||
* @param router The router on which the transaction will be applied
|
||||
* @param tag The tag for this controller
|
||||
*/
|
||||
public void showDialog(@NonNull Router router, @Nullable String tag) {
|
||||
dismissed = false;
|
||||
router.pushController(RouterTransaction.with(this)
|
||||
.pushChangeHandler(new SimpleSwapChangeHandler(false))
|
||||
.popChangeHandler(new SimpleSwapChangeHandler(false))
|
||||
.tag(tag));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the dialog and pop this controller
|
||||
*/
|
||||
public void dismissDialog() {
|
||||
if (dismissed) {
|
||||
return;
|
||||
}
|
||||
onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
|
||||
getRouter().popController(this);
|
||||
dismissed = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Dialog getDialog() {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
|
||||
* been called on the {@link PreferenceFragmentCompat} which launched this dialog.
|
||||
*
|
||||
* @return The {@link DialogPreference} associated with this
|
||||
* dialog.
|
||||
*/
|
||||
public DialogPreference getPreference() {
|
||||
if (mPreference == null) {
|
||||
final String key = getArgs().getString(ARG_KEY);
|
||||
final DialogPreference.TargetFragment controller =
|
||||
(DialogPreference.TargetFragment) getTargetController();
|
||||
mPreference = (DialogPreference) controller.findPreference(key);
|
||||
}
|
||||
return mPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the dialog builder to be shown when the preference is clicked.
|
||||
* Use this to set custom properties on the dialog.
|
||||
* <p>
|
||||
* Do not {@link AlertDialog.Builder#create()} or
|
||||
* {@link AlertDialog.Builder#show()}.
|
||||
*/
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the preference needs to display a soft input method when the dialog
|
||||
* is displayed. Default is false. Subclasses should override this method if they need
|
||||
* the soft input method brought up automatically.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
protected boolean needInputMethod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the required flags on the dialog window to enable input method window to show up.
|
||||
*/
|
||||
private void requestInputMethod(Dialog dialog) {
|
||||
Window window = dialog.getWindow();
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the content view for the dialog (if a custom content view is
|
||||
* required). By default, it inflates the dialog layout resource if it is
|
||||
* set.
|
||||
*
|
||||
* @return The content View for the dialog.
|
||||
* @see DialogPreference#setLayoutResource(int)
|
||||
*/
|
||||
protected View onCreateDialogView(Context context) {
|
||||
final int resId = mDialogLayoutRes;
|
||||
if (resId == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
return inflater.inflate(resId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds views in the content View of the dialog to data.
|
||||
* <p>
|
||||
* Make sure to call through to the superclass implementation.
|
||||
*
|
||||
* @param view The content View of the dialog, if it is custom.
|
||||
*/
|
||||
protected void onBindDialogView(View view) {
|
||||
View dialogMessageView = view.findViewById(android.R.id.message);
|
||||
|
||||
if (dialogMessageView != null) {
|
||||
final CharSequence message = mDialogMessage;
|
||||
int newVisibility = View.GONE;
|
||||
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
if (dialogMessageView instanceof TextView) {
|
||||
((TextView) dialogMessageView).setText(message);
|
||||
}
|
||||
|
||||
newVisibility = View.VISIBLE;
|
||||
}
|
||||
|
||||
if (dialogMessageView.getVisibility() != newVisibility) {
|
||||
dialogMessageView.setVisibility(newVisibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mWhichButtonClicked = which;
|
||||
}
|
||||
|
||||
public abstract void onDialogClosed(boolean positiveResult);
|
||||
}
|
@ -1 +1 @@
|
||||
include ':app'
|
||||
include ':app', ':j2k-preference'
|
||||
|
Loading…
Reference in New Issue
Block a user