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.
libraries:
com.highcapable.pangutext:
pangutext-android:
version: +
Configure dependency in your project build.gradle.kts
.
implementation(com.highcapable.pangutext.pangutext.android)
Traditional Method
Configure dependency in your project build.gradle.kts
.
implementation("com.highcapable.pangutext:pangutext-android:<version>")
Please change <version>
to the version displayed at the top of this document.
Function Introduction
You can view the KDoc click here.
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 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).
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 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. This is the recommended integration method, as it allows for non-intrusive and quick integration without modifying any existing layouts.
The following example
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)
}
}
Tips
Since LayoutInflater.Factory2
is taken over, recycled layouts like ListView
and RecyclerView
can also be correctly taken over.
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 loading.
If you are using ui-component → AppBindingActivity in BetterAndroid
, you need to slightly modify the current code.
The following example
class MainActivity : AppBindingActivity<ActivityMainBinding>() {
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
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(this)
setContentView(R.layout.activity_main)
}
}
Tips
PanguTextFactory2
can be used not only with Activity
but also injected into any existing LayoutInflater
instance. However, please inject before the LayoutInflater
instance is used to load the layout, 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
// 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
// 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
// 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
Tips
The injectPanguText
, injectRealTimePanguText
, setTextWithPangu
, setHintWithPangu
, and PanguText.format
methods support the config
parameter. You can refer to the 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
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)
}
}
Notice
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
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一段很长很长长长长长长长长长长长长还有English混入的的文本"
android:maxLines="1"
android:ellipsize="end" />
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
// 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: [PanguText] after injecting text and changing 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 every time in some dynamic layouts (such as `RecyclerView`) changes in text width,
// 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
Notice
If you integrated using the 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
// 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 method, you can use the following attributes in the XML layout declaration of TextView
, EditText
, or their subclasses for personalized configuration.
panguText_enabled
corresponds toPanguTextConfig.isEnabled
panguText_processedSpanned
corresponds toPanguTextConfig.isProcessedSpanned
panguText_autoRemeasureText
corresponds toPanguTextConfig.isAutoRemeasureText
panguText_excludePatterns
corresponds toPanguTextConfig.excludePatterns
, string array, multiple patterns separated by|@|
panguText_cjkSpacingRatio
corresponds toPanguTextConfig.cjkSpacingRatio
The following example
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xiaoming今年16岁"
app:panguText_enabled="true"
app:panguText_processedSpanned="true"
app:panguText_autoRemeasureText="true"
app:panguText_excludePatterns="https?://\\S+;\\[.*?]|@|\\[.*?]"
app:panguText_cjkSpacingRatio="7.0" />
Notice
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.
The following example
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs),
PanguTextView {
override fun configurePanguText(config: PanguTextConfig) {
// Configure your [PanguTextConfig].
}
}
Notice
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.