Search code examples
androidandroid-preferences

How to allow clicking on disabled preferences (or mimic disabled preferences functionality)?


Background

Some app features require permissions. If a permission is not granted, we need the preference of it to be "semi" disabled (and not change if it's ticked or not), with a text telling that it cannot be used because there aren't enough granted permissions, so that it only partially looks disabled (the text will stay black, with the warning being red). It won't look like a normal disabled preference.

Like a normal disabled preference, though, when it's in this state, all preferences that depend on it will be disabled and un-clickable.

However, unlike a normal disabled preference, Upon clicking such a preference, it will go to a screen of granting the required permissions.

The problem

There are various ways to handle clicking on preferences, but none that I've found to handle clicking on a disabled preference.

Since various other preferences depend on such a preference (so that they get enabled/disabled based on whether it's enabled/disabled), I need to let it become disabled when needed, yet allow to click on it.

What I've tried

I tried various ways to handle clicking:

  • onPreferenceTreeClick inside the PreferenceFragment
  • setOnPreferenceClickListener on the preference itself
  • trying to override performClick of preference, but it's considered a bad thing because it's hidden
  • override onClick of preference.
  • in onCreateView or onBindView of the preference, use setOnClickListener on the view that's created.
  • wrap the view of onCreateView of the preference, and use setOnClickListener on the view or on the new view that wraps it.

All of those do not get called when the preference is disabled, and you try to click on the preference.

The questions

  1. Is it possible to disable a preference yet still handle clicks on it?

  2. Alternatively, is it possible extend the functionality of the dependencies of the preferences ? To make other preferences to be dependent on this preference, but not just based on whether it's enabled or not ? Meaning they are enabled only if this one is enabled AND it's not in this semi-enabled state?

    Currently, only CheckboxPreference is used on the app as the one that needs this new state, so it's easy to just make its checkbox look disabled, yet let the preference itself stay enabled (I just store the CheckBox view by using view.findViewById(android.R.id.checkbox) inside onCreateView method)


Solution

  • OK, I've found a workaround for this:

    class CheckboxPreferenceEx : android.preference.CheckBoxPreference {
        var alwaysAllowClickingEnabled: Boolean = false
        private val onPreferenceViewClickListener: View.OnClickListener
    
        constructor(context: Context) : super(context)
        constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
        constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
    
        init {
            onPreferenceViewClickListener = View.OnClickListener {
                if (onPreferenceChangeListener != null && !isEnabled)
                    if (onPreferenceChangeListener.onPreferenceChange(this@CheckboxPreferenceEx, !isChecked))
                        isChecked = !isChecked
                if (this@CheckboxPreferenceEx.isEnabled)
                    this@CheckboxPreferenceEx.onClick()
            }
        }
    
        override fun onCreateView(parent: ViewGroup): View {
            val view = super.onCreateView(parent)
            setPreferenceTitleTextViewToHaveMultipleLines(view)
            //mTitleTextView = (TextView) view.findViewById(android.R.id.title);
            //mSummaryTextView = (TextView) view.findViewById(android.R.id.summary);
            return view
        }
    
        override fun onBindView(view: View) {
            super.onBindView(view)
            view.setOnClickListener(onPreferenceViewClickListener)
            val dependency = dependency
            if (!TextUtils.isEmpty(dependency) && !isEnabled) {
                //if the reason this preference is disabled is because of dependency, act as normal
                val dependencyPreference = preferenceManager.findPreference(dependency)
                if (!dependencyPreference!!.isEnabled || dependencyPreference is TwoStatePreference && !dependencyPreference.isChecked)
                    return
            }
            if (alwaysAllowClickingEnabled) {
                //we chose to enable all except for the checkbox, which will still look disabled
                view.isEnabled = true
                setEnabledToViewAndItsDescendants(view, true)
            }
        }
    
        companion object {
    
            fun setPreferenceTitleTextViewToHaveMultipleLines(v: View) {
                if (v is TextView && v.getId() == android.R.id.title) {
                    v.setSingleLine(false)
                    return
                }
                if (v is ViewGroup) {
                    for (i in 0 until v.childCount) {
                        val child = v.getChildAt(i)
                        setPreferenceTitleTextViewToHaveMultipleLines(child)
                    }
                }
            }
    
            fun setEnabledToViewAndItsDescendants(v: View?, enabled: Boolean) {
                if (v == null)
                    return
                v.isEnabled = enabled
                if (v is ViewGroup) {
                    val viewGroups = ArrayList<ViewGroup>()
                    viewGroups.add(v)
                    while (!viewGroups.isEmpty()) {
                        val viewGroup = viewGroups.removeAt(0)
                        val childCount = viewGroup.childCount
                        for (i in 0 until childCount) {
                            val childView = viewGroup.getChildAt(i)
                            childView.isEnabled = enabled
                            if (childView is ViewGroup)
                                viewGroups.add(childView)
                        }
                    }
                }
            }
        }
    
    }
    

    Having such a preference like that, when I called setAlwaysAllowClickingEnabled(true) on it, will cause the preference to really be disabled, yet when you click on it, it will call the callback.

    In our case, we wanted to let the checkbox stay disabled, yet enable the title and summary TextViews, but you can do as you wish, depending on your requirements. Updated the code.