An all-in-one Jetpack Compose component to handle text styling inside TextFields

Overview

Jetpack Compose Rich Text Editor

I've been looking for a library that is able to deliver an editable component which can render rich text in real time. The main issue with libraries I found was that they were using WebView and Javascript under the hood. I wanted something compatible with Jetpack Compose.

So the only solution was to create my own library.

Installation

  1. Add a link to the Jitpack repository
repositories {
    maven { url 'https://jitpack.io' }
}
  1. Include link do the library (change the version to the current one)
implementation "com.github.pChochura:richtext-compose:$version"

Usage

Insert function call to a Composable:

var value by remember { mutableStateOf(RichTextValue.get()) }

RichTextEditor(
    modifier = Modifier,
    value = value,
    onValueChange = { value = it },
    textFieldStyle = defaultRichTextFieldStyle().copy(
        placeholder = "My rich text editor in action",
        textColor = MaterialTheme.colors.onPrimary,
        placeholderColor = MaterialTheme.colors.secondaryVariant,
    )
)

// If you want to render static text use `RichText` instead
RichText(
   modifier = Modifier,
   value = value,
   textStyle = defaultRichTextStyle().copy(
      textColor = MaterialTheme.colors.onPrimary,
   )
)

You can easily stylize the TextField that is under the hood by providing values for the textFieldStyle. Default ones are as follows:

@Composable
fun defaultRichTextFieldStyle() = RichTextFieldStyle(
     keyboardOptions = KeyboardOptions(
         capitalization = KeyboardCapitalization.Sentences,
     ),
     placeholder = EMPTY_STRING,
     textStyle = MaterialTheme.typography.body1,
     textColor = MaterialTheme.colors.onPrimary,
     placeholderColor = MaterialTheme.colors.secondaryVariant,
     cursorColor = MaterialTheme.colors.secondary,
 )

To insert or clear styles you can use methods provided by the RichTextValue object:

abstract class RichTextValue {
    /**
     * Returns styles that are used inside the current selection (or composition)
     */
    abstract val currentStyles: Set<Style>
    abstract val isUndoAvailable: Boolean
    abstract val isRedoAvailable: Boolean

    abstract fun insertStyle(style: Style): RichTextValue
    abstract fun clearStyles(vararg styles: Style): RichTextValue

    abstract fun undo(): RichTextValue
    abstract fun redo(): RichTextValue
}

Every method that manipulates the text inside the TextField returns a copy of the object to be passed to a state.

Available styles

  1. Bold

    // Inserting style
    value = value.insertStyle(Style.Bold)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.Bold)
  2. Underline

    // Inserting style
    value = value.insertStyle(Style.Underline)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.Underline)
  3. Italic

    // Inserting style
    value = value.insertStyle(Style.Italic)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.Italic)
  4. Strikethrough

    // Inserting style
    value = value.insertStyle(Style.Strikethrough)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.Strikethrough)
  5. Align Left

    // Inserting style
    value = value.insertStyle(Style.AlignLeft)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.AlignLeft)
  6. Align Center

    // Inserting style
    value = value.insertStyle(Style.AlignCenter)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.AlignCenter)
  7. Align Right

    // Inserting style
    value = value.insertStyle(Style.AlignRight)
    
    // Checking if the style is used inside the current selection
    val isInCurrentSelection = value.currentStyles.contains(Style.AlignRight)
  8. Text Size

    // We're deleting all of the text size styles from the selection to avoid multiple multiplications of the size
    value = value.clearStyles(Style.TextColor())
    
    // Inserting style
    // You have to pass size as a parameter. It accepts values between 0.5f and 2.0f
    // Which means that the text size will be multiplied by the provided value
    value = value.insertStyle(Style.TextSize(textSize))
    
    // Checking if the style is used inside the current selection
    // Here we're using `filterIsInstance` to check if there are any of the text size styles
    val isInCurrentSelection = value.currentStyles.filterIsInstance<Style.TextSize>().isNotEmpty()
  9. Text Color

    // We're deleting all of the text color styles from the selection to avoid having more than one color on the same portion of the text (the last one would be displayed either way)
    value = value.clearStyles(Style.TextColor())
    
    // Inserting style
    // You have to pass color as a parameter
    value = value.insertStyle(Style.TextColor(color))
    
    // Checking if the style is used inside the current selection
    // Here we're using `filterIsInstance` to check if there are any of the text color styles
    val isInCurrentSelection = value.value.currentStyles.filterIsInstance<Style.TextColor>().isNotEmpty()

