强制重拨(Android 撰写)
forcing a recomposition (Android compose)
代码:
package com.example.saveandloadusername
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import com.example.saveandloadusername.ui.theme.SaveAndLoadUserNameTheme
import java.io.File
import java.io.IOException
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SaveAndLoadUserNameTheme {
Surface(color = MaterialTheme.colors.background) {
MainScreen(baseContext)
}
}
}
}
}
@Composable
fun MainScreen(context: Context) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
{
var name by remember { mutableStateOf("")}
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Text(text="Hello, give me your name :)")
} else {
Text(text="welcome back ${readNameFromInternalStorage(context)}")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
OutlinedTextField(
value=name,
onValueChange={ name = it },
label={Text(text="Name")},
singleLine = true,
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
)
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
name = name.replace(" ", "".replace("\n", ""))
if(name == "") {
Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
saveNameToInternalStorage(name, context)
}
},
)
{
Text(text="save")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(
context,
"the name is: '${readNameFromInternalStorage(context)}'",
Toast.LENGTH_SHORT
).show()
}
},
)
{
Text(text="check")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(onClick = { cleanNameData(context)}) {
Text("Remove name")
}
}
}
private fun saveNameToInternalStorage(name: String, context: Context): Boolean {
return try {
context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
stream.flush()
stream.write(name.toByteArray())
Log.i("SAVE_STATE","name ($name) written as ${name.toByteArray()}")
}
return true
} catch(e: IOException) {
e.printStackTrace()
false
}
}
private fun readNameFromInternalStorage(context: Context): String {
val file = File(context.filesDir, "name.txt")
return if (file.exists()) {
val contentOfFile = file.readBytes().decodeToString()
Log.i("CONTENTTT", contentOfFile)
contentOfFile
} else {
""
}
}
private fun checkIfNameIsEmpty(name: String): Boolean {
return name.isEmpty()
}
private fun cleanNameData(context: Context) {
context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
stream.flush()
Log.i("cleanNameData", "Name Removed from memory")
Toast.makeText(context, "Name removed", Toast.LENGTH_SHORT).show()
}
}
我创建了一个小应用程序来了解 android compose,但我偶然发现了一个我无法解决的问题,文本(位于 TextField 上方)在按下按钮“删除名称”或“保存”,文本仅在文本框中发生变化时更新,有没有办法手动强制重新组合该文本?
任何帮助表示赞赏:)
在下面的代码块中,您没有在任何地方使用 name
变量
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Text(text="Hello, give me your name :)")
} else {
Text(text="welcome back ${readNameFromInternalStorage(context)}")
}
因此,更改 name
值不会对重组产生任何影响。如果你真的想要,你需要(有点)remember{readNameFromInternalStorage(context)}
为了让重组对文本产生影响。
首先,如果您需要做的只是在那儿显示那个名字,那为什么还要费力地将它存储到存储器中,然后再读回来呢?建议:可以直接使用name参数作为Text
的值,修改后直接将新的name存入storage,如果交易成功,再更新变量。它将触发必要的重组。
接下来,文本未更新的原因是因为您用于从存储中获取名称的方法 returns 是原始类型,而不是 LiveData
。因此,如果您自己实现该方法,请先尝试阅读 LiveData
,而不是手动触发重组。实现起来应该不难。
但是(不推荐),如果您想要的只是一种手动重新触发合成的方法,那么这里就是。
您可能知道,Compose 使用 MutableState
对象来观察数据,您似乎对它非常熟悉,因为您已经在使用它了。因此,您需要做的只是添加一个 MutableState
类型的 'dummy' 变量,然后从相关按钮的 onClick
修改它。此外,您必须向 Compose 发送一条消息,表明正在 Text
中读取虚拟变量,因此它会触发重新组合。
@Composable
fun MainScreen(context: Context) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
{
var name by remember { mutableStateOf("")}
var dummy by mutableStateOf(false) // don't even need to remember, long as it compiles
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
dummy
Text(text="Hello, give me your name :)")
} else {
dummy
Text(text="welcome back ${readNameFromInternalStorage(context)}")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
OutlinedTextField(
value=name,
onValueChange={ name = it },
label={Text(text="Name")},
singleLine = true,
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
)
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
dummy = !dummy // modify to trigger recomposition
name = name.replace(" ", "".replace("\n", ""))
if(name == "") {
Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
saveNameToInternalStorage(name, context)
}
},
)
{
Text(text="save")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
dummy = !dummy // The same here
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(
context,
"the name is: '${readNameFromInternalStorage(context)}'",
Toast.LENGTH_SHORT
).show()
}
},
)
{
Text(text="check")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(onClick = {
dummy = !dummy //That's all
cleanNameData(context)
}) {
Text("Remove name")
}
}
}
是的,应该这样做。
还有,您不需要粘贴整个代码库。只需提供必要的位,如果需要,我们会要求更多。谢谢,
代码:
package com.example.saveandloadusername
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import com.example.saveandloadusername.ui.theme.SaveAndLoadUserNameTheme
import java.io.File
import java.io.IOException
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SaveAndLoadUserNameTheme {
Surface(color = MaterialTheme.colors.background) {
MainScreen(baseContext)
}
}
}
}
}
@Composable
fun MainScreen(context: Context) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
{
var name by remember { mutableStateOf("")}
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Text(text="Hello, give me your name :)")
} else {
Text(text="welcome back ${readNameFromInternalStorage(context)}")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
OutlinedTextField(
value=name,
onValueChange={ name = it },
label={Text(text="Name")},
singleLine = true,
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
)
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
name = name.replace(" ", "".replace("\n", ""))
if(name == "") {
Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
saveNameToInternalStorage(name, context)
}
},
)
{
Text(text="save")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(
context,
"the name is: '${readNameFromInternalStorage(context)}'",
Toast.LENGTH_SHORT
).show()
}
},
)
{
Text(text="check")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(onClick = { cleanNameData(context)}) {
Text("Remove name")
}
}
}
private fun saveNameToInternalStorage(name: String, context: Context): Boolean {
return try {
context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
stream.flush()
stream.write(name.toByteArray())
Log.i("SAVE_STATE","name ($name) written as ${name.toByteArray()}")
}
return true
} catch(e: IOException) {
e.printStackTrace()
false
}
}
private fun readNameFromInternalStorage(context: Context): String {
val file = File(context.filesDir, "name.txt")
return if (file.exists()) {
val contentOfFile = file.readBytes().decodeToString()
Log.i("CONTENTTT", contentOfFile)
contentOfFile
} else {
""
}
}
private fun checkIfNameIsEmpty(name: String): Boolean {
return name.isEmpty()
}
private fun cleanNameData(context: Context) {
context.applicationContext.openFileOutput("name.txt", MODE_PRIVATE).use { stream ->
stream.flush()
Log.i("cleanNameData", "Name Removed from memory")
Toast.makeText(context, "Name removed", Toast.LENGTH_SHORT).show()
}
}
我创建了一个小应用程序来了解 android compose,但我偶然发现了一个我无法解决的问题,文本(位于 TextField 上方)在按下按钮“删除名称”或“保存”,文本仅在文本框中发生变化时更新,有没有办法手动强制重新组合该文本? 任何帮助表示赞赏:)
在下面的代码块中,您没有在任何地方使用 name
变量
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Text(text="Hello, give me your name :)")
} else {
Text(text="welcome back ${readNameFromInternalStorage(context)}")
}
因此,更改 name
值不会对重组产生任何影响。如果你真的想要,你需要(有点)remember{readNameFromInternalStorage(context)}
为了让重组对文本产生影响。
首先,如果您需要做的只是在那儿显示那个名字,那为什么还要费力地将它存储到存储器中,然后再读回来呢?建议:可以直接使用name参数作为Text
的值,修改后直接将新的name存入storage,如果交易成功,再更新变量。它将触发必要的重组。
接下来,文本未更新的原因是因为您用于从存储中获取名称的方法 returns 是原始类型,而不是 LiveData
。因此,如果您自己实现该方法,请先尝试阅读 LiveData
,而不是手动触发重组。实现起来应该不难。
但是(不推荐),如果您想要的只是一种手动重新触发合成的方法,那么这里就是。
您可能知道,Compose 使用 MutableState
对象来观察数据,您似乎对它非常熟悉,因为您已经在使用它了。因此,您需要做的只是添加一个 MutableState
类型的 'dummy' 变量,然后从相关按钮的 onClick
修改它。此外,您必须向 Compose 发送一条消息,表明正在 Text
中读取虚拟变量,因此它会触发重新组合。
@Composable
fun MainScreen(context: Context) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
)
{
var name by remember { mutableStateOf("")}
var dummy by mutableStateOf(false) // don't even need to remember, long as it compiles
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
dummy
Text(text="Hello, give me your name :)")
} else {
dummy
Text(text="welcome back ${readNameFromInternalStorage(context)}")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
OutlinedTextField(
value=name,
onValueChange={ name = it },
label={Text(text="Name")},
singleLine = true,
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
)
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
dummy = !dummy // modify to trigger recomposition
name = name.replace(" ", "".replace("\n", ""))
if(name == "") {
Toast.makeText(context, "name invalid", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "name (${name}) saved :D", Toast.LENGTH_SHORT).show()
saveNameToInternalStorage(name, context)
}
},
)
{
Text(text="save")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(
onClick = {
dummy = !dummy // The same here
if(checkIfNameIsEmpty(readNameFromInternalStorage(context))) {
Toast.makeText(context, "you need to give me a name first ;)", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(
context,
"the name is: '${readNameFromInternalStorage(context)}'",
Toast.LENGTH_SHORT
).show()
}
},
)
{
Text(text="check")
}
Spacer(modifier = Modifier
.height(10.dp)
.fillMaxWidth())
Button(onClick = {
dummy = !dummy //That's all
cleanNameData(context)
}) {
Text("Remove name")
}
}
}
是的,应该这样做。
还有,您不需要粘贴整个代码库。只需提供必要的位,如果需要,我们会要求更多。谢谢,