Android Livedata 使用对象的副本更新他的状态
Android Livedata updates his states with copy of objects
嗨,我想我现在真的不知道实时数据是如何工作的。
我有一个带有预填充值的二维数组。
val randomboard =
arrayOf(arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0))
还有一个实时数据对象,它只在我的 ViewModel 的初始化函数中发布一次我的数组的当前状态。
init {
_preFillButtons.postValue(randomboard.copyOf())
}
所以我希望只有我的数组的当前状态而不是未来的状态被推送到我的 LiveData 对象,所以我给了它一个我的数组的副本。
当我更改我的 Array randomboard 的任何值然后像我的方向一样更改时,我的 livedata 对象在他的状态中只有这个新值,而没有我将任何新状态推送到该 LiveData 对象。
也许 Livedata 对象不是我需要的,但我不知道为什么连复制对象都更新了。
更新:
完整代码
class GameActivity : AppCompatActivity(), View.OnClickListener {
val viewModel: GameViewModel by viewModels()
val buttonIDs = arrayOf(
intArrayOf(R.id.button1, R.id.button2, R.id.button3, R.id.button4),
intArrayOf(R.id.button5, R.id.button6, R.id.button7, R.id.button8),
intArrayOf(R.id.button9, R.id.button10, R.id.button11, R.id.button12),
intArrayOf(R.id.button13, R.id.button14, R.id.button15, R.id.button16)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.preFillButtons.observe(this) {
prefillButtons(it)
}
}
private fun prefillButtons(board: Array<Array<Int>>) {
for (row in 0..3) {
for (col in 0..3) {
val button = findViewById<Button>(buttonIDs[row][col])
button.setTag(R.id.row, row)
button.setTag(R.id.column, col)
button.setOnClickListener(this)
if (board[row][col] != 0) {
button.text = board[row][col].toString()
button.isEnabled = false
} else {
button.text = getString(R.string.defaultbuttontext)
button.isEnabled = true
}
}
}
}
override fun onClick(view: View?) {
val button = view as? Button
if (button != null) {
val row = button.getTag(R.id.row) as Int
val col = button.getTag(R.id.column) as Int
showAlertDialog(col, row)
}
}
private fun showAlertDialog(col: Int, row: Int) {
val builder = AlertDialog.Builder(this)
builder.setItems(R.array.choices) { dialogInterface: DialogInterface, i: Int ->
if (i != 4) {
viewModel.trySetValue(row, col, i + 1)
}
}
builder.setTitle(getString(R.string.dialogtitle, row + 1, col + 1))
builder.show()
}
}
视图模型:
class GameViewModel : ViewModel() {
val preFillButtons: LiveData<Array<Array<Int>>>
get() = _preFillButtons
private val _preFillButtons = MutableLiveData<Array<Array<Int>>>()
val randomboard =
arrayOf(arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0))
init {
_preFillButtons.postValue(randomboard.copyOf())
}
fun trySetValue(row: Int, col: Int, num: Int) {
randomboard[0][0] = 1
}
}
屏幕旋转后(配置更改后)看到新状态与 LiveData 的工作方式无关。您正在正确观察您的 LiveData。
So i want that only the current State of my Array and no future states are pushed to my LiveData Objects so i gave it a copy of my Array.
此处稍作更正,您给了它一个 shallow 数组副本。
浅拷贝是原始数组的拷贝,但如果项目是实例(对对象的引用),它们将通过引用进行拷贝。这意味着对数组副本中项目的更改也会影响原始数组中的项目,因为它是相同的引用(=相同的实例)。
并且由于您有一个数组数组,所以数组中的项目是其他 4 个数组的实例。当您在 trySetValue
函数中更改 randomboard
内的值时,您每次都会访问相同的 4 个数组实例。
arrayOf
调用创建一个新的数组实例并用作为参数提供的值填充它。因此,您对 randomboard
的初始分配会创建 5 个数组实例:
- 4 是 Integer[] 类型,包含值 0、0、0、0
- 1 是 Integer[][] 类型,包含对上述 4 个数组的引用。对该数组的引用存储在字段
randomboards
中
这段代码通过引用比较值,显示主数组是一个新实例(因为 copyOf
调用),但里面的数组是相同的实例。
init {
val copy = randomboard.copyOf()
Log.d("Test", "copy === randomboard: ${copy === randomboard}") // false
Log.d("Test", "copy[0] === randomboard[0]: ${copy[0] === randomboard[0]}") // true
Log.d("Test", "copy[1] === randomboard[1]: ${copy[1] === randomboard[1]}") // true
Log.d("Test", "copy[2] === randomboard[2]: ${copy[2] === randomboard[2]}") // true
Log.d("Test", "copy[3] === randomboard[3]: ${copy[3] === randomboard[3]}") // true
//...
}
如果您不希望更改影响数组的复制,则必须进行深度复制。您可以通过在源数组上调用 .map
或使用将初始化函数作为第二个参数的 Array<T>(size: Int, init: (Int) -> T)
构造函数来实现。
通过在源数组上调用 map
init {
val deepCopy = randomboard.map { it.copyOf() }.toTypedArray()
_preFillButtons.postValue(deepCopy)
}
通过使用 Array<T>(size: Int, init: (Int) -> T)
构造函数
init {
// Same as: val deepCopy = Array(randomboard.size) { idx -> randomboard[idx].copyOf() } }
val deepCopy = randomboard.let { Array(it.size) { idx -> it[idx].copyOf() } }
_preFillButtons.postValue(deepCopy)
}
然后,如果您通过引用比较所有值,您会发现现在内部数组也是一个新实例。
init {
val deepCopy = randomboard.map { it.copyOf() }.toTypedArray()
Log.d("Test", "deepCopy === randomboard: ${deepCopy === randomboard}") // false
Log.d("Test", "deepCopy[0] === randomboard[0]: ${deepCopy[0] === randomboard[0]}") // false
Log.d("Test", "deepCopy[1] === randomboard[1]: ${deepCopy[1] === randomboard[1]}") // false
Log.d("Test", "deepCopy[2] === randomboard[2]: ${deepCopy[2] === randomboard[2]}") // false
Log.d("Test", "deepCopy[3] === randomboard[3]: ${deepCopy[3] === randomboard[3]}") // false
// ...
}
嗨,我想我现在真的不知道实时数据是如何工作的。
我有一个带有预填充值的二维数组。
val randomboard =
arrayOf(arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0))
还有一个实时数据对象,它只在我的 ViewModel 的初始化函数中发布一次我的数组的当前状态。
init {
_preFillButtons.postValue(randomboard.copyOf())
}
所以我希望只有我的数组的当前状态而不是未来的状态被推送到我的 LiveData 对象,所以我给了它一个我的数组的副本。
当我更改我的 Array randomboard 的任何值然后像我的方向一样更改时,我的 livedata 对象在他的状态中只有这个新值,而没有我将任何新状态推送到该 LiveData 对象。
也许 Livedata 对象不是我需要的,但我不知道为什么连复制对象都更新了。
更新:
完整代码
class GameActivity : AppCompatActivity(), View.OnClickListener {
val viewModel: GameViewModel by viewModels()
val buttonIDs = arrayOf(
intArrayOf(R.id.button1, R.id.button2, R.id.button3, R.id.button4),
intArrayOf(R.id.button5, R.id.button6, R.id.button7, R.id.button8),
intArrayOf(R.id.button9, R.id.button10, R.id.button11, R.id.button12),
intArrayOf(R.id.button13, R.id.button14, R.id.button15, R.id.button16)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.preFillButtons.observe(this) {
prefillButtons(it)
}
}
private fun prefillButtons(board: Array<Array<Int>>) {
for (row in 0..3) {
for (col in 0..3) {
val button = findViewById<Button>(buttonIDs[row][col])
button.setTag(R.id.row, row)
button.setTag(R.id.column, col)
button.setOnClickListener(this)
if (board[row][col] != 0) {
button.text = board[row][col].toString()
button.isEnabled = false
} else {
button.text = getString(R.string.defaultbuttontext)
button.isEnabled = true
}
}
}
}
override fun onClick(view: View?) {
val button = view as? Button
if (button != null) {
val row = button.getTag(R.id.row) as Int
val col = button.getTag(R.id.column) as Int
showAlertDialog(col, row)
}
}
private fun showAlertDialog(col: Int, row: Int) {
val builder = AlertDialog.Builder(this)
builder.setItems(R.array.choices) { dialogInterface: DialogInterface, i: Int ->
if (i != 4) {
viewModel.trySetValue(row, col, i + 1)
}
}
builder.setTitle(getString(R.string.dialogtitle, row + 1, col + 1))
builder.show()
}
}
视图模型:
class GameViewModel : ViewModel() {
val preFillButtons: LiveData<Array<Array<Int>>>
get() = _preFillButtons
private val _preFillButtons = MutableLiveData<Array<Array<Int>>>()
val randomboard =
arrayOf(arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0))
init {
_preFillButtons.postValue(randomboard.copyOf())
}
fun trySetValue(row: Int, col: Int, num: Int) {
randomboard[0][0] = 1
}
}
屏幕旋转后(配置更改后)看到新状态与 LiveData 的工作方式无关。您正在正确观察您的 LiveData。
So i want that only the current State of my Array and no future states are pushed to my LiveData Objects so i gave it a copy of my Array.
此处稍作更正,您给了它一个 shallow 数组副本。 浅拷贝是原始数组的拷贝,但如果项目是实例(对对象的引用),它们将通过引用进行拷贝。这意味着对数组副本中项目的更改也会影响原始数组中的项目,因为它是相同的引用(=相同的实例)。
并且由于您有一个数组数组,所以数组中的项目是其他 4 个数组的实例。当您在 trySetValue
函数中更改 randomboard
内的值时,您每次都会访问相同的 4 个数组实例。
arrayOf
调用创建一个新的数组实例并用作为参数提供的值填充它。因此,您对 randomboard
的初始分配会创建 5 个数组实例:
- 4 是 Integer[] 类型,包含值 0、0、0、0
- 1 是 Integer[][] 类型,包含对上述 4 个数组的引用。对该数组的引用存储在字段
randomboards
中
这段代码通过引用比较值,显示主数组是一个新实例(因为 copyOf
调用),但里面的数组是相同的实例。
init {
val copy = randomboard.copyOf()
Log.d("Test", "copy === randomboard: ${copy === randomboard}") // false
Log.d("Test", "copy[0] === randomboard[0]: ${copy[0] === randomboard[0]}") // true
Log.d("Test", "copy[1] === randomboard[1]: ${copy[1] === randomboard[1]}") // true
Log.d("Test", "copy[2] === randomboard[2]: ${copy[2] === randomboard[2]}") // true
Log.d("Test", "copy[3] === randomboard[3]: ${copy[3] === randomboard[3]}") // true
//...
}
如果您不希望更改影响数组的复制,则必须进行深度复制。您可以通过在源数组上调用 .map
或使用将初始化函数作为第二个参数的 Array<T>(size: Int, init: (Int) -> T)
构造函数来实现。
通过在源数组上调用 map
init {
val deepCopy = randomboard.map { it.copyOf() }.toTypedArray()
_preFillButtons.postValue(deepCopy)
}
通过使用 Array<T>(size: Int, init: (Int) -> T)
构造函数
init {
// Same as: val deepCopy = Array(randomboard.size) { idx -> randomboard[idx].copyOf() } }
val deepCopy = randomboard.let { Array(it.size) { idx -> it[idx].copyOf() } }
_preFillButtons.postValue(deepCopy)
}
然后,如果您通过引用比较所有值,您会发现现在内部数组也是一个新实例。
init {
val deepCopy = randomboard.map { it.copyOf() }.toTypedArray()
Log.d("Test", "deepCopy === randomboard: ${deepCopy === randomboard}") // false
Log.d("Test", "deepCopy[0] === randomboard[0]: ${deepCopy[0] === randomboard[0]}") // false
Log.d("Test", "deepCopy[1] === randomboard[1]: ${deepCopy[1] === randomboard[1]}") // false
Log.d("Test", "deepCopy[2] === randomboard[2]: ${deepCopy[2] === randomboard[2]}") // false
Log.d("Test", "deepCopy[3] === randomboard[3]: ${deepCopy[3] === randomboard[3]}") // false
// ...
}