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 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, onValueChangeveremember { mutableStateOf("")} yerinerememberTextFieldState()koyun.singleLine = trueyerinelineLimits = TextFieldLineLimits.SingleLinekoyun.
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. onValueChangeiçinde filtreleme mantığınıInputTransformationkullanarak yeniden uygulayın.stateöğesini güncellemek içinInputTransformationalı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.InputTransformationaracılığıylaTextFieldBufferönerilen değişiklikler hemen uygulanır. Böylece, yazılım klavyesi ileTextFieldarası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
onValueChangeiçindeki filtrelemeyiInputTransformationile değiştirin.onValueChangeile filtreleme bölümüne bakın.
- Kısa çizgi 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 sorumlusunuz.OutputTransformation, ofset eşlemeyi otomatik olarak gerçekleştirir.OutputTransformation.transformOutputalıcı kapsamındakiTextFieldBufferkullanarak 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.setTextAndPlaceCursorAtEndile 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 korur.
- Değer geri çağırma döngüsünü
rememberTextFieldState()ile değiştirin. maxLines = 10yerinelineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 10)koyun.- Yeni bir
TextFieldValuedeğ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
TextFieldValueoluşturulur. - Ayrıca seçim, metnin yeni dizinlerine göre ayarlanır.
TextFieldState.edit,TextFieldBufferkullanı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,
onValueChangeyaklaşı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 bu yaklaşımın metin girişiyle ilgili olarak ö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'nin username: TextFieldState ve password: TextFieldState ile değiştirilmesi, TextFieldState doğası gereği değiştirilebilir bir veri yapısı olduğundan garip görünebilir.
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 öğesine referans tutmak doğası gereği 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çTextFieldStatedeğeri girin.- Bu
TextFieldStatenesneleriniLoginFormcomposable'ı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) } }
ViewModelveUiStatesınıflarınızı aynı tutun.- Durumu doğrudan
ViewModeliçine yerleştiripTextFieldsiçin doğruluk kaynağı yapmak yerineViewModelöğesini basit bir veri tutucuya dönüştürün.- Bunu yapmak için
TextFieldState.textdeğişikliklerini gözlemleyin. Bunun içinLaunchedEffectiçindesnapshotFlowtoplayın.
- Bunu yapmak için
ViewModel, kullanıcı arayüzündeki en son değerleri kullanmaya devam eder ancakuiState: StateFlow<UiState>,TextField'leri yönlendirmez.ViewModeliçinde uygulanan diğer tüm kalıcılık mantığı aynı kalabilir.