防止 ScrollView 在应用进入后台时返回顶部

Prevent ScrollView from returning to top when app goes to background

提问人:A. Cedano 提问时间:11/14/2023 更新时间:11/14/2023 访问量:24

问:

我在一个片段中有一个 ScrollView,当应用程序进入后台并重新打开它时,它总是回到顶部。

根据这个答案,我试图用 来防止这种情况,但问题仍然存在。android:descendantFocusability="blocksDescendants"

我怎样才能防止它总是去顶部,停留在文本视图的部分,当应用程序进入后台时?

是什么导致了这种行为?我有另一个相同的布局,我将其加载到另一个片段中,并且没有出现问题。为什么它只发生在这个片段中?

布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".core.presentation.today.TodayFragment">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:descendantFocusability="blocksDescendants"
        android:background="@color/textBackground">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <org.deiverbum.app.util.ZoomTextView
                android:id="@+id/tv_Zoomable"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:contentDescription="@string/app_name" />
        </LinearLayout>
    </ScrollView>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="100dp"
            android:layout_marginTop="200dp"
            android:indeterminate="true"
            android:minWidth="200dp"
            android:minHeight="70dp"
            android:progress="0" />
    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

片段

@AndroidEntryPoint
class TodayFragment : BaseFragment<FragmentTodayBinding>() {
    private val mViewModel: TodayViewModel by viewModels()
    private lateinit var mTextView: ZoomTextView
    private var progressBar: ProgressBar? = null
    private var seekBar: SeekBar? = null
    private var isReading = false
    private var isVoiceOn = false
    private var hasInvitatory = false
    private var sbReader: StringBuilder? = null
    private var audioMenu: Menu? = null
    private var voiceItem: MenuItem? = null
    private var mTtsManager: TtsManager? = null
    private var mainMenu: Menu? = null
    private var mActionMode: ActionMode? = null
    private lateinit var todayRequest: TodayRequest

    override fun constructViewBinding(): ViewBinding =
        FragmentTodayBinding.inflate(layoutInflater)

