Zengin metin girişi, kullanıcıların yazdıklarını biçimlendirmesine olanak tanır. Örneğin, bir kelimeyi kalın yapabilir, başlık ekleyebilir, paragrafı hizalayabilir veya vurgulanmış bir @mention ekleyebilirler.
Compose'da bunu, duruma dayalı metin alanlarının (BasicTextField ve TextField) üzerine inşa edersiniz. Stiller, metinle birlikte TextFieldState içinde bulunur. Bu sayede düzenlemeler, seçim değişiklikleri ve yeniden oluşturma işlemlerinden etkilenmez ve kullanıcı yazarken, silerken ve yapıştırırken senkronize kalır.
Bu sayfada, TextFieldBuffer kullanarak TextFieldState içinde stilleri nasıl uygulayacağınız, güncelleyeceğiniz, sorgulayacağınız, ayıklayacağınız ve kaldıracağınız, ayrıca TextFieldState.textStyles üzerinden biçimlendirme araç çubuğunu nasıl kullanacağınız açıklanmaktadır.
Satır içi ve paragraf stilleri uygulama
TextFieldState.edit, InputTransformation.transformInput ve OutputTransformation.transformOutput dahil olmak üzere herhangi bir TextFieldBuffer alıcı kapsamı içinde karakter düzeyinde ve paragraf düzeyinde biçimlendirme uygulayabilirsiniz.
ExpandPolicy ile sınır genişletmeyi kontrol etme
addStyle yöntemini çağırdığınızda, kullanıcı doğrudan start veya end sınırına yeni karakterler eklediğinde stilize edilmiş aralığın nasıl davranacağını tanımlamak için bir ExpandPolicy iletebilirsiniz:
(start, end)içine ekleme: Her zaman stilli aralığı genişletir.startöncesine ekleme: Her zaman hemstarthem deendofsetlerini sağa kaydırır.- Doğrudan
startveyaendkonumuna ekleme: BelirttiğinizExpandPolicytarafından kontrol edilir.
Aşağıdaki tabloda, kullanılabilir ExpandPolicy seçenekleri ve bunların tipik kullanım alanları açıklanmaktadır:
| Politika | Sınır davranışı | Önerilen kullanım alanları | Örnek |
|---|---|---|---|
ExpandPolicy.InsideOnly |
start veya end konumunda yazılan metin, stil uygulanan aralığın dışında kalır. |
@mentions, #hashtags gibi ayrı jetonlar veya etiketin yanına yazarken etiketin arka plan rengini devralmaması gereken arka plan hapları. |
![]() |
ExpandPolicy.AtStart |
start konumunda yazılan metin stili devralır, end konumunda yazılan metin ise stilsiz kalır. |
Stil uygulanmış metnin hemen önüne yazılan metin de aynı biçimlendirmeyi kullanmalıdır. | ![]() |
ExpandPolicy.AtEnd |
end konumunda yazılan metin stili devralır, start konumunda yazılan metin ise stilsiz kalır. |
Standart zengin metin düzenleyici yazma işlemi. Kalın veya italik bir kelimeden sonra yazmaya devam ettiğinizde bu biçimlendirme devam eder. | ![]() |
ExpandPolicy.AtBoth |
start veya end konumunda yazılan metin, aralığı genişletir ve stili devralır. |
Her iki kenardan da büyümesi gereken etkin vurgular veya sürekli biçimlendirilmiş bloklar. | ![]() |
SpanStyle ile karakter biçimlendirmesi uygulama
Arabellekteki belirli bir TextRange öğesine metin rengi, arka plan vurgusu, FontWeight, FontStyle veya altı çizili gibi karakter düzeyinde biçimlendirme uygulamak için SpanStyle ile addStyle kullanın.
SpanStyle aralıkları için temel davranışlar
- Katman sırası: Stiller, arabelleğe eklediğiniz sırayla uygulanır. Çakışan birden fazla
SpanStylearalığı, aynı özelliği farklı değerlerle ayarlarsa daha sonra eklenen stil en üstte oluşturulur. - Boş olmayan aralık koşulu:
start<endolacak şekilde daraltılmamış bir aralık iletmeniz gerekir.0uzunluğundaki daraltılmış bir seçimdeaddStyleçağrıldığında hiçbir etki olmaz ve ters çevrilmiş veya sınırların dışındaki dizinler iletildiğindeIllegalArgumentExceptionhatası oluşur.
TextField( state = rememberTextFieldState(), inputTransformation = InputTransformation { for (i in 0 until changes.changeCount) { val range = changes.getRange(i) if (!range.collapsed) { addStyle( spanStyle = SpanStyle( fontWeight = FontWeight.ExtraBold, fontStyle = FontStyle.Italic, color = Color.Red, ), range = range, expandPolicy = ExpandPolicy.AtEnd ) } } } )
ParagraphStyle ile blok biçimlendirme uygulama
Bir paragraf aralığına textAlign, lineHeight, textDirection veya textIndent gibi blok düzeyinde yapısal biçimlendirme uygulamak için addStyle yöntemini ParagraphStyle ile kullanın.
fun setParagraphAlignment(state: TextFieldState, alignment: TextAlign) { val targetRange = if (state.selection.collapsed) { TextRange(0, state.text.length) } else { state.selection } if (targetRange.collapsed) return state.edit { // 1. Remove existing paragraph styles in this range getParagraphStyles(targetRange).forEach { removeStyle(it) } // 2. Apply the new paragraph alignment addStyle( paragraphStyle = ParagraphStyle(textAlign = alignment), range = targetRange, expandPolicy = ExpandPolicy.InsideOnly ) } }
ParagraphStyle tuşunu kullanarak paragraf hizalamasını başlangıç, orta ve son arasında değiştirme.ParagraphStyle ile ilgili kaçınılması gereken hatalar
ParagraphStyle, metin bloklarının tamamına uygulandığından aşağıdaki senaryolarda kullanmaktan kaçının:
- Bitişik paragraflara genişletme: Kullanıcı düzenlemelerinden sonra bitişik
ParagraphStylearalıklarının çakışmaması içinExpandPolicydeğerini dikkatli bir şekilde ayarlayın. Örneğin,[0, 5)ExpandPolicy.AtEnd'i,[5, 10)iseExpandPolicy.AtStart'yi kullanıyorsa5dizinine metin eklemek her iki stili de genişleterek çakışmaya neden olur veIllegalArgumentExceptionhatası verir. - İç içe yerleştirilmiş
ParagraphStylearalıkları: Tamamen iç içe yerleştirilmiş aralıklar (ör.[0, 10)ve[2, 8))AnnotatedStringiçinde geçerli olsa da metin alanındaParagraphStylearalıklarını iç içe yerleştirmekten kaçının. Düzen, iç içe yerleştirilmiş aralıkları ayrı paragraflara böler ve sonraki düzenlemeler, iç içe yerleştirilmiş aralıkları yanlışlıkla kısmi bir çakışmaya dönüştürebilir.
Bir stili TrackedRange ile yerinde güncelleme
addStyle işlevini TextRange ve ExpandPolicy ile çağırmak, TrackedRange<SpanStyle> veya TrackedRange<ParagraphStyle> olabilen bir TrackedRange herkese açık anahtarı döndürür. TrackedRange, TextFieldBuffer içindeki bir stil aralığına dinamik bir referanstır. Metin eklendikçe, silindikçe veya değiştirildikçe start ve end karakter ofsetlerini otomatik olarak günceller.
Yerinde değiştirilebilirlik
Mevcut bir stili güncellemek veya sınırlarını ayarlamak için removeStyle ve addStyle yöntemlerini çağırmanız gerekmez. TextFieldBuffer kapsamı içinde TrackedRange, aralığı yerinde güncelleyen ve diğer stillere göre orijinal katman sırasını koruyan
uzantı özellikleri sağlar:
TrackedRange<SpanStyle>.spanStyle:TrackedRange<SpanStyle>üzerindekiSpanStyleyükünü okur veya değiştirir.TrackedRange<ParagraphStyle>.paragraphStyle:TrackedRange<ParagraphStyle>üzerindekiParagraphStyleyükünü okur veya değiştirir.TrackedRange<*>.textRange: MevcutTextRangesınırlarını okur veya günceller. Daraltılmış, ters çevrilmiş veya sınırların dışında bir aralık ayarlandığındaIllegalArgumentExceptionhatası döndürülür.TrackedRange<*>.expandPolicy:ExpandPolicydeğerini yerinde okur veya günceller.TrackedRange<*>.isValid: Stil aralığı arabellekte hâlâ etkinsetrue, düzenlemeler nedeniyle uzunluğu0değerine düşürüldüyse veya kaldırıldıysafalsedeğerini döndürür.
// Trim an existing bold span in place when the selection overlaps one edge val range = style.textRange if (range.start < selection.min && range.end <= selection.max) { // Selection covers the end of the span - shrink end boundary in place style.textRange = TextRange(range.start, selection.min) } else if (range.start >= selection.min && range.end > selection.max) { // Selection covers the start of the span - shrink start boundary in place style.textRange = TextRange(selection.max, range.end) }
Kapsam sınırlaması ve yaşam döngüsü kuralları
TrackedRange örnekleriyle çalışırken aşağıdaki kapsam ve yaşam döngüsü kurallarını göz önünde bulundurun:
TextFieldBufferile sınırlı:TrackedRangeuzantı mülkleri yalnızca, herkese açık kullanıcı adının oluşturulduğu veya sorgulandığıTextFieldBufferkapsamında bulunur.- Daraltma sırasında otomatik budama: Kullanıcı düzenlemeleri, bir
TrackedRangetarafından kapsanan tüm karakterleri silerse uzunluğu0değerine küçülür,isValidfalsedeğerini döndürür ve stil, arabellekten kaldırılır.
Stilleri sorgulama ve kaldırma
Compose, kullanıcı arayüzünüz için arabelleği değiştirmenize veya durumu okumanıza bağlı olarak etkin stilleri incelemenin iki yolunu sunar:
TextFieldBufferbloğunun içinde: DeğiştirilebilirTrackedRangetutamaçları elde etmek için doğrudanTextFieldBufferalıcısındagetSpanStylesvegetParagraphStylesyöntemlerini çağırın.- Düzenleme bloğunun dışında: Onaylanan durumu yansıtan, sabit
AnnotatedString.Rangelisteleri döndürenTextFieldState.textStyles'yi okuyun.
TextFieldBuffer içinde kesişen stilleri sorgulama
TextFieldBuffer.getSpanStyles ve
TextFieldBuffer.getParagraphStyles, belirtilen TextRange ile kesişen tüm TrackedRange örneklerini,
tampona eklenme zamanlarına göre sıralanmış olarak döndürür.
Örneğin, kullanıcının mevcut seçimini kapsayan stilleri incelemek veya güncellemek için state.edit içinde getSpanStyles(selection) sorgusu yapabilirsiniz:
state.edit { if (selection.collapsed) return@edit // Query all bold spans that overlap with the current selection val boldStyles: List<TrackedRange<SpanStyle>> = getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold } // ... }
Stil aralıkları, yarı açık bir matematiksel aralık [start, end) kullanır. Dokunma stilinin döndürülüp döndürülmeyeceği, bir seçimi mi yoksa tek bir imleç noktasını mı sorguladığınıza bağlıdır:
- Boş olmayan bir seçim için
[start, end):- Döndürülenler: Seçim aralığıyla çakışan, seçim aralığını içeren veya seçim aralığının içinde yer alan tüm stiller. Örneğin,
[5, 15)sorgusu[0, 20),[8, 12)veya[0, 10)ile eşleşir. - Döndürülmez: Karakterlerle çakışmadan yalnızca dış sınır kenarlarına dokunan stiller. Örneğin,
[5, 15)sorgusu[0, 5)ve[15, 25)sorgularını hariç tutar.
- Döndürülenler: Seçim aralığıyla çakışan, seçim aralığını içeren veya seçim aralığının içinde yer alan tüm stiller. Örneğin,
- Daraltılmış imleç için
[index, index]:- Döndürülen: İmleci içeren veya imleç konumunda başlayan tüm stiller. Örneğin,
10değerindeki bir imleç[0, 20)ve[10, 20)ile eşleşir. - Döndürülmedi (bitiş kuralına dokunuyor): İmleç konumunda biten tüm stiller. Örneğin,
10değerindeki bir imleç[0, 10)değerini hariç tutar.
- Döndürülen: İmleci içeren veya imleç konumunda başlayan tüm stiller. Örneğin,
removeStyle ile stilleri kaldırın veya açıp kapatın
TextFieldBuffer öğesinden belirli bir stil aralığını silmek için removeStyle işlevine bir TrackedRange tutamacı iletin.
removeStyle, TrackedRange bulunup kaldırıldıysa true, zaten kaldırılmışsa veya farklı bir arabelleğe aitse false değerini döndürür. Yalnızca hedeflenen TrackedRange tutamacını kaldırır ve diğer çakışan stilleri olduğu gibi bırakır.
fun toggleBold(state: TextFieldState) { state.edit { if (selection.collapsed) return@edit // Query all bold spans that overlap with the current selection val boldStyles: List<TrackedRange<SpanStyle>> = getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold } if (boldStyles.isNotEmpty()) { // Remove or trim existing bold spans covering the selection for (style in boldStyles) { val range = style.textRange if (range.start >= selection.min && range.end <= selection.max) { removeStyle(style) } else if (range.start < selection.min && range.end > selection.max) { val oldEnd = range.end style.textRange = TextRange(range.start, selection.min) addStyle( spanStyle = SpanStyle(fontWeight = FontWeight.Bold), range = TextRange(selection.max, oldEnd), expandPolicy = ExpandPolicy.AtEnd ) } else if (range.start < selection.min) { style.textRange = TextRange(range.start, selection.min) } else { style.textRange = TextRange(selection.max, range.end) } } } else { // No bold spans intersect the selection - apply bold addStyle( spanStyle = SpanStyle(fontWeight = FontWeight.Bold), range = selection, expandPolicy = ExpandPolicy.AtEnd ) } } }
Zengin metni ayıklama veya serileştirme
Biçimlendirilmiş girişi salt okunur bir Text composable'da görüntülemek veya bir arka uç hizmetine serileştirmek için düzenleme bloğunun dışında TextFieldState.textStyles değerini okuyun.
state.textStyles.getSpanStyles ve
state.textStyles.getParagraphStyles, doğrudan AnnotatedString oluşturucuya iletebileceğiniz List<AnnotatedString.Range<SpanStyle>> ve List<AnnotatedString.Range<ParagraphStyle>> değerlerini döndürür:
fun TextFieldState.toAnnotatedString(): AnnotatedString { val fullRange = TextRange(0, text.length) return AnnotatedString( text = text.toString(), spanStyles = textStyles.getSpanStyles(fullRange), paragraphStyles = textStyles.getParagraphStyles(fullRange) ) }
Jetchat'teki eksiksiz örnek
Satır içi karakter stilini (kalın, italik, altı çizili, üstü çizili ve renkli) birleştiren eksiksiz bir uygulama için GitHub'daki Jetchat örneğine bakın:
RichTextState.kt:TextFieldStatestil sorgularını, span açma/kapatma, kırpma veya seçim sınırları boyunca bölme ve daha fazlasını kapsar.UserInput.kt:RichTextStateuygulamasını zengin metin biçimlendirme araç çubuğuna ve mesaj giriş alanına bağlar.



