Bu sayfada, değere dayalı TextField
'ları duruma dayalı TextField
'lara nasıl taşıyabileceğinize dair örnekler verilmektedir. Değere ve duruma dayalı TextField
'ler arasındaki farklar hakkında bilgi için Metin alanlarını yapılandırma sayfasını inceleyin.
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()
koyun.singleLine = true
yerinelineLimits = TextFieldLineLimits.SingleLine
koyun.
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. onValueChange
içinde filtreleme mantığınıInputTransformation
kullanarak yeniden uygulayın.state
öğesini güncellemek içinInputTransformation
alıcısının kapsamındakiTextFieldBuffer
öğesini kullanın.InputTransformation
, kullanıcı girişi algılandıktan hemen sonra tam olarak çağrılmalıdır.InputTransformation
aracılığıylaTextFieldBuffer
önerilen değişiklikler hemen uygulanır. Böylece, yazılım klavyesi ileTextField
arasında senkronizasyon sorunu yaşanmaz.
Kredi kartı biçimlendiricisi 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.
- Kısa çizgi eklemek için
VisualTransformation
yerineOutputTransformation
yazın.VisualTransformation
ile hem tireli yeni metni oluşturmaktan hem de dizinlerin görsel metin ile destekleyici durum arasında nasıl eşlendiğini hesaplamaktan siz sorumlusunuz.OutputTransformation
, ofset eşlemeyi otomatik olarak gerçekleştirir.OutputTransformation.transformOutput
alıcı kapsamındakiTextFieldBuffer
kullanarak tireleri 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. TextFieldState.setTextAndPlaceCursorAtEnd
ile değer atamasını 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, bir düğme, imlecin etrafındaki veya mevcut seçimdeki metni kalınlaştırmak için Markdown süslemeleri ekler. Ayrıca, değişikliklerden sonra seçim konumunu da korur.
- Değer geri çağırma döngüsünü
rememberTextFieldState()
ile değiştirin. maxLines = 10
yerinelineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 10)
koyun.- Yeni bir
TextFieldValue
değerini hesaplama mantığınıTextFieldState.edit
çağrısıyla değiştirin.- Mevcut metin, geçerli seçime göre birleştirilerek ve araya Markdown süslemeleri eklenerek yeni bir
TextFieldValue
oluşturulur. - Ayrıca seçim, metnin yeni dizinlerine göre ayarlanır.
TextFieldState.edit
,TextFieldBuffer
kullanılarak mevcut durumu düzenlemenin daha doğal bir yolunu sunar.- Seçim, süslemelerin nereye ekleneceğini açıkça tanımlar.
- Ardından,
onValueChange
yaklaşımına benzer şekilde seçimi düzenleyin.
- Mevcut metin, geçerli seçime göre birleştirilerek ve araya Markdown süslemeleri eklenerek yeni bir
ViewModel StateFlow
mimarisi
Birçok uygulama, Modern uygulama geliştirme kurallarına uyar. Bu kurallar, 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
kullanılmasını teşvik eder.
Bu tür uygulamalarda, metin girişi içeren bir giriş ekranı gibi formlar genellikle aşağıdaki şekilde 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 yükseltme paradigmasını kullanan TextFields
ile mükemmel 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ı, Compose'da TextField için etkili durum yönetimi başlıklı blog yayınında ayrıntılı olarak açıklanmaktadır.
Sorun, yeni TextFieldState
tasarımının StateFlow
destekli ViewModel kullanıcı arayüzü durumuyla doğrudan uyumlu olmamasıdır. username: String
ve password: String
öğelerini username: TextFieldState
ve password: TextFieldState
ile değiştirmek garip görünebilir. Bunun nedeni, TextFieldState
öğesinin doğası gereği değiştirilebilir bir veri yapısı olmasıdır.
Kullanıcı arayüzü bağımlılıklarını ViewModel
içine yerleştirmemek yaygın bir öneridir.
Bu genellikle iyi bir uygulama olsa da bazen yanlış yorumlanabilir.
Bu durum, özellikle yalnızca veri yapıları olan ve TextFieldState
gibi kullanıcı arayüzü öğeleri içermeyen Compose bağımlılıkları için geçerlidir.
MutableState
veya TextFieldState
gibi sınıflar, Compose'un Snapshot durum sistemi tarafından desteklenen basit durum tutuculardır. StateFlow
veya RxJava
gibi bağımlılıklardan farklı değildir. Bu nedenle,kodunuzda "ViewModel'de kullanıcı arayüzü bağımlılıkları yok" ilkesini nasıl uyguladığınızı yeniden değerlendirmenizi öneririz. TextFieldState
içinde ViewModel
referansı tutmak kötü bir uygulama değildir.
Önerilen basit yaklaşım
username
veya password
gibi değerleri UiState
öğesinden çıkarmanızı ve ViewModel
içinde 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>
yerine birkaçTextFieldState
değeri girin.- Bu
TextFieldState
nesneleriniLoginForm
composable'ındaTextFields
öğesine iletin.
Uygunluk yaklaşımı
Bu tür mimari değişiklikler her zaman kolay olmayabilir. Bu değişiklikleri yapma özgürlüğünüz olmayabilir veya zaman yatırımı, yeni TextField
kullanmanın faydalarından daha fazla olabilir. Bu durumda, eyalete dayalı metin alanlarını küçük bir değişiklikle 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
içine yerleştiripTextFields
için doğruluk kaynağı yapmak yerineViewModel
öğesini basit bir veri tutucuya dönüştürün.- Bunu yapmak için
TextFieldState.text
değerlerindeki değişiklikleri gözlemleyin. Bunun içinLaunchedEffect
içindesnapshotFlow
toplayın.
- Bunu yapmak için
ViewModel
, kullanıcı arayüzündeki en son değerleri kullanmaya devam eder ancakuiState: StateFlow<UiState>
,TextField
'ları yönlendirmez.ViewModel
içinde uygulanan diğer tüm kalıcılık mantıkları aynı kalabilir.