androidx PreferenceDialogFragmentCompat 和 DialogPreference的配合应用

本文介绍了在升级到androidx后,如何使用PreferenceDialogFragmentCompat和DialogPreference配合实现数据持久化和UI绑定。文章详细阐述了两者的使用流程,包括点击Preference触发显示Dialog、事件传递、数据绑定以及关键方法的重写,特别是如何设置Dialog的属性。作者还分享了解决Dialog positiveButton启用问题的方法,并鼓励读者深入学习这一系列内容。

最近在升级替换使用 androidx DialogPreference 的时候,发现androidx 对DialogPreference进行了解耦,之前的数据持久化和Ui的绑定在DialogPreference中就能实现,而现在需要通过PreferenceDialogFragmentCompat和DialogPreference配合才能使用。

说实话对preference的应用在做这个升级之前我并没有遇见过,所以,只能摸索着去探索。

1.首先分析下这两个类要怎么使用

参考文档:https://medium.com/@JakobUlbrich/building-a-settings-screen-for-android-part-3-ae9793fd31ec

首先看一张图:

这张图是主要的架构图:(以一个设置页面为例子)

1.PreferenceDialogFragmentCompat:点击DialogPrecence的时候展示的 Dialog

2.Preference:显示在设置页面上的item,同时也是数据持久化的容器

3.PreferenceFragmentCompat:展示设置的主要Fragment

4.PreferenceManager:sharePreference(Preference)和PreferenceFragmentCompat的数据管理器,两个类共享这个Manager

主要的流程:

1.preference 点击--> 调用onclick方法

 protected void onClick() {
        getPreferenceManager().showDialog(this);
    }

这时候会通过PrefereceManager去调用 showDialog方法

   */
    public void showDialog(Preference preference) {
        if (mOnDisplayPreferenceDialogListener != null) {
            mOnDisplayPreferenceDialogListener.onDisplayPreferenceDialog(preference);
        }
    }

2.传递事件到Fragment:

通过listener传递出去,在这边我们很容易猜到 PreferenceFragmentCompat 实现了 DisplayPreferenceDialogListener

到时候事件通过监听传递到PreferenceFragmentCompat

3.PreferenceFragmentCompat 实现 display方法

   public void onStart() {
        super.onStart();
        mPreferenceManager.setOnPreferenceTreeClickListener(this);
        mPreferenceManager.setOnDisplayPreferenceDialogListener(this);
    }

 

    @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 (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
            return;
        }

        final DialogFragment f;
        if (preference instanceof EditTextPreference) {
            f = EditTextPreferenceDialogFragmentCompat.newInstance(preference.getKey());
        } else if (preference instanceof ListPreference) {
            f = ListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
        } else if (preference instanceof AbstractMultiSelectListPreference) {
            f = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
        } else {
            throw new IllegalArgumentException("Tried to display dialog for unknown " +
                    "preference type. Did you forget to override onDisplayPreferenceDialog()?");
        }
        f.setTargetFragment(this, 0);
        f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
    }

默认的实现方法中 会根据preference的不同来使用显示不同的Dialog 也就是说dialogPrefece和DialogFragmentCompact都是配套使用的

我们在重写display方法的时候页应该根据不同的prefence去显示不同的dialog

 

问题:Preference和Fragment之间是怎么怎么关联起来的?

1.看源码:可以看出来,FragmentManager会在inflate xml的时候,attach到Preference,这个就不多说了

 

然后接下来是Prefence和PreferenceDialogFragment之间的联系

我们先从getPeference这个方法入手:

   public DialogPreference getPreference() {
        if (mPreference == null) {
            final String key = getArguments().getString(ARG_KEY);
            final DialogPreference.TargetFragment fragment =
                    (DialogPreference.TargetFragment) getTargetFragment();
            mPreference = (DialogPreference) fragment.findPreference(key);
        }
        return mPreference;
    }

这边的流程是:首先根据key找到一个TargeFragment,然后根据这个通过TargeFragment去找到一个preference

从这边我们很容易猜到 TargetFragment其实就是PreferenceFragmentCompact。也就说 PreferenceDialogFragmentCompact和PreferenceFragment是绑定在一起的,跟人觉得这样设计就是为了把之前的数据绑定,转化成UI的绑定。

从这段代码我们也可以看到setTarget 和设置key是必不可少的一步操作,不然,就无法将各个元素关联起来(之前在这边踩过坑)。而这边的key和关键,其实就是绑定的preference的key

  @SuppressWarnings("ReferenceEquality")
    public void setTargetFragment(@Nullable Fragment fragment, int requestCode) {
        // Don't allow a caller to set a target fragment in another FragmentManager,
        // but there's a snag: people do set target fragments before fragments get added.
        // We'll have the FragmentManager check that for validity when we move
        // the fragments to a valid state.
        final FragmentManager mine = getFragmentManager();
        final FragmentManager theirs = fragment != null ? fragment.getFragmentManager() : null;
        if (mine != null && theirs != null && mine != theirs) {
            throw new IllegalArgumentException("Fragment " + fragment
                    + " must share the same FragmentManager to be set as a target fragment");
        }

        // Don't let someone create a cycle.
        for (Fragment check = fragment; check != null; check = check.getTargetFragment()) {
            if (check == this) {
                throw new IllegalArgumentException("Setting " + fragment + " as the target of "
                        + this + " would create a target cycle");
            }
        }
        mTarget = fragment;
        mTargetRequestCode = requestCode;
    }

