5

很可能是一个新手问题,因为我对 Android 开发人员还很陌生 - 我在配置更改/导航时在 @Composable 中保留 AndroidView 的状态时遇到了麻烦,因为工厂块被调用(如预期的那样)并且我的图表被重新实例化。

@Composable
fun ChartView(viewModel:ViewModel, modifier:Modifier){
    val context = LocalContext.current
    val chart = remember { DataChart(context) }

    AndroidView(
        modifier = modifier,
        factory = { context ->
            Log.d("DEBUGLOG", "chart init")
            chart
        },
        update = { chart ->
            Log.d("DEBUGLOG", "chart update")
        })
}

DataChart是具有复杂图表的第 3 方组件,我想保留缩放/滚动状态。我知道我可以使用 ViewModel 在 conf 中保留 UI 状态。变化,但考虑到保存缩放/滚动状态的复杂性,我想问一下是否还有其他更简单的方法来实现这一点?

我试图将整个图表实例移动到 viewModel,但由于它使用上下文,我收到有关上下文对象泄漏的警告。

任何帮助,将不胜感激!

4

3 回答 3

2

如果您想在配置更改时保留 AndroidView 的状态,请rememberSaveable改用remember

虽然记住可以帮助您在重新组合中保留状态,但状态不会在配置更改中保留。为此,您必须使用 rememberSaveablerememberSaveable自动保存任何可以保存在 Bundle 中的值。对于其他值,您可以传入自定义保护程序对象。

如果 DataChart 是 Parcelable、Serializable 或其他可以存储在 bundle 中的数据类型:

val chart = rememberSaveable { DataChart(context) }

如果上述方式不起作用,则创建一个 MapSave 来保存数据、缩放/滚动状态...我假设 DataChart 具有您需要保存的 zoomIndex、scrollingIndex、values 属性:

fun getDataChartSaver(context: Context) = run {
    val zoomKey = "ZoomState"
    val scrollingKey = "ScrollingState"
    val dataKey = "DataState"

    mapSaver(
        save = { mapOf(zoomKey to it.zoomIndex, scrollingKey to it.scrollingIndex, dataKey to it.values) },
        restore = { 
            DataChart(context).apply{
               zoomIndex = it[zoomKey]
               scrollingIndex = it[scrollingKey]
               values = it[dataKey]
            } 
        }
    )
}

采用:

val chart = rememberSaveable(stateSaver = getDataChartSaver(context)){ DataChart(context) }

查看更多存储状态的方法

于 2021-06-08T22:53:41.153 回答
1

这是我所知道的最简单的方法。当我旋转手机时,这会保持状态并且不会触发 WebView 上的重新加载。它应该适用于每个视图。

首先创建一个应用类

class MainApplication : Application() {

    private var view: WebView? = null

    override fun onCreate() {
        super.onCreate()
        LOG("started application")
    }

    fun loadView(context: Context): WebView {
        if (view == null) {
            view = WebView(context)
            //init your view here
        }
        return view!!
    }
}

然后将应用程序类添加到manifest.xml

<manifest>
    ...
    <application
        android:name=".MainApplication"
    ...

最后将其添加到可组合

AndroidView(modifier = Modifier.fillMaxSize(), factory = { context ->
    val application = context.applicationContext as MainApplication
    application.loadView(context = context)
})

就是这样。我不确定这是否会导致内存泄漏,但我还没有遇到问题。

于 2021-10-27T01:51:31.833 回答
0

我想说你的直觉是正确地将图表实例移动到视图模型中,但是,正如你所指出的,当视图以外的对象需要上下文依赖关系时,它们可能会变得很麻烦。对我来说,这变成了依赖注入的问题,其中依赖是上下文,或者更广泛地说,是整个数据图表。我很想知道您如何获取视图模型,但我假设它依赖于 Android 视图模型提供程序(通过by viewModels()或某种方式ViewModelProvider.Factory)。

此问题的直接解决方案是将视图模型转换为AndroidViewModel通过视图模型的构造函数提供对应用程序上下文的引用的子类。虽然它仍然是一种反模式并且应该谨慎使用,但 Android 团队已经认识到某些用例是有效的。我个人不使用,AndroidViewModel因为我认为它是一个粗略的解决方案,否则可以通过改进依赖图来解决。但是,它是由官方文档批准的,这只是我的个人意见。根据经验,我必须说它的使用使得测试视图模型成为事后的噩梦。如果您对依赖注入库感兴趣,我强烈推荐Hilt最近推出稳定版的新实现1.0.0就在上个月发布。

撇开这一点不谈,我现在将为您的困境提供两种可能的解决方案:一种利用 the AndroidViewModel,另一种不利用。如果您的视图模型已经在上下文之外具有其他依赖项,则该AndroidViewModel解决方案不会为您节省太多开销,因为您可能已经ViewModelProvider.Factory在某个时候实例化了 a。这些解决方案将考虑 Android 的范围,但可以通过对生命周期钩子等进行一些调整轻松Fragment实现。ActivityDialogFragment

AndroidViewModel

import android.app.Application
import androidx.lifecycle.AndroidViewModel

class MyViewModel(application: Application) : AndroidViewModel(application) {

    val dataChart: DataChart

    init {
        dataChart = DataChart(application.applicationContext)
    }
}

片段可能在哪里

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View { ... }
}

没有AndroidViewModel

import androidx.lifecycle.ViewModel

class MyViewModel(args: Args) : ViewModel() {

    data class Args(
        val dataChart: DataChart
    )

    val dataChart: DataChart = args.dataChart
}

片段可能在哪里

class MyFragment : Fragment() {

    private lateinit var viewModel: MyViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val applicationContext: Context = requireContext().applicationContext
        val dataChart = DataChart(applicationContext)

        val viewModel: MyViewModel by viewModels {
            ArgsViewModelFactory(
                args = MyViewModel.Args(
                    dataChart = dataChart,
                ),
                argsClass = MyViewModel.Args::class.java,
            )
        }

        this.viewModel = viewModel

        ...
    }
}

我自己的创作在哪里ArgsViewModelFactory,如下所示

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class ArgsViewModelFactory<T>(
    private val args: T,
    private val argsClass: Class<T>,
) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T = modelClass.getConstructor(
        argsClass,
    ).newInstance(
        args,
    )
}

编辑(通过 Hilt 模块):

@Module
@InstallIn(...)
object DataChartModule {

    @Provides
    fun provideDataChart(
        @ApplicationContext context: Context,
    ): DataChart = DataChart(context)
}
于 2021-06-08T21:34:08.687 回答