Bu sayfada, değere dayalı TextField'leri eyalete dayalı TextField'lere nasıl taşıyabileceğinizle ilgili örnekler verilmiştir. Değere ve duruma dayalı TextField 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 bağlı
@Composable fun NewSimpleTextField() { TextField( state = rememberTextFieldState(), lineLimits = TextFieldLineLimits.SingleLine ) }
value, onValueChangeveremember { mutableStateOf("")değerlerinirememberTextFieldState()ile değiştirin.singleLine = trueyerinelineLimits = TextFieldLineLimits.SingleLinekoyun.
onValueChange ile filtreleme
Değere dayalı
@Composable fun OldNoLeadingZeroes() { var input by rememberSaveable { mutableStateOf("") } TextField( value = input, onValueChange = { newText -> input = newText.trimStart { it == '0' } } ) }
Eyalet bazlı
@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. InputTransformationkullanarakonValueChangeiçinde filtreleme mantığını yeniden uygulayın.statedeğerini güncellemek içinInputTransformationalıcı kapsamındakiTextFieldBufferdeğerini kullanın.InputTransformation, kullanıcı girişi algılandıktan hemen sonra çağrılır.InputTransformationtarafındanTextFieldBufferaracılığıyla önerilen değişiklikler hemen uygulanır. Böylece yazılım klavyesi ileTextFieldarası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 } } ) } ) }
Eyalet bazlı
@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
onValueChangeiçindeki filtrelemeyiInputTransformationile değiştirin.onValueChangeile filtreleme bölümüne bakın.
- Tire eklemek için
VisualTransformationyerineOutputTransformationyazın.VisualTransformationile hem tireli yeni metni oluşturmaktan hem de dizinlerin görsel metin ile destekleyici durum arasında nasıl eşlendiğini hesaplamaktan siz sorumlu olursunuz.OutputTransformation, ofset eşlemeyi otomatik olarak gerçekleştirir.OutputTransformation.transformOutputalıcı kapsamındakiTextFieldBufferöğesini 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 } ) }
Eyalet bazlı
@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.setTextAndPlaceCursorAtEndile 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 ) }
Eyalet bazlı
@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 mevcut seçimin etrafındaki metni kalın yapmak için Markdown süslemeleri ekleyen bir düğme kullanılır. 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 = 10yerinelineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 10)koyun.TextFieldState.editçağrısıyla yeni birTextFieldValuehesaplama mantığını değiştirin.- Mevcut metin, geçerli seçime göre birleştirilerek ve araya Markdown süslemeleri eklenerek yeni bir
TextFieldValueoluşturulur. - Ayrıca seçim, metnin yeni dizinlerine göre ayarlanır.
TextFieldState.edit,TextFieldBufferkullanarak mevcut durumu daha doğal bir şekilde düzenler.- Seçim, süslemelerin nereye ekleneceğini açıkça tanımlar.
- Ardından,
onValueChangeyaklaşımına benzer şekilde seçimi ayarlayın.
- Mevcut metin, geçerli seçime göre birleştirilerek ve araya Markdown süslemeleri eklenerek yeni bir
ViewModel StateFlow mimarisi
Birçok uygulama, tüm bilgileri taşıyan tek bir sabit 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 eden Modern uygulama geliştirme yönergelerini izler.
Bu tür uygulamalarda, metin girişi içeren giriş ekranı gibi bir form 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 state hoisting 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 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ğası gereği değiştirilebilir bir veri yapısı olduğundan username: String ve password: String yerine username: TextFieldState ve password: TextFieldState kullanmak garip görünebilir.
Kullanıcı arayüzü bağımlılıklarını ViewModel içine yerleştirmekten kaçınmanız önerilir.
Bu genellikle iyi bir uygulama olsa da bazen yanlış yorumlanabilir.
Bu durum özellikle tamamen 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 anlık görüntü durum sistemi tarafından desteklenen basit durum bilgisi depolayıcılardır. StateFlow veya RxJava gibi bağımlılıklardan farkları yoktur. Bu nedenle, "ViewModel'de kullanıcı arayüzü bağımlılığı olmaması" ilkesini kodunuzda nasıl uyguladığınızı yeniden değerlendirmenizi öneririz. ViewModel içinde bir TextFieldState referansı tutmak, doğası gereği kötü bir uygulama değildir.
Önerilen basit yaklaşım
UiState içinden username veya password gibi değerleri çı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>değerini birkaçTextFieldStatedeğeriyle değiştirin.- Bu
TextFieldStatenesneleriniLoginFormbileşenindekiTextFieldsöğesine 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 zaman yatırımı, yeni TextField'leri kullanmanın avantajlarından daha ağır basabilir. Bu durumda, duruma dayalı metin alanlarını küçük bir değişiklikle kullanabilirsiniz.
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(passwordState) { snapshotFlow { passwordState.text.toString() }.collectLatest { loginViewModel.updatePassword(it) } } SecureTextField(passwordState) } }
ViewModelveUiStatesınıflarınızı aynı tutun.- Durumu doğrudan
ViewModeliçine yerleştiripTextFieldsiçin doğru kaynak haline getirmek yerineViewModelöğesini basit bir veri tutucuya dönüştürün.- Bunun için her bir
TextFieldState.textile ilgili değişiklikleri gözlemlemek üzereLaunchedEffectiçindesnapshotFlowtoplayın.
- Bunun için her bir
ViewModel, kullanıcı arayüzündeki en son değerlere sahip olmaya devam eder ancakuiState: StateFlow<UiState>,TextField'leri yönlendirmez.ViewModeluygulamanızda uygulanan diğer kalıcılık mantıkları aynı kalabilir.