当前位置: 首页 > 知识库问答 >
问题:

Jetpack Compose - 在配置更改时保留 AndroidView 的状态

罗毅
2023-03-14

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

@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是一个具有复杂图表的第三方组件,我想保留缩放/滚动状态。我知道我可以使用ViewModel跨配置更改保存UI状态,但考虑到保存缩放/滚动状态的复杂性,我想问一下是否有其他更简单的方法来实现这一点?

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

任何帮助都将不胜感激!

共有3个答案

法烨烨
2023-03-14

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

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

除此之外,我现在将为您的困境提供两种可能的解决方案:一种是利用AndroidViewModel,另一种是不利用。如果视图模型已经具有上下文之外的其他依赖项,AndroidViewModel解决方案不会为您节省太多开销,因为您可能已经实例化了ViewModelProvider。工厂在某个点。这些解决方案将考虑Android<code>片段活动或<code>DialogFragment</code>中实现,并对生命周期挂钩等进行一些调整。

使用 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,
    )
}

编辑(通过刀柄模块):

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

    @Provides
    fun provideDataChart(
        @ApplicationContext context: Context,
    ): DataChart = DataChart(context)
}
归星驰
2023-03-14

这是我知道的最简单的方法。当我旋转手机时,它会保持状态,不会触发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!!
    }
}

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

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

最后,将其添加到composable中

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

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

龙兴贤
2023-03-14

如果要在配置更改时保留AndroidView的状态,请使用< code>rememberSaveable而不是< code > remembers 。

虽然remember有助于您在重新组合时保留状态,但在配置更改时不会保留状态。为此,必须使用rememberSaveable。rememberSaveable自动保存任何可以保存在包中的值。对于其他值,可以传入一个自定义的saver对象。

如果数据图表是一种可打包、可序列化的数据类型或其他可以捆绑存储的数据类型:

val chart = rememberSaveable { DataChart(context) }

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

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) }

查看更多存储状态的方法

 类似资料:
  • 问题内容: 示例应用程序: http //angular.github.com/angular- phonecat/step-11/app/#/phones 如果您选择最后一个手机“ Motorola charm”,它将为您显示手机的详细信息。当您在浏览器上浏览时,它将重新加载数据,并且滚动条位于顶部。 导航返回时,自动滚动到剩余位置的最佳方法是什么?而且,为什么角度重新加载数据? 我的计算机上有

  • 我是RxJava的新手,并将其与MVP架构一起使用。 我发现了一些使用保留片段在配置更改时保存可观察对象的示例(仍然不确定这是否是最好的方法)。不过,我发现的示例是直接在活动或片段上处理可观察对象,而不是从演示者那里。 因此,我尝试并设置了这个快速示例(仅使用Reactivex的RxJava和RxAndroid库)以进行测试,看起来效果不错。此示例所做的是: 使用无头保留片段启动活动 我想知道我这

  • 慢慢地,jpanel的背景色将变得比以前更不透明。值得注意的是,我正在使用jpanel的挫折方法。以下是一些您可能想要查看的代码链接。 自定义GUI按钮 它所在的Gui--请看第158行。

  • 我已经开发了一个自定义滚动文件附加器,它工作得很好。唯一的问题是,每次我重新启动服务器或更改log4j2时,它都会被重新初始化。xml文件(文件的任何部分)和之前的所有日志会突然被删除。我还没有观察到默认附加器的这种行为,所以我想知道我能做些什么来保留我的配置。 附言:我试图使它成为一个单例,但除此之外,它不起作用,我真的不想让它远离被重新配置,我只想保留我以前生成的日志。 使现代化 显然,每次服

  • 问题内容: 当我从一个视图控制器移动到另一个视图控制器时,第一个控制器上的开关会重置自身,并且不会保持其状态。查看其他控制器后,如何恢复状态?以及在关闭应用程序后如何使其保存状态。我看过各种stackOverflow问题和响应以及Apple文档,但似乎没有任何效果。 这是我的带有开关的View Controller的类。 我是Swift和Xcode的初学者。预先感谢您的时间和帮助:) 问题答案:

  • 我知道React可能会异步和批量地执行状态更新,以进行性能优化。因此,在调用之后,您永远不能相信会更新状态。但是,您是否信任React按照调用时的顺序更新状态 相同的组件? 不同的组件? 任何有文档支持的答案都是非常感谢的。