# Android



This is the core dependency for the Android platform. When using `PanguText` on Android, you need to include this module.
## Configure Dependency
You can add this module to your project using the following method.
### SweetDependency (Recommended)
Add dependency in your project's `SweetDependency` configuration file.
```yaml
libraries:
com.highcapable.pangutext:
pangutext-android:
version: +
```
Configure dependency in your project `build.gradle.kts`.
```kotlin
implementation(com.highcapable.pangutext.pangutext.android)
```
### Version Catalog
Add dependency in your project's `gradle/libs.versions.toml`.
```toml
[versions]
pangutext-android = ""
[libraries]
pangutext-android = { module = "com.highcapable.pangutext:pangutext-android", version.ref = "pangutext-android" }
```
Configure dependency in your project's `build.gradle.kts`.
```kotlin
implementation(libs.pangutext.android)
```
Please change `` to the version displayed at the top of this document.
### Traditional Method
Configure dependency in your project's `build.gradle.kts`.
```kotlin
implementation("com.highcapable.pangutext:pangutext-android:")
```
Please change `` to the version displayed at the top of this document.
## Function Introduction
You can view the KDoc [click here](kdoc://pangutext-android).
### Implementation Principle
`PanguText` provides two methods for text formatting on the Android platform: `SpannableString` (does not alter the original text length) and direct insertion of whitespace characters (alters the original text length).
The first method, `SpannableString`, adds a `Span` with spacing to the character before the one that needs spacing, changing the text style without altering the string content. The rendering is done by the `TextView` layer (or manually using `TextPaint` based on `Spanned` for layout styling), achieving non-intrusive text styling.
This method also supports processing already styled text (`Spanned`), such as text created via `Html.fromHtml`.
**However, it is currently experimental and may still have unexpected style errors**. You can refer to the [Personalized Configuration](#personalized-configuration) section below to disable it.
The dynamic application (injection) feature mainly targets the input state of `EditText`. It sets a custom `TextWatcher` for `EditText` to monitor input changes and formats the text from `afterTextChanged`.
The second method directly inserts whitespace characters after the characters that need spacing. This method alters the original text length and content but does not rely on the `TextView` layer for rendering. It uses `TextPaint` to draw the text directly, suitable for all scenarios, **but does not support dynamic application (injection)**.
::: warning Unresolved Issues
`PanguText` may conflict with Material components like `TextInputEditText`, `MaterialAutoCompleteTextView`, and `TextInputLayout` when using `setHint`, as `TextView` does not account for `Span` during measurement. This issue is particularly noticeable in single-line text, and there is no solution yet. Use these components cautiously.
Due to the above issue, calculating the width of a `TextView` with `PanguText` style using the `View.measure` method may also result in errors.
`PanguText` currently cannot handle continuous characters like underlines or strikethroughs in `Spanned` text, as the lines will break after adding spacing. It may also cause style errors or fail to apply styles correctly to some special characters. For stability, avoid enabling `PanguText` for very complex rich text or refer to the [Personalized Configuration](#personalized-configuration) section to set `excludePatterns`.
:::
### Integrate into Existing Projects
Integrating `PanguText` into your current project is very easy. You don't need to change much code. Choose your preferred method below to complete the integration.
#### Inject to LayoutInflater
`PanguText` supports direct injection of `LayoutInflater.Factory2` or creating a `LayoutInflater.Factory2` instance for the current `Activity` to take over the entire view layout inflation. This is the recommended integration method, as it allows for non-intrusive and quick integration without modifying any existing layouts.
> The following example
```kotlin
class MainActivity : AppCompatActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(this)
setContentView(binding.root)
}
}
```
::: tip
Since `LayoutInflater.Factory2` is taken over, recycled layouts like `ListView` and `RecyclerView` can also be correctly handled.
After injecting the `LayoutInflater` instance in the `Activity`, the following instances attached to the current `Context` will automatically take effect:
- `Fragment`
- `Dialog`
- `PopupWindow`
- `Toast` (foreground only in higher system versions)
Layouts based on `RemoteView` will not take effect because they are remote objects and do not use the current `Context`'s `LayoutInflater` for layout inflation.
:::
If you are using [ui-component → AppBindingActivity](https://betterandroid.github.io/BetterAndroid/KDoc/ui-component/ui-component/com.highcapable.betterandroid.ui.component.activity/-app-binding-activity) in `BetterAndroid`, you need to slightly modify the current code.
> The following example
```kotlin
class MainActivity : AppBindingActivity() {
override fun onPrepareContentView(savedInstanceState: Bundle?): LayoutInflater {
val inflater = super.onPrepareContentView(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(inflater)
return inflater
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Your code here.
}
}
```
If your application does not use `AppCompatActivity` or `ViewBinding`, don't worry, you can still use the original method.
> The following example
```kotlin
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(this)
setContentView(R.layout.activity_main)
}
}
```
::: tip
`PanguTextFactory2` can be used not only with `Activity` but also injected into any existing `LayoutInflater` instance.
However, please inject it before the `LayoutInflater` instance is used to inflate the layout, otherwise it will not take effect.
:::
#### Using the Patching Tool
You can use `PanguTextPatcher` to patch existing `View` or `ViewGroup` instances.
Patch the entire root layout, and `PanguTextPatcher` will automatically patch all `TextView` instances or their subclasses under the root layout.
> The following example
```kotlin
// Assume you have a root layout.
val root: ViewGroup
// Patch the root layout.
PanguTextPatcher.patch(root)
```
Patch a single `View`, which is of type `TextView` or a subclass of `TextView`.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Patch a single View.
PanguTextPatcher.patch(textView)
```
::: warning
When using `PanguTextPatcher` in recycled layouts such as `RecyclerView`, `ListView`, or `ViewPager`, you need to patch the `itemView` in `onCreateViewHolder` or `onBindViewHolder`,
otherwise it will not take effect.
:::
#### Manual Injection or Text Formatting
`PanguText` also supports manual injection, allowing you to inject it into the desired `TextView` or `EditText`.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Assume this is your EditText.
val editText: EditText
// Inject into existing text.
textView.injectPanguText()
editText.injectPanguText()
// Optionally choose whether to inject Hint (default is true).
textView.injectPanguText(injectHint = false)
editText.injectPanguText(injectHint = false)
// Dynamic injection, re-calling setText will automatically take effect.
textView.injectRealTimePanguText()
// Dynamic injection mainly targets the input state of EditText.
editText.injectRealTimePanguText()
// Optionally choose whether to inject Hint (default is true).
textView.injectRealTimePanguText(injectHint = false)
editText.injectRealTimePanguText(injectHint = false)
```
`PanguText` also extends the `setText` method of `TextView`, allowing you to directly set text with `PanguText` style.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Set text with PanguText style.
textView.setTextWithPangu("Xiaoming今年16岁")
// Set Hint with PanguText style.
textView.setHintWithPangu("输入Xiaoming的年龄")
```
You can also use the `PanguText.format` method to directly format text.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Format text using SpannableString method.
// Requires passing the current TextView's Resources and text size.
// If the input text is already Spannable,
// it will return the original object without creating a new SpannableString.
val text = PanguText.format(textView.resources, textView.textSize, "Xiaoming今年16岁")
// Set text.
textView.text = text
// Directly format text using whitespace characters for insertion.
// This method adds extra whitespace characters " " (HSP) to the text.
// The result below will output the string "Xiaoming 今年 16 岁".
// You can also customize the whitespace character at the end of the method.
val text = PanguText.format("Xiaoming今年16岁")
// Set text.
textView.text = text
```
::: tip
The `injectPanguText`, `injectRealTimePanguText`, `setTextWithPangu`, `setHintWithPangu`, and `PanguText.format` methods support the `config` parameter.
You can refer to the [Personalized Configuration](#personalized-configuration) section below.
:::
#### Custom View
`PanguText` can also be used with custom `View`. You can extend your `View` to `AppCompatTextView` and override the `setText` method.
> The following example
```kotlin
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) {
override fun setText(text: CharSequence?, type: BufferType?) {
// Manually inject here.
val panguText = text?.let { PanguText.format(resources, textSize, it) }
super.setText(panguText, type)
}
}
```
::: warning
After injecting `PanguText` into `TextView`, if you use `android:singleLine="true"` in XML layout or `TextView.setSingleLine(true)` in code along with `android:ellipsize="..."`,
this method of setting single-line text may cause unresolvable `OBJ` characters (truncated by ellipsis) to appear when the text exceeds the screen width, because `TextView` does not account for `Span` during measurement, leading to incorrect text width calculation.
The solution is to use `android:maxLines="1"` in XML layout or `TextView.setMaxLines(1)` in code instead.
> The following example
```xml
```
:::
### Personalized Configuration
`PanguText` supports personalized configuration. You can use the global static instance `PanguText.globalConfig` to get the global configuration or configure it individually.
> The following example
```kotlin
// Get global configuration.
val config = PanguText.globalConfig
// Enable or disable the feature.
config.isEnabled = true
// Process Spanned text.
// Processing Spanned text is enabled by default, but this feature is experimental.
// If issues occur, you can disable it. When disabled, Spanned text will return the original text.
config.isProcessedSpanned = true
// Whether to automatically re-measure the text width after processing.
// Note: After [PanguText] injects text and changes the text,
// the width of [TextView] will not be calculated automatically.
// At this time, this feature will call [TextView.setText] to re-execute the measurements,
// which can fix issues in some dynamic layouts (such as `RecyclerView`) where text width changes each time,
// but may cause performance issues. You can choose to disable this feature.
// To prevent unnecessary performance overhead,
// this feature only takes effect on [TextView] with `maxLines` set to 1 or `singleLine`.
config.isAutoRemeasureText = true
// Set patterns to exclude during formatting using regular expressions.
// For example, exclude all URLs.
config.excludePatterns.add("https?://\\S+".toRegex())
// For example, exclude emoji placeholders like "[doge]".
// If you use [ImageSpan] to display emoji images, you can choose to exclude these placeholders.
config.excludePatterns.add("\\[.*?]".toRegex())
// Set the spacing ratio for CJK characters.
// This determines the final layout effect.
// It is recommended to keep the default ratio and adjust it according to personal preference.
config.cjkSpacingRatio = 7f
```
::: warning
If you integrated using the [Inject to LayoutInflater](#inject-to-layoutinflater) method, configure `PanguText.globalConfig` before executing `PanguTextFactory2.inject(...)`, otherwise the configuration will not take effect.
:::
You can also pass the `config` parameter for personalized configuration when manually injecting or formatting text.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Create a new configuration.
// You can set [copyFromGlobal] to false to not copy from the global configuration.
val config = PanguTextConfig(copyFromGlobal = false) {
excludePatterns.add("https?://\\S+".toRegex())
excludePatterns.add("\\[.*?]".toRegex())
cjkSpacingRatio = 7f
}
// You can also copy and create a new configuration from any configuration.
val config2 = config.copy {
excludePatterns.clear()
excludePatterns.add("https?://\\S+".toRegex())
excludePatterns.add("\\[.*?]".toRegex())
cjkSpacingRatio = 7f
}
// Manually inject and configure.
textView.injectPanguText(config = config2)
```
If you integrated using the [Inject to LayoutInflater](#inject-to-layoutinflater) method, you can use the following attributes in the XML layout declaration of `TextView`, `EditText`, or their subclasses for personalized configuration.
- `panguText_enabled` corresponds to `PanguTextConfig.isEnabled`
- `panguText_processedSpanned` corresponds to `PanguTextConfig.isProcessedSpanned`
- `panguText_autoRemeasureText` corresponds to `PanguTextConfig.isAutoRemeasureText`
- `panguText_excludePatterns` corresponds to `PanguTextConfig.excludePatterns`, string array, multiple patterns separated by `|@|`
- `panguText_cjkSpacingRatio` corresponds to `PanguTextConfig.cjkSpacingRatio`
> The following example
```xml
```
::: warning
Due to issues with Android Studio, the above attributes may not have auto-completion hints. Please complete them manually.
Don't forget to add the declaration `xmlns:app="http://schemas.android.com/apk/res-auto"`.
:::
In custom `View`, you can extend your `View` to implement the `PanguTextView` interface to achieve the same functionality.
This feature is also effective for the [Using the Patching Tool](#using-the-patching-tool) method.
> The following example
```kotlin
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs),
PanguTextView {
override fun configurePanguText(config: PanguTextConfig) {
// Configure your [PanguTextConfig].
}
}
```
::: warning
The `PanguTextView` interface takes precedence over attributes used directly in the XML layout. If you use both methods for configuration, the `PanguTextView` interface configuration will override the XML layout configuration.
Individual configurations will override global configurations, and options not configured will follow the global configuration.
:::