रिच टेक्स्ट इनपुट की मदद से, उपयोगकर्ता अपने टाइप किए गए टेक्स्ट को फ़ॉर्मैट कर सकते हैं. इसमें किसी शब्द को बोल्ड करना, हेडिंग जोड़ना, पैराग्राफ़ को अलाइन करना या हाइलाइट किया गया @mention शामिल करना शामिल है.
Compose में, इसे स्टेट-आधारित टेक्स्ट फ़ील्ड (BasicTextField और TextField) के ऊपर बनाया जाता है. स्टाइल, टेक्स्ट के साथ TextFieldState में मौजूद होती हैं. इसलिए, बदलाव करने, चुने गए टेक्स्ट में बदलाव करने, और फिर से कंपोज़ करने पर भी वे बनी रहती हैं. साथ ही, उपयोगकर्ता के टाइप करने, मिटाने, और चिपकाने पर भी वे सिंक रहती हैं.
इस पेज पर, TextFieldBuffer का इस्तेमाल करके, TextFieldState में स्टाइल लागू करने, अपडेट करने, क्वेरी करने, एक्सट्रैक्ट करने, और हटाने का तरीका बताया गया है. साथ ही, TextFieldState.textStyles से फ़ॉर्मैटिंग टूलबार को चलाने का तरीका भी बताया गया है.
इनलाइन और पैराग्राफ़ स्टाइल लागू करना
TextFieldBuffer के किसी भी रिसीवर स्कोप में, वर्ण-लेवल और पैराग्राफ़-लेवल की फ़ॉर्मैटिंग लागू की जा सकती है. इसमें TextFieldState.edit, InputTransformation.transformInput, और OutputTransformation.transformOutput शामिल हैं.
ExpandPolicy की मदद से, बाउंड्री को बड़ा करने की सुविधा को कंट्रोल करना
addStyle को कॉल करते समय, ExpandPolicy को पास किया जा सकता है. इससे यह तय किया जा सकता है कि जब उपयोगकर्ता सीधे start या end बाउंड्री पर नए वर्ण शामिल करता है, तो स्टाइल की गई रेंज कैसा व्यवहार करती है:
(start, end)के अंदर टेक्स्ट डालना: इससे स्टाइल की गई रेंज हमेशा बड़ी हो जाती है.startसे पहले इंसर्ट करना: इससेstartऔरend, दोनों ऑफ़सेट हमेशा दाईं ओर शिफ़्ट हो जाते हैं.startयाendपर सीधे तौर पर विज्ञापन डालना: इसे आपके तय किए गएExpandPolicyसे कंट्रोल किया जाता है.
नीचे दी गई टेबल में, उपलब्ध ExpandPolicy विकल्पों और उनके इस्तेमाल के सामान्य उदाहरणों के बारे में बताया गया है:
| नीति | बाउंड्री बिहेवियर | इस्तेमाल के सुझाए गए उदाहरण | उदाहरण |
|---|---|---|---|
ExpandPolicy.InsideOnly |
start या end पर टाइप किया गया टेक्स्ट, स्टाइल की गई रेंज से बाहर रहता है. |
@mentions, #hashtags जैसे अलग-अलग टोकन या बैकग्राउंड पिल, जहां टैग के बगल में टाइप करने पर, टैग के बैकग्राउंड का रंग इनहेरिट नहीं होना चाहिए. |
![]() |
ExpandPolicy.AtStart |
start पर टाइप किए गए टेक्स्ट में स्टाइल लागू हो जाता है. वहीं, end पर टाइप किए गए टेक्स्ट में स्टाइल लागू नहीं होता. |
स्टाइल किए गए टेक्स्ट के ठीक सामने टाइप किए गए टेक्स्ट में भी वही फ़ॉर्मैटिंग लागू होनी चाहिए. | ![]() |
ExpandPolicy.AtEnd |
end पर टाइप किए गए टेक्स्ट में स्टाइल लागू हो जाता है. वहीं, start पर टाइप किए गए टेक्स्ट में स्टाइल लागू नहीं होता. |
स्टैंडर्ड रिच टेक्स्ट एडिटर टाइपिंग, जिसमें बोल्ड या इटैलिक शब्द के बाद टाइप करना जारी रखने पर, वह फ़ॉर्मैटिंग जारी रहती है. | ![]() |
ExpandPolicy.AtBoth |
start या end पर टाइप किया गया टेक्स्ट, रेंज को बढ़ाता है और स्टाइल को इनहेरिट करता है. |
ऐक्टिव हाइलाइट या लगातार फ़ॉर्मैट किए गए ब्लॉक, जो किसी भी किनारे से बढ़ने चाहिए. | ![]() |
SpanStyle की मदद से, वर्ण की फ़ॉर्मैटिंग लागू करना
बफ़र में किसी खास TextRange पर, टेक्स्ट का रंग, बैकग्राउंड हाइलाइट, FontWeight, FontStyle या अंडरलाइन जैसी वर्ण-लेवल की फ़ॉर्मैटिंग लागू करने के लिए, SpanStyle के साथ addStyle का इस्तेमाल करें.
SpanStyle रेंज के लिए मुख्य व्यवहार
- लेयरिंग का क्रम: स्टाइल उसी क्रम में लागू होती हैं जिस क्रम में उन्हें बफ़र में जोड़ा जाता है. अगर ओवरलैप होने वाली कई
SpanStyleरेंज, एक ही प्रॉपर्टी को अलग-अलग वैल्यू के साथ सेट करती हैं, तो बाद में जोड़ी गई स्टाइल सबसे ऊपर रेंडर होती है. - खाली न होने वाली रेंज की ज़रूरी शर्त: आपको ऐसी रेंज पास करनी होगी जो छोटा न की गई हो, जहां
start<end.addStyleको0लंबाई के छोटे किए गए सिलेक्शन पर कॉल करने से कोई असर नहीं पड़ता. साथ ही, उलटे या सीमा से बाहर के इंडेक्स पास करने परIllegalArgumentExceptionदिखता है.
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 की मदद से, ब्लॉक फ़ॉर्मैटिंग लागू करना
addStyle का इस्तेमाल ParagraphStyle के साथ करें, ताकि पैराग्राफ़ की रेंज में ब्लॉक-लेवल के स्ट्रक्चरल फ़ॉर्मैटिंग को लागू किया जा सके. जैसे, textAlign, lineHeight, textDirection या textIndent.
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 का इस्तेमाल करके, पैराग्राफ़ के अलाइनमेंट को शुरुआत, बीच, और आखिर में टॉगल करना.ParagraphStyle का इस्तेमाल करते समय होने वाली आम गलतियों से बचना
ParagraphStyle, टेक्स्ट के पूरे ब्लॉक पर लागू होता है. इसलिए, इन मामलों में इसका इस्तेमाल करने से बचें:
- आस-पास के पैराग्राफ़ में टेक्स्ट जोड़ना:
ExpandPolicyको ध्यान से सेट करें, ताकि उपयोगकर्ता के बदलाव करने के बाद, आस-पास कीParagraphStyleरेंज ओवरलैप न हों. उदाहरण के लिए, अगर[0, 5),ExpandPolicy.AtEndका इस्तेमाल करता है और[5, 10),ExpandPolicy.AtStartका इस्तेमाल करता है, तो इंडेक्स5पर टेक्स्ट डालने से दोनों स्टाइल ओवरलैप हो जाती हैं औरIllegalArgumentExceptionदिखता है. ParagraphStyleरेंज को नेस्ट करना: हालांकि,AnnotatedStringमें पूरी तरह से नेस्ट की गई रेंज (जैसे कि[0, 10)और[2, 8)) मान्य हैं, लेकिन टेक्स्ट फ़ील्ड मेंParagraphStyleरेंज को नेस्ट करने से बचें. Layout, नेस्ट की गई रेंज को अलग-अलग पैराग्राफ़ में बांट देता है. इसके बाद, किए गए बदलावों की वजह से, नेस्ट की गई रेंज अनजाने में कुछ हद तक ओवरलैप हो सकती हैं.
TrackedRange की मदद से, किसी स्टाइल को अपडेट करना
TextRange और ExpandPolicy के साथ addStyle को कॉल करने पर, TrackedRange हैंडल मिलता है. यह TrackedRange<SpanStyle> या TrackedRange<ParagraphStyle> हो सकता है. TrackedRange, TextFieldBuffer में स्टाइल इंटरवल का डाइनैमिक रेफ़रंस होता है. यह टेक्स्ट को शामिल करने, मिटाने या बदलने पर, अपने start और end वर्ण ऑफ़सेट को अपने-आप अपडेट करता है.
मौजूदा जगह पर बदलाव करने की क्षमता
किसी मौजूदा स्टाइल को अपडेट करने या उसकी सीमाओं में बदलाव करने के लिए, आपको removeStyle और addStyle को कॉल करने की ज़रूरत नहीं है. TextFieldBuffer स्कोप में, TrackedRange एक्सटेंशन प्रॉपर्टी उपलब्ध कराता है. ये प्रॉपर्टी, इंटरवल को अपडेट करती हैं. साथ ही, अन्य स्टाइल के हिसाब से, लेयरिंग के ओरिजनल ऑर्डर को बनाए रखती हैं:
TrackedRange<SpanStyle>.spanStyle:TrackedRange<SpanStyle>पर मौजूदSpanStyleपेलोड को पढ़ता है या बदलता है.TrackedRange<ParagraphStyle>.paragraphStyle: यहTrackedRange<ParagraphStyle>पर मौजूदParagraphStyleपेलोड को पढ़ता है या उसे बदलता है.TrackedRange<*>.textRange: मौजूदाTextRangeबाउंड को पढ़ता या अपडेट करता है. अगर रेंज को छोटा, उलटा या सीमा से बाहर सेट किया जाता है, तोIllegalArgumentExceptionदिखता है.TrackedRange<*>.expandPolicy: यहExpandPolicyको पढ़ता है या अपडेट करता है.TrackedRange<*>.isValid: अगर स्टाइल इंटरवल अब भी बफ़र में चालू है, तोtrueदिखाता है. अगर बदलावों की वजह से इसकी लंबाई0हो गई है या इसे हटा दिया गया है, तोfalseदिखाता है.
// 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) }
दायरे को सीमित करना और लाइफ़साइकल के नियम
TrackedRange इंस्टेंस के साथ काम करते समय, स्कोप और लाइफ़साइकल के इन नियमों का ध्यान रखें:
TextFieldBufferतक सीमित:TrackedRangeएक्सटेंशन प्रॉपर्टी सिर्फ़ उसTextFieldBufferस्कोप में मौजूद होती हैं जहां हैंडल बनाया गया था या क्वेरी की गई थी.- छोटा करने पर अपने-आप छोटा होना: अगर उपयोगकर्ता के बदलावों से
TrackedRangeमें शामिल सभी वर्ण मिट जाते हैं, तो इसकी लंबाई घटकर0हो जाती है,isValid,falseदिखाता है, और स्टाइल को बफ़र से हटा दिया जाता है.
स्टाइल के बारे में क्वेरी करना और उन्हें हटाना
Compose, ऐक्टिव स्टाइल की जांच करने के दो तरीके उपलब्ध कराता है. यह इस बात पर निर्भर करता है कि यूज़र इंटरफ़ेस (यूआई) के लिए, बफ़र में बदलाव किया जा रहा है या स्टेटस को रीड किया जा रहा है:
TextFieldBufferब्लॉक के अंदर:TextFieldBufferरिसीवर पर सीधेgetSpanStylesऔरgetParagraphStylesको कॉल करें, ताकि म्यूटेबलTrackedRangeहैंडल मिल सकें.- एडिट ब्लॉक के बाहर:
TextFieldState.textStylesको रीड करें. इससे, ऐसीAnnotatedString.Rangeसूचियां मिलती हैं जिनमें बदलाव नहीं किया जा सकता. ये सूचियां, कमिट की गई स्थिति को दिखाती हैं.
TextFieldBuffer में इंटरसेक्ट करने वाली स्टाइल के लिए क्वेरी करना
TextFieldBuffer.getSpanStyles और
TextFieldBuffer.getParagraphStyles, TrackedRange के उन सभी इंस्टेंस को दिखाते हैं जो दिए गए TextRange को इंटरसेक्ट करते हैं. इन्हें बफ़र में जोड़े जाने के क्रम में दिखाया जाता है.
उदाहरण के लिए, उपयोगकर्ता के मौजूदा चुने गए हिस्से को कवर करने वाली स्टाइल की जांच करने या उन्हें अपडेट करने के लिए, state.edit के अंदर getSpanStyles(selection) से क्वेरी की जा सकती है:
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 } // ... }
स्टाइल इंटरवल, सेमी-ओपन मैथमैटिकल रेंज [start, end) का इस्तेमाल करते हैं. टचिंग स्टाइल दिखेगा या नहीं, यह इस बात पर निर्भर करता है कि आपने किसी चुने गए हिस्से के लिए क्वेरी की है या कर्सर के किसी एक पॉइंट के लिए:
- चुने गए ऐसे सेल के लिए जो खाली नहीं हैं
[start, end):- नतीजे में दिखने वाली स्टाइल: चुनी गई रेंज में मौजूद, ओवरलैप होने वाली या उसमें शामिल कोई भी स्टाइल. उदाहरण के लिए, क्वेरी
[5, 15),[0, 20),[8, 12)या[0, 10)से मैच करती है. - नहीं दिखाया जाता: ऐसी स्टाइल जो वर्णों को ओवरलैप किए बिना, सिर्फ़ बाहरी बाउंड्री के किनारों को छूती हैं. उदाहरण के लिए, क्वेरी
[5, 15)में[0, 5)और[15, 25)शामिल नहीं हैं.
- नतीजे में दिखने वाली स्टाइल: चुनी गई रेंज में मौजूद, ओवरलैप होने वाली या उसमें शामिल कोई भी स्टाइल. उदाहरण के लिए, क्वेरी
- छोटे किए गए कर्सर
[index, index]के लिए:- नतीजे में मिली स्टाइल: कर्सर की पोज़िशन पर मौजूद या कर्सर की पोज़िशन से शुरू होने वाली कोई भी स्टाइल. उदाहरण के लिए,
10पर मौजूद कर्सर,[0, 20)और[10, 20)से मैच करता है. - Not returned (touching end rule): कर्सर की पोज़िशन पर खत्म होने वाली कोई भी स्टाइल. उदाहरण के लिए,
10पर मौजूद कर्सर,[0, 10)को शामिल नहीं करता.
- नतीजे में मिली स्टाइल: कर्सर की पोज़िशन पर मौजूद या कर्सर की पोज़िशन से शुरू होने वाली कोई भी स्टाइल. उदाहरण के लिए,
removeStyle की मदद से स्टाइल हटाएं या टॉगल करें
TextFieldBuffer से स्टाइल इंटरवल को मिटाने के लिए, removeStyle को TrackedRange हैंडल पास करें.
removeStyle, true दिखाता है, अगर TrackedRange मिल गया और उसे हटा दिया गया या
false दिखाता है, अगर उसे पहले ही हटा दिया गया था या वह किसी दूसरे बफ़र से जुड़ा था. यह सिर्फ़ टारगेट किए गए TrackedRange हैंडल को हटाता है. साथ ही, ओवरलैप होने वाली अन्य स्टाइल को बरकरार रखता है.
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 ) } } }
रिच टेक्स्ट को एक्सट्रैक्ट या सीरियलाइज़ करना
फ़ॉर्मैट किए गए इनपुट को सिर्फ़ पढ़ने के लिए उपलब्ध Text कंपोज़ेबल में दिखाने या उसे बैकएंड सेवा में सीरियलाइज़ करने के लिए, एडिट ब्लॉक के बाहर TextFieldState.textStyles को पढ़ें.
state.textStyles.getSpanStyles और
state.textStyles.getParagraphStyles, List<AnnotatedString.Range<SpanStyle>> और
List<AnnotatedString.Range<ParagraphStyle>> दिखाते हैं. इन्हें सीधे
AnnotatedString कंस्ट्रक्टर को पास किया जा सकता है:
fun TextFieldState.toAnnotatedString(): AnnotatedString { val fullRange = TextRange(0, text.length) return AnnotatedString( text = text.toString(), spanStyles = textStyles.getSpanStyles(fullRange), paragraphStyles = textStyles.getParagraphStyles(fullRange) ) }
Jetchat में पूरा उदाहरण
इनलाइन वर्ण स्टाइलिंग (बोल्ड, इटैलिक, अंडरलाइन, स्ट्राइकथ्रू, और रंग) को जोड़ने वाले पूरे तरीके को देखने के लिए, GitHub पर Jetchat सैंपल देखें:
RichTextState.kt: इसमेंTextFieldStateस्टाइल क्वेरी, स्पैन टॉगल करना, चुने गए टेक्स्ट की सीमाओं के हिसाब से ट्रिम करना या स्प्लिट करना वगैरह शामिल है.UserInput.kt: यहRichTextStateको रिच टेक्स्ट फ़ॉर्मैटिंग टूलबार और मैसेज इनपुट फ़ील्ड से कनेक्ट करता है.