那现在整个流程就都连起来了。接下去看下:PreferenceDialogFragmentCompact的创建过程,有哪几个关键的操作

    @Override
    public @NonNull
    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;
    }

从createDialog入手,流程大概是:1.创建一个 Dialog -->2.oncreateDialogView 创建一个ContentView ->3.onBindViewHolder对数据进行绑定->onPrepareDialogBuilder 对dialog的参数进行进一步的设置--> 设置是否显示键盘-->然后返回Dialog

所以我们所做的操作就是在子类重写几个方法:

1.onCreateDialogView中设置contView的样式

2.onBindViewHolder 去通过Preference的数据进行数据的绑定

3. onPrepareDialogBuilder去进一步设置Dialog,这样就完成了Dialog的创建

问题:如果想设置Dialog positiveButton的Enable属性,要怎么设置,Builder并没有这样的方法。

解决办法:

子类重写oncreateDialog

获取super已经创建好的dialog,然后对dialog的参数进一步设置,如设置showListener等等,紧接着再返回

 

具体的例子:上面的参考文档有写

<?xml version="1.0" encoding="utf-8"?>
<TimePicker
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/edit"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="@dimen/alert_def_padding"
    android:paddingBottom="@dimen/alert_def_padding"
    android:theme="@style/AppAlertDialogContent" />
import android.support.v7.preference.DialogPreference;
public class TimePreference extends DialogPreference {
    ...
}
private int mTime;
private int mDialogLayoutResId = R.layout.pref_dialog_time;
public TimePreference(Context context) {
    this(context, null);
}
public TimePreference(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}
public TimePreference(Context context, AttributeSet attrs,
        int defStyleAttr) {
    this(context, attrs, defStyleAttr, defStyleAttr);
}
public TimePreference(Context context, AttributeSet attrs,
        int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    // Do custom stuff here
    // ...
    // read attributes etc.
}

public int getTime() {
    return mTime;
}
public void setTime(int time) {
    mTime = time;
    // Save to Shared Preferences
    persistInt(time);
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    // Default value from attribute. Fallback value is set to 0.
    return a.getInt(index, 0);
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue,
        Object defaultValue) {
    // Read the value. Use the default value if it is not possible.
    setTime(restorePersistedValue ?
        getPersistedInt(mTime) : (int) defaultValue);
}
public static TimePreferenceDialogFragmentCompat newInstance(
        String key) {
    final TimePreferenceDialogFragmentCompat
            fragment = new TimePreferenceDialogFragmentCompat();
    final Bundle b = new Bundle(1);
    b.putString(ARG_KEY, key);
    fragment.setArguments(b);

    return fragment;
}
@Override
protected void onBindDialogView(View view) {
    super.onBindDialogView(view);

    mTimePicker = (TimePicker) view.findViewById(R.id.edit);

    // Exception when there is no TimePicker
    if (mTimePicker == null) {
        throw new IllegalStateException("Dialog view must contain" +
                " a TimePicker with id 'edit'");
    }

    // Get the time from the related Preference
    Integer minutesAfterMidnight = null;
    DialogPreference preference = getPreference();
    if (preference instanceof TimePreference) {
        minutesAfterMidnight =
                ((TimePreference) preference).getTime();
    }

    // Set the time to the TimePicker
    if (minutesAfterMidnight != null) {
        int hours = minutesAfterMidnight / 60;
        int minutes = minutesAfterMidnight % 60;
        boolean is24hour = DateFormat.is24HourFormat(getContext());

        mTimePicker.setIs24HourView(is24hour);
        mTimePicker.setCurrentHour(hours);
        mTimePicker.setCurrentMinute(minutes);
    }
}
@Override
public void onDialogClosed(boolean positiveResult) {
    if (positiveResult) {
        // generate value to save
        int hours = mTimePicker.getCurrentHour();
        int minutes = mTimePicker.getCurrentMinute();
        int minutesAfterMidnight = (hours * 60) + minutes;

        // Get the related Preference and save the value
        DialogPreference preference = getPreference();
        if (preference instanceof TimePreference) {
            TimePreference timePreference =
                    ((TimePreference) preference);
            // This allows the client to ignore the user value.
            if (timePreference.callChangeListener(
                    minutesAfterMidnight)) {
                // Save the value
                timePreference.setTime(minutesAfterMidnight);
            }
        }
    }
}

 

@Override
public void onDisplayPreferenceDialog(Preference preference) {
    // Try if the preference is one of our custom Preferences
    DialogFragment dialogFragment = null;
    if (preference instanceof TimePreference) {
        // Create a new instance of TimePreferenceDialogFragment with the key of the related
        // Preference
        dialogFragment = TimePreferenceDialogFragmentCompat
                .newInstance(preference.getKey());
    }

    // If it was one of our cutom Preferences, show its dialog
    if (dialogFragment != null) {
        dialogFragment.setTargetFragment(this, 0);
        dialogFragment.show(this.getFragmentManager(),
                "android.support.v7.preference" +
                ".PreferenceFragment.DIALOG");
    }
    // Could not be handled here. Try with the super method.
    else {
        super.onDisplayPreferenceDialog(preference);
    }
}

今天的的源码分析就在这里了,其实这一个系列的使用以前也没怎么接触过,这次也是边学习边写,接下去用空再继续深入学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值