Bu sayfada, değere dayalı TextField
'leri duruma dayalı TextField
'lere nasıl taşıyabileceğinize dair örnekler verilmiştir. Değere ve duruma dayalı TextField
'ler arasındaki farklar hakkında bilgi edinmek için Metin alanlarını yapılandırma sayfasına bakın.
Temel kullanım
Değere dayalı
@Composable fun OldSimpleTextField() { var state by rememberSaveable { mutableStateOf("") } TextField( value = state, onValueChange = { state = it }, singleLine = true, ) }
Duruma dayalı
@Composable fun NewSimpleTextField() { TextField( state = rememberTextFieldState(), lineLimits = TextFieldLineLimits.SingleLine ) }
value, onValueChange
veremember { mutableStateOf("")
} yerinerememberTextFieldState()
yazın.singleLine = true
yerinelineLimits = TextFieldLineLimits.SingleLine
yazın.
onValueChange
üzerinden filtreleme
Değere dayalı
@Composable fun OldNoLeadingZeroes() { var input by rememberSaveable { mutableStateOf("") } TextField( value = input, onValueChange = { newText -> input = newText.trimStart { it == '0' } } ) }
Duruma dayalı
@Preview @Composable fun NewNoLeadingZeros() { TextField( state = rememberTextFieldState(), inputTransformation = InputTransformation { while (length > 0 && charAt(0) == '0') delete(0, 1) } ) }
- Değer geri çağırma döngüsünü
rememberTextFieldState()
ile değiştirin. InputTransformation
kullanarakonValueChange
'te filtreleme mantığını yeniden uygulayın.state
öğesini güncellemek içinInputTransformation
alıcı kapsamındakiTextFieldBuffer
öğesini kullanın.InputTransformation
, kullanıcı girişi algılandıktan hemen sonra çağrılır.InputTransformation
ileTextFieldBuffer
arasında önerilen değişiklikler hemen uygulanır. Böylece, yazılım klavyesi ileTextField
arasında senkronizasyon sorunu yaşanmaz.
Kredi kartı biçimlendirici TextField
Değere dayalı
@Composable fun OldTextFieldCreditCardFormatter() { var state by remember { mutableStateOf("") } TextField( value = state, onValueChange = { if (it.length <= 16) state = it }, visualTransformation = VisualTransformation { text -> // Making XXXX-XXXX-XXXX-XXXX string. var out = "" for (i in text.indices) { out += text[i] if (i % 4 == 3 && i != 15) out += "-" } TransformedText( text = AnnotatedString(out), offsetMapping = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { if (offset <= 3) return offset if (offset <= 7) return offset + 1 if (offset <= 11) return offset + 2 if (offset <= 16) return offset + 3 return 19 } override fun transformedToOriginal(offset: Int): Int { if (offset <= 4) return offset if (offset <= 9) return offset - 1 if (offset <= 14) return offset - 2 if (offset <= 19) return offset - 3 return 16 } } ) } ) }
Duruma dayalı
@Composable fun NewTextFieldCreditCardFormatter() { val state = rememberTextFieldState() TextField( state = state, inputTransformation = InputTransformation.maxLength(16), outputTransformation = OutputTransformation { if (length > 4) insert(4, "-") if (length > 9) insert(9, "-") if (length > 14) insert(14, "-") }, ) }
- Girişin maksimum uzunluğunu ayarlamak için
onValueChange
içindeki filtrelemeyiInputTransformation
ile değiştirin.onValueChange
ile filtreleme bölümüne bakın.
- Tire eklemek için
VisualTransformation
yerineOutputTransformation
yazın.VisualTransformation
ile hem kısa çizgilerle yeni metni oluşturmaktan hem de dizinin görsel metin ile destek durumu arasında nasıl eşleneceğini hesaplamaktan siz sorumlusunuz.OutputTransformation
, ofset eşlemesini otomatik olarak yapar.OutputTransformation.transformOutput
alıcı kapsamındakiTextFieldBuffer
öğesini kullanarak kısa çizgileri doğru yerlere eklemeniz yeterlidir.
Durumu güncelleme (basit)
Değere dayalı
@Composable fun OldTextFieldStateUpdate(userRepository: UserRepository) { var username by remember { mutableStateOf("") } LaunchedEffect(Unit) { username = userRepository.fetchUsername() } TextField( value = username, onValueChange = { username = it } ) }
Duruma dayalı
@Composable fun NewTextFieldStateUpdate(userRepository: UserRepository) { val usernameState = rememberTextFieldState() LaunchedEffect(Unit) { usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) } TextField(state = usernameState) }
- Değer geri çağırma döngüsünü
rememberTextFieldState()
ile değiştirin. - Değer atamasını
TextFieldState.setTextAndPlaceCursorAtEnd
ile değiştirin.
Durumu güncelleme (karmaşık)
Değere dayalı
@Composable fun OldTextFieldAddMarkdownEmphasis() { var markdownState by remember { mutableStateOf(TextFieldValue()) } Button(onClick = { // add ** decorations around the current selection, also preserve the selection markdownState = with(markdownState) { copy( text = buildString { append(text.take(selection.min)) append("**") append(text.substring(selection)) append("**") append(text.drop(selection.max)) }, selection = TextRange(selection.min + 2, selection.max + 2) ) } }) { Text("Bold") } TextField( value = markdownState, onValueChange = { markdownState = it }, maxLines = 10 ) }
Duruma dayalı
@Composable fun NewTextFieldAddMarkdownEmphasis() { val markdownState = rememberTextFieldState() LaunchedEffect(Unit) { // add ** decorations around the current selection markdownState.edit { insert(originalSelection.max, "**") insert(originalSelection.min, "**") selection = TextRange(originalSelection.min + 2, originalSelection.max + 2) } } TextField( state = markdownState, lineLimits = TextFieldLineLimits.MultiLine(1, 10) ) }
Bu kullanım alanında, imlecin veya geçerli seçimin etrafındaki metni kalın yapmak için Markdown süslemeleri ekleyen bir düğme bulunur. Ayrıca, değişikliklerden sonra seçim konumunu korur.
- Değer geri çağırma döngüsünü
rememberTextFieldState()
ile değiştirin. maxLines = 10
yerinelineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 10)
yazın.TextFieldState.edit
çağrısıyla yeni birTextFieldValue
hesaplama mantığını değiştirin.- Mevcut metin, mevcut seçime göre birleştirilerek ve Markdown süslemeleri arasına yerleştirilerek yeni bir
TextFieldValue
oluşturulur. - Ayrıca seçim, metnin yeni dizine göre ayarlanır.
TextFieldState.edit
,TextFieldBuffer
kullanarak mevcut durumu düzenlemenin daha doğal bir yoluna sahiptir.- Seçim, süslemelerin nereye ekleneceğini açıkça tanımlar.
- Ardından,
onValueChange
yaklaşımına benzer şekilde seçimi ayarlayın.
- Mevcut metin, mevcut seçime göre birleştirilerek ve Markdown süslemeleri arasına yerleştirilerek yeni bir
ViewModel StateFlow
mimarisi
Birçok uygulama, tüm bilgileri taşıyan tek bir değişmez sınıf aracılığıyla bir ekranın veya bileşenin kullanıcı arayüzü durumunu tanımlamak için StateFlow
kullanmayı teşvik eden modern uygulama geliştirme yönergelerine uyar.
Bu tür uygulamalarda, metin girişi içeren bir giriş ekranı gibi formlar genellikle aşağıdaki gibi tasarlanır:
class LoginViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> get() = _uiState.asStateFlow() fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } } data class UiState( val username: String = "", val password: String = "" ) @Composable fun LoginForm( loginViewModel: LoginViewModel, modifier: Modifier = Modifier ) { val uiState by loginViewModel.uiState.collectAsStateWithLifecycle() Column(modifier) { TextField( value = uiState.username, onValueChange = { loginViewModel.updateUsername(it) } ) TextField( value = uiState.password, onValueChange = { loginViewModel.updatePassword(it) }, visualTransformation = PasswordVisualTransformation() ) } }
Bu tasarım, value,
onValueChange
durum kaldırma paradigmasını kullanan TextFields
ile mükemmel bir uyum sağlar. Ancak metin girişi söz konusu olduğunda bu yaklaşımın öngörülemeyen dezavantajları vardır. Bu yaklaşımla ilgili derin senkronizasyon sorunları, Oluşturma bölümündeki TextField için etkili durum yönetimi blog yayınında ayrıntılı olarak açıklanmıştır.
Sorun, yeni TextFieldState
tasarımının StateFlow
destekli ViewModel kullanıcı arayüzü durumuyla doğrudan uyumlu olmamasıdır. TextFieldState
doğal olarak değişken bir veri yapısı olduğundan username: String
ve password: String
'yi username: TextFieldState
ve password: TextFieldState
ile değiştirmek garip görünebilir.
Genel bir öneri, kullanıcı arayüzü bağımlılıkları ViewModel
içine yerleştirmekten kaçınmaktır.
Bu genellikle iyi bir uygulama olsa da bazen yanlış yorumlanabilir.
Bu durum özellikle, tamamen veri yapısı olan ve TextFieldState
gibi herhangi bir kullanıcı arayüzü öğesi içermeyen Compose bağımlılıkları için geçerlidir.
MutableState
veya TextFieldState
gibi sınıflar, Compose'un anlık görüntü durumu sistemi tarafından desteklenen basit durum tutucularıdır. Bunlar, StateFlow
veya RxJava
gibi bağımlılıklardan farklı değildir. Bu nedenle,kodunuzda "ViewModel'de kullanıcı arayüzü bağımlılığı yok" ilkesini nasıl uyguladığınızı yeniden değerlendirmenizi öneririz. ViewModel
içinde TextFieldState
referansı bulundurmak kötü bir uygulama değildir.
Önerilen basit yaklaşım
UiState
kaynağından username
veya password
gibi değerleri ayıklayıp ViewModel
kaynağında bunlar için ayrı bir referans tutmanızı öneririz.
class LoginViewModel : ViewModel() { val usernameState = TextFieldState() val passwordState = TextFieldState() } @Composable fun LoginForm( loginViewModel: LoginViewModel, modifier: Modifier = Modifier ) { Column(modifier) { TextField(state = loginViewModel.usernameState,) SecureTextField(state = loginViewModel.passwordState) } }
MutableStateFlow<UiState>
değerini birkaçTextFieldState
değeriyle değiştirin.- Bu
TextFieldState
nesneleriniLoginForm
bileşenindeTextFields
'a iletin.
Uyumlu yaklaşım
Bu tür mimari değişiklikler her zaman kolay değildir. Bu değişiklikleri yapma özgürlüğünüz olmayabilir veya yeni TextField
'leri kullanmanın avantajları, harcayacağınız zamana kıyasla daha az olabilir. Bu durumda, küçük bir değişiklikle duruma dayalı metin alanlarını kullanmaya devam edebilirsiniz.
class LoginViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> get() = _uiState.asStateFlow() fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } } data class UiState( val username: String = "", val password: String = "" ) @Composable fun LoginForm( loginViewModel: LoginViewModel, modifier: Modifier = Modifier ) { val initialUiState = remember(loginViewModel) { loginViewModel.uiState.value } Column(modifier) { val usernameState = rememberTextFieldState(initialUiState.username) LaunchedEffect(usernameState) { snapshotFlow { usernameState.text.toString() }.collectLatest { loginViewModel.updateUsername(it) } } TextField(usernameState) val passwordState = rememberTextFieldState(initialUiState.password) LaunchedEffect(usernameState) { snapshotFlow { usernameState.text.toString() }.collectLatest { loginViewModel.updatePassword(it) } } SecureTextField(passwordState) } }
ViewModel
veUiState
sınıflarınızı aynı tutun.- Durumu doğrudan
ViewModel
'e gönderipTextFields
için doğruluk kaynağı haline getirmek yerineViewModel
'ü basit bir veri tutucusuna dönüştürün.- Bunu yapmak için bir
LaunchedEffect
içindesnapshotFlow
toplayarak herTextFieldState.text
'teki değişiklikleri gözlemleyin.
- Bunu yapmak için bir
ViewModel
, kullanıcı arayüzünden gelen en son değerlere sahip olmaya devam eder ancakuiState: StateFlow<UiState>
,TextField
değerlerini etkilemez.ViewModel
uygulamanızda uygulanan diğer tüm kalıcılık mantıkları aynı kalabilir.