این صفحه نمونه هایی از نحوه انتقال TextField
مبتنی بر ارزش به TextField
مبتنی بر حالت ارائه می دهد. صفحه پیکربندی فیلدهای متنی را برای اطلاعات در مورد تفاوت بین مقدار و TextField
مبتنی بر حالت مشاهده کنید.
استفاده اساسی
مبتنی بر ارزش
@Composable fun OldSimpleTextField() { var state by rememberSaveable { mutableStateOf("") } TextField( value = state, onValueChange = { state = it }, singleLine = true, ) }
مبتنی بر دولت
@Composable fun NewSimpleTextField() { TextField( state = rememberTextFieldState(), lineLimits = TextFieldLineLimits.SingleLine ) }
-
value, onValueChange
جایگزین کنید وremember { mutableStateOf("")
} را باrememberTextFieldState()
به خاطر بسپارید. -
singleLine = true
باlineLimits = TextFieldLineLimits.SingleLine
جایگزین کنید.
فیلتر کردن از طریق onValueChange
مبتنی بر ارزش
@Composable fun OldNoLeadingZeroes() { var input by rememberSaveable { mutableStateOf("") } TextField( value = input, onValueChange = { newText -> input = newText.trimStart { it == '0' } } ) }
مبتنی بر دولت
@Preview @Composable fun NewNoLeadingZeros() { TextField( state = rememberTextFieldState(), inputTransformation = InputTransformation { while (length > 0 && charAt(0) == '0') delete(0, 1) } ) }
- حلقه برگشتی مقدار را با
rememberTextFieldState()
جایگزین کنید. - با استفاده از
InputTransformation
منطق فیلتر را درonValueChange
دوباره پیاده سازی کنید. - از
TextFieldBuffer
از دامنه گیرندهInputTransformation
برای به روز رسانیstate
استفاده کنید.-
InputTransformation
دقیقاً پس از شناسایی ورودی کاربر نامیده می شود. - تغییراتی که توسط
InputTransformation
از طریقTextFieldBuffer
پیشنهاد میشوند، بلافاصله اعمال میشوند و از مشکل همگامسازی بین صفحهکلید نرمافزار وTextField
جلوگیری میشود.
-
فرمت کننده کارت اعتباری TextField
مبتنی بر ارزش
@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 } } ) } ) }
مبتنی بر دولت
@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, "-") }, ) }
- فیلتر کردن در
onValueChange
را با یکInputTransformation
جایگزین کنید تا حداکثر طول ورودی را تنظیم کنید.- به بخش Filtering through
onValueChange
مراجعه کنید.
- به بخش Filtering through
- برای افزودن خط تیره،
VisualTransformation
باOutputTransformation
جایگزین کنید.- با
VisualTransformation
، شما مسئول ایجاد متن جدید با خط تیره و همچنین محاسبه نحوه ترسیم شاخص ها بین متن بصری و حالت پشتیبان هستید. -
OutputTransformation
به طور خودکار از نگاشت افست مراقبت می کند. فقط باید با استفاده ازTextFieldBuffer
از محدوده گیرندهOutputTransformation.transformOutput
خط تیره ها را در مکان های صحیح اضافه کنید.
- با
به روز رسانی حالت (ساده)
مبتنی بر ارزش
@Composable fun OldTextFieldStateUpdate(userRepository: UserRepository) { var username by remember { mutableStateOf("") } LaunchedEffect(Unit) { username = userRepository.fetchUsername() } TextField( value = username, onValueChange = { username = it } ) }
مبتنی بر دولت
@Composable fun NewTextFieldStateUpdate(userRepository: UserRepository) { val usernameState = rememberTextFieldState() LaunchedEffect(Unit) { usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) } TextField(state = usernameState) }
- حلقه برگشتی مقدار را با
rememberTextFieldState()
جایگزین کنید. - تخصیص مقدار را با
TextFieldState.setTextAndPlaceCursorAtEnd
تغییر دهید.
به روز رسانی حالت (مختلط)
مبتنی بر ارزش
@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 ) }
مبتنی بر دولت
@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) ) }
در این مورد، یک دکمه تزئینات Markdown را اضافه می کند تا متن در اطراف مکان نما یا انتخاب فعلی پررنگ شود. همچنین موقعیت انتخاب را پس از تغییرات حفظ می کند.
- حلقه برگشتی مقدار را با
rememberTextFieldState()
جایگزین کنید. -
maxLines = 10
باlineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 10)
جایگزین کنید. - منطق محاسبه
TextFieldValue
جدید را با تماسTextFieldState.edit
تغییر دهید.- یک
TextFieldValue
جدید با اتصال متن موجود بر اساس انتخاب فعلی، و درج تزئینات Markdown در بین آنها ایجاد می شود. - همچنین انتخاب با توجه به شاخص های جدید متن تنظیم می شود.
-
TextFieldState.edit
راه طبیعی تری برای ویرایش وضعیت فعلی با استفاده ازTextFieldBuffer
دارد. - انتخاب به صراحت مشخص می کند که کجا تزئینات را وارد کنید.
- سپس، انتخاب را مانند رویکرد
onValueChange
تنظیم کنید.
- یک
معماری ViewModel StateFlow
بسیاری از برنامهها از دستورالعملهای توسعه برنامه مدرن پیروی میکنند، که استفاده از StateFlow
را برای تعریف وضعیت رابط کاربری یک صفحه یا یک جزء از طریق یک کلاس تغییرناپذیر که همه اطلاعات را حمل میکند، ترویج میکند.
در این نوع برنامه ها معمولاً فرمی مانند صفحه ورود با ورودی متن به صورت زیر طراحی می شود:
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() ) } }
این طرح کاملاً با TextFields
که از value, onValueChange
استفاده می کنند، مطابقت دارد. با این حال، وقتی صحبت از ورودی متن به میان می آید، این رویکرد دارای معایب غیرقابل پیش بینی است. مسائل همگام سازی عمیق با این رویکرد به طور مفصل در مدیریت حالت موثر برای TextField در پست وبلاگ نوشتن توضیح داده شده است.
مشکل این است که طراحی جدید TextFieldState
مستقیماً با حالت رابط کاربری ViewModel با پشتیبانی StateFlow
سازگار نیست. جایگزین کردن username: String
و password: String
با username: TextFieldState
و password: TextFieldState
، زیرا TextFieldState
یک ساختار داده ذاتا قابل تغییر است.
یک توصیه رایج این است که از قرار دادن وابستگی های رابط کاربری در ViewModel
خودداری کنید. اگرچه این به طور کلی یک عمل خوب است، اما گاهی اوقات ممکن است سوء تعبیر شود. این امر به ویژه برای وابستگی های Compose که صرفاً ساختارهای داده ای هستند و هیچ عنصر رابط کاربری را با خود حمل نمی کنند، مانند TextFieldState
صادق است.
کلاس هایی مانند MutableState
یا TextFieldState
دارای حالت ساده ای هستند که توسط سیستم حالت Snapshot Compose پشتیبانی می شوند. آنها هیچ تفاوتی با وابستگی هایی مانند StateFlow
یا RxJava
ندارند. بنابراین، ما شما را تشویق میکنیم که نحوه اعمال اصل "بدون وابستگی رابط کاربری در ViewModel" را در کد خود دوباره ارزیابی کنید. نگه داشتن ارجاع به TextFieldState
در ViewModel
خود یک عمل ذاتی بد نیست.
روش ساده توصیه شده
توصیه می کنیم مقادیری مانند username
یا password
را از UiState
استخراج کنید و یک مرجع جداگانه برای آنها در ViewModel
نگه دارید.
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>
با چند مقدارTextFieldState
جایگزین کنید. - آن اشیاء
TextFieldState
را بهTextFields
درLoginForm
composable ارسال کنید.
رویکرد انطباق
این نوع تغییرات معماری همیشه آسان نیست. ممکن است شما آزادی انجام این تغییرات را نداشته باشید، یا سرمایه گذاری در زمان ممکن است بیشتر از مزایای استفاده از TextField
جدید باشد. در این حالت، همچنان می توانید با کمی تغییر از فیلدهای متنی مبتنی بر حالت استفاده کنید.
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
وUiState
خود را یکسان نگه دارید. - به جای اینکه حالت را مستقیماً در
ViewModel
قرار دهید و آن را به منبع حقیقت برایTextFields
تبدیل کنید،ViewModel
به یک دارنده داده ساده تبدیل کنید.- برای انجام این کار، تغییرات هر
TextFieldState.text
را با جمع آوری یکsnapshotFlow
درLaunchedEffect
مشاهده کنید.
- برای انجام این کار، تغییرات هر
-
ViewModel
شما همچنان آخرین مقادیر را از UI خواهد داشت، اماuiState: StateFlow<UiState>
باعث ایجادTextField
s نخواهد شد. - هر منطق تداوم دیگری که در
ViewModel
شما پیادهسازی شده باشد میتواند ثابت بماند.