    override fun init(viewBinding: ViewBinding) {
        setMenu()
        setConfiguration()
        fetchData()
    }
    val mActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
        override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
            val menuItem = item.itemId
            if (menuItem == R.id.audio_play) {
                readText()
                audioMenu?.findItem(R.id.audio_pause)?.isVisible = true
                audioMenu?.findItem(R.id.audio_stop)?.isVisible = true
                item.isVisible = false
                return true
            }
            if (menuItem == R.id.audio_pause) {
                mTtsManager?.pause()
                audioMenu?.findItem(R.id.audio_resume)?.isVisible = true
                item.isVisible = false
                return true
            }
            if (menuItem == R.id.audio_resume) {
                mTtsManager?.resume()
                audioMenu?.findItem(R.id.audio_pause)?.isVisible = true
                item.isVisible = false
                return true
            }
            if (menuItem == R.id.audio_stop) {
                mTtsManager?.stop()
                audioMenu?.findItem(R.id.audio_play)?.isVisible = true
                audioMenu?.findItem(R.id.audio_pause)?.isVisible = false
                audioMenu?.findItem(R.id.audio_resume)?.isVisible = false
                item.isVisible = false
                return true
            }
            return false
        }

        override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
            mode.menuInflater.inflate(R.menu.contextual_action_bar, menu)
            audioMenu = menu
            @SuppressLint("InflateParams") val view: View =
                LayoutInflater.from(context).inflate(R.layout.seekbar, null)
            mode.customView = view
            seekBar = view.findViewById(R.id.seekbar)
            return true
        }

        override fun onDestroyActionMode(mode: ActionMode) {
            mActionMode = null
            cleanTTS()
            setPlayerButton()
        }

        override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
            return false
        }
    }

    private fun fetchData() {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mViewModel.uiState.collect { state ->
                    when (state) {
                        is TodayViewModel.TodayUiState.Loaded -> onLoaded(state.itemState)
                        is TodayViewModel.TodayUiState.Error -> showError(state.message)
                        else -> showLoading()
                    }
                }
            }
        }
    }

    private fun onLoaded(todayItemUiState: TodayItemUiState) {
        todayItemUiState.run {
            getViewBinding().progressBar.visibility = View.GONE
            if (todayResponse.success) {
                getViewBinding().tvZoomable.text = todayResponse.dataModel.getAllForView(todayRequest)
                //mTextView.text = todayResponse.dataModel.getAllForView(false,false)
                if (isVoiceOn) {
                    sbReader = todayResponse.dataModel.getAllForRead()
                }
            } else {
                val msgNoData = activity?.resources?.getString(R.string.err_no_data)
                getViewBinding().tvZoomable.text = msgNoData
            }
        }
    }

    private fun showLoading() {
        mTextView.text = PACIENCIA
    }

    private fun showError(stringRes: String) {
        mTextView.text = stringRes
        Toast.makeText(requireContext(), stringRes, Toast.LENGTH_SHORT).show()
    }

    private fun setMenu() {
        val menuHost: MenuHost = requireActivity()
        menuHost.addMenuProvider(object : MenuProvider {
            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
                menuInflater.inflate(R.menu.toolbar_menu, menu)
                mainMenu = menu
                voiceItem = menu.findItem(R.id.item_voz)
                voiceItem!!.isVisible = isVoiceOn
                if (isReading) {
                    voiceItem!!.isVisible = false
                }

            }

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return when (menuItem.itemId) {
                    android.R.id.home -> {
                        val navController =
                            NavHostFragment.findNavController(requireParentFragment())
                        navController.popBackStack()
                        true
                    }
                    R.id.item_voz -> {
                        if (mActionMode == null) {
                            mActionMode =
                                requireActivity().startActionMode(mActionModeCallback)
                        }
                        readText()
                        isReading = true
                        voiceItem?.isVisible = false
                        requireActivity().invalidateOptionsMenu()
                        true
                    }
                    else -> {
                        val navController =
                            NavHostFragment.findNavController(requireParentFragment())
                        NavigationUI.onNavDestinationSelected(menuItem, navController)
                    }
                }

            }
        }, viewLifecycleOwner)
    }

    private fun setConfiguration() {
        val args: TodayFragmentArgs by navArgs()
        mTextView = getViewBinding().tvZoomable
        progressBar = getViewBinding().progressBar
        val sp = PreferenceManager.getDefaultSharedPreferences(requireActivity().applicationContext)
        val fontSize = sp?.getString("font_size", "18")!!.toFloat()
        val fontFamily = String.format(
            Locale("es"),
            "fonts/%s",
            sp.getString("font_name", "robotoslab_regular.ttf")
        )
        val tf = Typeface.createFromAsset(requireActivity().assets, fontFamily)
        mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize)
        mTextView.typeface = tf
        hasInvitatory = sp.getBoolean("invitatorio", false)
        isVoiceOn = sp.getBoolean("voice", true)
        todayRequest =
            TodayRequest(pickOutDate(), args.hourId, isNightMode(), hasInvitatory)
        mViewModel.loadData(todayRequest)
    }

    private fun setPlayerButton() {
        voiceItem!!.isVisible = isVoiceOn
    }

    private fun readText() {
        mTtsManager = TtsManager(
            context,
            sbReader.toString(),
            Constants.SEPARADOR
        ) { current: Int, max: Int ->
            seekBar!!.progress = current
            seekBar!!.max = max
        }
        seekBar!!.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                if (mTtsManager == null) return
                mTtsManager!!.changeProgress(progress)
            }

            override fun onStartTrackingTouch(seekBar: SeekBar) {}
            override fun onStopTrackingTouch(seekBar: SeekBar) {}
        })
        mTtsManager!!.start()
    }

    private fun cleanTTS() {
        mTtsManager?.close()
    }

    private fun pickOutDate(): Int {
        val bundle = arguments
        val mDate = if (bundle != null && bundle.containsKey("FECHA")) {
            bundle.getInt("FECHA")
        } else {
            Utils.hoy.toInt()
        }
        val actionBar = (requireActivity() as AppCompatActivity).supportActionBar
        actionBar?.subtitle = Utils.formatDate(mDate.toString(), "yyyyMMdd", "d '-' MMMM yyyy")
        return mDate
    }


    override fun onDestroyView() {
        super.onDestroyView()
        if (mActionMode != null) {
            mActionMode!!.finish()
        }
        cleanTTS()
    }

}
Kotlin Android布局

评论

0赞 ianhanniballake 11/14/2023
你没有的原因吗?如果视图没有 ID 且 ID 包括 ScrollView 的滚动位置,则不会保存其状态。android:idScrollView
0赞 A. Cedano 11/14/2023
@ianhanniballake我在滚动视图中添加了一个 id,但没有任何变化。我应该指出,只有当我在文本视图上执行“捏合缩放”时,它才会返回顶部,当文本处于正常大小时,它不会返回顶部。但是在另一个布局/片段中,我也使用“捏合缩放”,问题不会发生。
0赞 A. Cedano 11/14/2023
@ianhanniballake我添加到里面,它工作正常!android:descendantFocusability="blocksDescendants"LinearLayoutScrollView

答: 暂无答案