Skip to content

Commit 15e07c0

Browse files
committed
feat: add PanguTextPatcher
1 parent 6946e5e commit 15e07c0

File tree

4 files changed

+150
-17
lines changed

4 files changed

+150
-17
lines changed

docs-source/src/en/library/android.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,39 @@ However, please inject before the `LayoutInflater` instance is used to load the
150150

151151
:::
152152

153+
#### Using the Patching Tool
154+
155+
You can use `PanguTextPatcher` to patch existing `View` or `ViewGroup` instances.
156+
157+
Patch the entire root layout, and `PanguTextPatcher` will automatically patch all `TextView` or its subclasses under the root layout.
158+
159+
> The following example
160+
161+
```kotlin
162+
// Assume you have a root layout.
163+
val root: ViewGroup
164+
// Patch the root layout.
165+
PanguTextPatcher.patch(root)
166+
```
167+
168+
Patch a single `View`, which is of type `TextView` or a subclass of `TextView`.
169+
170+
> The following example
171+
172+
```kotlin
173+
// Assume this is your TextView.
174+
val textView: TextView
175+
// Patch a single View.
176+
PanguTextPatcher.patch(textView)
177+
```
178+
179+
::: warning
180+
181+
When using `PanguTextPatcher` in recycled layouts such as `RecyclerView`, `ListView`, or `ViewPager`, you need to patch the `itemView` in `onCreateViewHolder` or `onBindViewHolder`,
182+
otherwise it will not take effect.
183+
184+
:::
185+
153186
#### Manual Injection or Text Formatting
154187

155188
`PanguText` also supports manual injection, allowing you to inject it into the desired `TextView` or `EditText`.
@@ -356,6 +389,7 @@ Don't forget to add the declaration `xmlns:app="http://schemas.android.com/apk/r
356389
:::
357390

