Установка Android в ночном режиме с помощью шаблона AppCompatPreferenceActivity отменяет нажатие на заголовки настроек

Моя цель: настройка ночного режима с помощью настроек и обновление пользовательского интерфейса в режиме реального времени.

Пока: Готово. НО, когда я возвращаюсь к экрану настроек заголовков, я не могу снова вернуться на экраны различных настроек.

Уточняю: на тот момент мои настройки были довольно простыми. Я следую предустановке, установленной Android Studio (3.1.4) для SettingsActivity, имея шаблон AppCompatPreferenceActivity. У меня один основной экран и два более глубоких.

На моем первом экране есть два варианта: «Общие» и «О программе». Выбрав «Общие», я загружаю GeneralPreferenceFragment с одним параметром Switch, параметром «Ночной режим». Если я устанавливаю его, он переключает тему в реальном времени, и когда я возвращаюсь, это также делается на экране моих первых настроек.

Проблема: когда я меняю тему, возвращаюсь к основному экрану настроек и пытаюсь вернуться к экранам «Общие» или «О программе», я больше не могу углубляться! Если я переключу предпочтение на четное число, чтобы перейти к исходной теме, я смогу посетить их, как будто ничего не произошло.

Класс SettingsActivity

class SettingsActivity : AppCompatPreferenceActivity() {

// To be used for live changes in night mode
private var mCurrentNightMode: Int = 0
private val TAG = "SettingsActivity"
private var mThemeId = 0

/**
 * Doing this hack to add my own actionbar on top since it wasn't there on its own
 */
override fun onPostCreate(savedInstanceState: Bundle?) {
    super.onPostCreate(savedInstanceState)
    Log.d(TAG, "onPostCreate")

    val root = findViewById<View>(android.R.id.list).parent.parent.parent as LinearLayout
    val bar = LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false) as Toolbar
    root.addView(bar, 0) // insert at top
    bar.setNavigationOnClickListener {finish()}
}

override fun setTheme(resid: Int) {
    super.setTheme(resid)
    mThemeId = resid
}

override fun onCreate(savedInstanceState: Bundle?) {
    if (delegate.applyDayNight() && (mThemeId != 0) ) {
        // If DayNight has been applied, we need to re-apply the theme for
        // the changes to take effect. On API 23+, we should bypass
        // setTheme(), which will no-op if the theme ID is identical to the
        // current theme ID.
        if (Build.VERSION.SDK_INT >= 23) {
            onApplyThemeResource(theme, mThemeId, false);
        } else {
            setTheme(mThemeId);
        }
    }
    super.onCreate(savedInstanceState)
    Log.d("SettingsActivity", "onCreate")
    setupActionBar()

    // Storing the current value so if we come back we'll check on postResume for any changes
    mCurrentNightMode = getCurrentNightMode();
}

// Comparing current and last known night mode value
private fun hasNightModeChanged(): Boolean {
    return mCurrentNightMode != getCurrentNightMode()
}

private fun getCurrentNightMode(): Int {
    return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
}

override fun onPostResume() {
    super.onPostResume()

    // Self-explanatory. When I load back into this screen, if there's a change in the theme, load it back!
    if(hasNightModeChanged()) {
        recreate()
    }
}

/**
 * Set up the [android.app.ActionBar], if the API is available.
 */
private fun setupActionBar() {
    supportActionBar?.setDisplayHomeAsUpEnabled(true)
}

/**
 * {@inheritDoc}
 */
override fun onIsMultiPane(): Boolean {
    return isXLargeTablet(this)
}

/**
 * {@inheritDoc}
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
override fun onBuildHeaders(target: List<PreferenceActivity.Header>) {
    loadHeadersFromResource(R.xml.pref_headers, target)
}

/**
 * This method stops fragment injection in malicious applications.
 * Make sure to deny any unknown fragments here.
 */
override fun isValidFragment(fragmentName: String): Boolean {
    return PreferenceFragment::class.java.name == fragmentName
            || GeneralPreferenceFragment::class.java.name == fragmentName
            || DataSyncPreferenceFragment::class.java.name == fragmentName
            || NotificationPreferenceFragment::class.java.name == fragmentName
            || AboutFragment::class.java.name == fragmentName
}

/**
 * This fragment shows general preferences only. It is used when the
 * activity is showing a two-pane settings UI.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
class GeneralPreferenceFragment : PreferenceFragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        addPreferencesFromResource(R.xml.pref_general)
        setHasOptionsMenu(true)

        // Bind the summaries of EditText/List/Dialog/Ringtone preferences
        // to their values. When their values change, their summaries are
        // updated to reflect the new value, per the Android Design
        // guidelines.
        findPreference("night_mode").setOnPreferenceChangeListener { preference, newValue ->
            val booleanValue = newValue as Boolean

            if(booleanValue) {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            } else {
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            }

            activity.recreate()
            true
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId
        if (id == android.R.id.home) {
            startActivity(Intent(activity, SettingsActivity::class.java))
            return true
        }
        return super.onOptionsItemSelected(item)
    }
}
 .
 .
 .
 companion object {
 // (didn't change anything here)
 }

Проблема возникает, я думаю, при моих вызовах «воссоздать». Это похоже на то, что onItemClickListener списка предпочтений имеет значение null или что-то подобное.

Кто-нибудь может помочь?

РЕДАКТИРОВАТЬ: упрощено, теперь вся моя логика в классе SettingsActivity, мне не нужно иметь ее в абстрактном классе


person Orestis Gartaganis    schedule 09.10.2018    source источник


Ответы (1)


Я не могу поверить, что решил это, добавив delayedRecreate вместо воссоздания (пробовал разные вещи из другого вопроса - этот):

override fun onPostResume() {
    super.onPostResume()

    // Self-explanatory. When I load back into this screen, if there's a change in the theme, load it back!
    if(hasNightModeChanged()) {
        delayedRecreate()
    }
}

private fun delayedRecreate() {
    val handler = Handler()
    handler.postDelayed(this::recreate, 1)
}

Тем не менее, мне не очень нравится мерцание, которое происходит, когда экран немного задерживается в воссоздании. Если у кого-то есть другие подсказки, это было бы очень признательно!

person Orestis Gartaganis    schedule 09.10.2018
comment
при изменении темы, как вы можете также изменить ее в действии, которое запустило SettingsActivity, когда вы вернетесь? - person glisu; 29.11.2018
comment
Я устанавливаю локальную переменную в onCreate действия: viewModel.mCurrentNightMode = getCurrentNightMode() private fun getCurrentNightMode(): Int { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK } и проверяю, есть ли какие-либо изменения, переопределяющие метод onPostResume(): private fun hasNightModeChanged(): Boolean { return viewModel.mCurrentNightMode != getCurrentNightMode() } если есть изменения, я вызываю recreate() - person Orestis Gartaganis; 02.12.2018
comment
спасибо, но почему вы выполняете проверку в onPostResume, а не в onResume? - person glisu; 02.12.2018
comment
@glisu Это было решение некоторое время назад, должна была быть причина, но теперь, когда я тестирую его, я вижу, что выполнение этого в onResume() также работает, так что выбор за вами! - person Orestis Gartaganis; 03.12.2018