Custom styling

If you want to create your own styles you're free to do so. You would have to create a class that extends StyleMapper and implement the styling there for the styles that you would have created.

CustomStyle "${CustomParagraphStyle.javaClass.simpleName}/" -> CustomParagraphStyle else -> throw IllegalArgumentException() } override fun toSpanStyle(style: Style): SpanStyle? = super.toSpanStyle(style) ?: when (style) { // Here we're customizing the behavior of the style is CustomStyle -> SpanStyle( color = Color.Red, fontWeight = FontWeight.Bold, ) else -> null } override fun toParagraphStyle(style: Style): ParagraphStyle? = super.toParagraphStyle(style) ?: when (style) { is CustomParagraphStyle -> ParagraphStyle( textAlign = TextAlign.Justify, textIndent = TextIndent(firstLine = 12.sp) ) else -> null } }">
// If you want to create a paragraph style you have to extend `ParagraphStyle` interface!
object CustomParagraphStyle : Style
object CustomStyle : Style

class CustomStyleMapper : StyleMapper() {

    override fun fromTag(tag: String): Style =
        runCatching { super.fromTag(tag) }.getOrNull() ?: when (tag) {
            // It is necessary to ensure undo/redo actions work correctly
            "${CustomStyle.javaClass.simpleName}/" -> CustomStyle
            "${CustomParagraphStyle.javaClass.simpleName}/" -> CustomParagraphStyle
            else -> throw IllegalArgumentException()
        }

    override fun toSpanStyle(style: Style): SpanStyle? = super.toSpanStyle(style) ?: when (style) {
        // Here we're customizing the behavior of the style
        is CustomStyle -> SpanStyle(
            color = Color.Red,
            fontWeight = FontWeight.Bold,
        )
        else -> null
    }

    override fun toParagraphStyle(style: Style): ParagraphStyle? =
        super.toParagraphStyle(style) ?: when (style) {
            is CustomParagraphStyle -> ParagraphStyle(
                textAlign = TextAlign.Justify,
                textIndent = TextIndent(firstLine = 12.sp)
            )
            else -> null
        }
}

And then you would have to pass an instance of the class you created as a parameter to the RichTextValue class:

var value by remember {
    mutableStateOf(
        RichTextValue.get(
            styleMapper = CustomStyleMapper()
        )
    )
}
You might also like...
RichEditor for Android is a beautiful Rich Text WYSIWYG Editor for Android.
RichEditor for Android is a beautiful Rich Text WYSIWYG Editor for Android.

RichEditor for Android is a beautiful Rich Text WYSIWYG Editor for Android. Looking for iOS? Check out cjwirth/RichEditorView Supported Functions Bold

Animation effects to text, not really textview
Animation effects to text, not really textview

HTextView Animation effects with custom font support to TextView see iOS Effects see Flutter Effects Screenshot type gif Scale Evaporate Fall Line Typ

A TextView that automatically resizes text to fit perfectly within its bounds.
A TextView that automatically resizes text to fit perfectly within its bounds.

AutoFitTextView A TextView that automatically resizes text to fit perfectly within its bounds. Usage dependencies { compile 'me.grantland:autofitt

:page_facing_up: Android Text Full Jusiftication / Wrapping / Justify / Hyphenate - V2.0
:page_facing_up: Android Text Full Jusiftication / Wrapping / Justify / Hyphenate - V2.0

LIBRARY IS NO LONGER MAINTAINED If you want to adopt + maintain this library, please drop me a message - [email protected] Android Full Justific

Android form edit text is an extension of EditText that brings data validation facilities to the edittext.
Android form edit text is an extension of EditText that brings data validation facilities to the edittext.

Android Form EditText Android form edit text is an extension of EditText that brings data validation facilities to the edittext. Example App I built a

A Custom TextView with trim text
A Custom TextView with trim text

ReadMoreTextView A Custom TextView with trim text Download To add the ReadMoreTextView library to your Android Studio project, simply add the followin

[DISCONTINUED] Rrich text editor for android platform. 安卓富文本编辑器,暂停维护

icarus-android Maybe the best rich text editor on android platform. Base on Simditor Features Alignment (left/center/right) Bold Blockquote Code Horiz

An address-autocompleting text field for Android
An address-autocompleting text field for Android

android-PlacesAutocompleteTextView An AutocompleteTextView that interacts with the Google Maps Places API to provide location results and caches selec

Chips EditText, Token EditText, Bubble EditText, Spannable EditText and etc.. There are many names of this control. Here I develop easy to understand , modify and integrate Chips Edit Text widget for Android
Chips EditText, Token EditText, Bubble EditText, Spannable EditText and etc.. There are many names of this control. Here I develop easy to understand , modify and integrate Chips Edit Text widget for Android

Chips EditText Library Chips EditText, Token EditText, Bubble EditText, Spannable EditText and etc.. There are many names of this control. Here I deve

Comments
  • Expose more parameters of the internal Text/TextField

    Expose more parameters of the internal Text/TextField

    Hi there! The current API design in the library hide a ton of useful parameters from the Text/TextField that are used internally by the library.

    I think it would be useful to expose them to the user to allow further customization (e.g. setting singleLine/maxLines).

    Thanks in advance!

    enhancement 
    opened by Skaldebane 1
Releases(1.2.2)
Owner
Paweł Chochura
Mostly Android developer, enthusiast and a poor student
Paweł Chochura
LocalizedEditText - Custom edit text that allow only one language

LocalizedEditText Custom edit text that allow only one language Supported languages : Arabic , English Default language : English Examples

Mostafa Gad 1 Nov 28, 2021
A editable text with a constant text/placeholder for Android.

ParkedTextView A EditText with a constant text in the end. How to use <com.goka.parkedtextview.ParkedTextView xmlns:app="http://schemas.android.co

goka 270 Nov 11, 2022
RTL marquee text view android right to left moving text - persian - farsi - arabic - urdo

RTL marquee text view android right to left moving text - persian - farsi - arabic - urdo

mehran elyasi 4 Feb 14, 2022
AutoLinkTextView is TextView that supports Hashtags (#), Mentions (@) , URLs (http://), Phone and Email automatically detecting and ability to handle clicks.

AutoLinkTextView Deprecated Please use the new version of AutoLinkTextView AutoLinkTextView is TextView that supports Hashtags (#), Mentions (@) , URL

Arman 1.1k Nov 23, 2022
Markdown Text for Android Jetpack Compose 📋.

Markdown Text for Android Jetpack Compose ??.

Jeziel Lago 250 Jan 4, 2023
Date text field with on the fly validation built with Jetpack Compose.

Date text field with on the fly validation built with Jetpack Compose.

null 15 Nov 16, 2022
Jetpack Compose Rich Text Editor

An all-in-one Jetpack Compose component to handle text styling inside TextFields

Paweł Chochura 26 Dec 14, 2022
Android UI component library with stateful

Android UI component library with stateful

null 0 Oct 13, 2021
IconabTextView - Android UI component library with stateful

IconabTextView - Android UI component library with stateful

Burhan Cabiroglu 1 Oct 15, 2021
RoundedLetterView like the one in Android 5.0 Contacts app

RoundedLetterView RoundedLetterView like the one in Android 5.0 Contacts app Attributes to choose from: rlv_titleText - The text in the first row. rlv

Pavlos-Petros Tournaris 651 Dec 30, 2022