358391
In custom `View`, you can extend your `View` to implement the `PanguTextView` interface to achieve the same functionality.
392+
This feature is also effective for the [Using the Patching Tool](#using-the-patching-tool) method.
359393

360394
> The following example
361395

docs-source/src/zh-cn/library/android.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,38 @@ class MainActivity : Activity() {
152152

153153
:::
154154

155+
#### 使用修补工具
156+
157+
你可以使用 `PanguTextPatcher` 修补现有的 `View` 或 `ViewGroup` 实例。
158+
159+
修补整个根布局,`PanguTextPatcher` 会自动修补根布局下的所有 `TextView` 或继承于其的组件。
160+
161+
> 示例如下
162+
163+
```kotlin
164+
// 假设你有一个根布局
165+
val root: ViewGroup
166+
// 修补根布局
167+
PanguTextPatcher.patch(root)
168+
```
169+
170+
修补单个 `View`,类型为 `TextView` 或继承于 `TextView` 的组件。
171+
172+
> 示例如下
173+
174+
```kotlin
175+
// 假设这就是你的 TextView
176+
val textView: TextView
177+
// 修补单个 View
178+
PanguTextPatcher.patch(textView)
179+
```
180+
181+
::: warning
182+
183+
在 `RecyclerView`、`ListView`、`ViewPager` 等回收式布局中使用 `PanguTextPatcher` 时,你需要在 `onCreateViewHolder` 或 `onBindViewHolder` 中获取到 `itemView` 后进行修补,否则不会生效。
184+
185+
:::
186+
155187
#### 手动注入或格式化文本
156188

157189
`PanguText` 同样支持手动注入,你可以在需要的 `TextView` 或 `EditText` 上手动进行注入。
@@ -351,7 +383,7 @@ textView.injectPanguText(config = config2)
351383

352384
:::
353385

354-
在自定义 `View` 中,你可以将你的 `View` 继承于 `PanguTextView` 接口以同样实现上述功能。
386+
在自定义 `View` 中,你可以将你的 `View` 继承于 `PanguTextView` 接口以同样实现上述功能,此功能对 [使用修补工具](#使用修补工具) 方案同样有效
355387

356388
> 示例如下
357389
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* PanguText - A typographic solution for the optimal alignment of CJK characters, English words, and half-width digits.
3+
* Copyright (C) 2019 HighCapable
4+
* https://github.com/BetterAndroid/PanguText
5+
*
6+
* Apache License Version 2.0
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*
20+
* This file is created by fankes on 2025/3/4.
21+
*/
22+
package com.highcapable.pangutext.android.factory
23+
24+
import android.view.View
25+
import android.view.ViewGroup
26+
import android.widget.TextView
27+
import com.highcapable.betterandroid.ui.extension.view.walkThroughChildren
28+
import com.highcapable.pangutext.android.PanguText
29+
import com.highcapable.pangutext.android.PanguTextConfig
30+
31+
/**
32+
* Patcher for [PanguText].
33+
*/
34+
object PanguTextPatcher {
35+
36+
/**
37+
* Patch [PanguText] to the view.
38+
* @param view the view or view group.
39+
* @param config the configuration of [PanguText].
40+
*/
41+
@JvmOverloads
42+
@JvmStatic
43+
fun patch(view: View, config: PanguTextConfig = PanguText.globalConfig) {
44+
when (view) {
45+
is TextView -> PanguWidget.startInjection(view, config = config)
46+
is ViewGroup ->
47+
view.walkThroughChildren()
48+
.filterIsInstance<TextView>()
49+
.forEach { PanguWidget.startInjection(it, config = config) }
50+
}
51+
}
52+
}

pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguWidget.kt

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,44 +85,59 @@ internal object PanguWidget {
8585
}
8686
// Ignore if the instance is not a [TextView].
8787
if (instance !is TextView) return null
88-
var config = PanguText.globalConfig
88+
return startInjection(instance, attrs)
89+
}
90+
91+
/**
92+
* Start the injection of [PanguText] to the given [TextView].
93+
* @param instance the instance of [TextView].
94+
* @param attrs the attributes.
95+
* @param config the configuration of [PanguText].
96+
* @return [TV]
97+
*/
98+
inline fun <reified TV : TextView> startInjection(
99+
instance: TV,
100+
attrs: AttributeSet? = null,
101+
config: PanguTextConfig = PanguText.globalConfig
102+
): TV {
103+
var sConfig = config
89104
if (instance is PanguTextView) {
90-
val configCopy = config.copy()
105+
val configCopy = sConfig.copy()
91106
instance.configurePanguText(configCopy)
92-
config = configCopy
93-
if (!config.isEnabled) return instance
107+
sConfig = configCopy
108+
if (!sConfig.isEnabled) return instance
94109
} else instance.obtainStyledAttributes(attrs, R.styleable.PanguTextHelper) {
95110
val isEnabled = it.getBooleanOrNull(R.styleable.PanguTextHelper_panguText_enabled)
96111
val isProcessedSpanned = it.getBooleanOrNull(R.styleable.PanguTextHelper_panguText_processedSpanned)
97112
val isAutoRemeasureText = it.getBooleanOrNull(R.styleable.PanguTextHelper_panguText_autoRemeasureText)
98113
val cjkSpacingRatio = it.getFloatOrNull(R.styleable.PanguTextHelper_panguText_cjkSpacingRatio)
99114
val excludePatterns = it.getStringOrNull(R.styleable.PanguTextHelper_panguText_excludePatterns)
100-
?.split(TEXT_REGEX_SPLITE_SYMBOL)?.mapNotNull { regex ->
115+
?.split(TEXT_REGEX_SPLITE_SYMBOL)?.mapNotNull { regex ->
101116
runCatching { regex.toRegex() }.onFailure { th ->
102117
Log.e(PangutextAndroidProperties.PROJECT_NAME, "Invalid exclude pattern of $instance: $regex", th)
103118
}.getOrNull()
104119
}?.toTypedArray() ?: emptyArray()
105120
if (isEnabled == false) return instance
106121
if (isProcessedSpanned != null || isAutoRemeasureText != null || cjkSpacingRatio != null || excludePatterns.isNotEmpty()) {
107-
val configCopy = config.copy()
108-
configCopy.isProcessedSpanned = isProcessedSpanned ?: config.isProcessedSpanned
109-
configCopy.isAutoRemeasureText = isAutoRemeasureText ?: config.isAutoRemeasureText
110-
configCopy.cjkSpacingRatio = cjkSpacingRatio ?: config.cjkSpacingRatio
122+
val configCopy = sConfig.copy()
123+
configCopy.isProcessedSpanned = isProcessedSpanned ?: sConfig.isProcessedSpanned
124+
configCopy.isAutoRemeasureText = isAutoRemeasureText ?: sConfig.isAutoRemeasureText
125+
configCopy.cjkSpacingRatio = cjkSpacingRatio ?: sConfig.cjkSpacingRatio
111126
if (excludePatterns.isNotEmpty()) {
112-
config.excludePatterns.clear()
113-
config.excludePatterns.addAll(excludePatterns)
114-
}; config = configCopy
127+
sConfig.excludePatterns.clear()
128+
sConfig.excludePatterns.addAll(excludePatterns)
129+
}; sConfig = configCopy
115130
}
116131
}
117132
when (instance.javaClass.name) {
118133
// Specialize those components because loading "hint" style after [doOnAttachRepeatable] causes problems.
119134
"com.google.android.material.textfield.TextInputEditText",
120135
"com.google.android.material.textfield.MaterialAutoCompleteTextView" -> {
121-
instance.injectPanguText(config = config)
122-
instance.doOnAttachRepeatable(config) { it.injectRealTimePanguText(injectHint = false, config) }
136+
instance.injectPanguText(config = sConfig)
137+
instance.doOnAttachRepeatable(sConfig) { it.injectRealTimePanguText(injectHint = false, sConfig) }
123138
}
124-
else -> instance.doOnAttachRepeatable(config) {
125-
it.injectRealTimePanguText(config = config)
139+
else -> instance.doOnAttachRepeatable(sConfig) {
140+
it.injectRealTimePanguText(config = sConfig)
126141
}
127142
}; return instance
128143
}

0 commit comments

Comments
 (0)