From 15e07c01dd2f97beca044860a49642cf8d4f5e54 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Wed, 5 Mar 2025 21:48:55 +0800 Subject: [PATCH] feat: add PanguTextPatcher --- docs-source/src/en/library/android.md | 34 ++++++++++++ docs-source/src/zh-cn/library/android.md | 34 +++++++++++- .../android/factory/PanguTextPatcher.kt | 52 +++++++++++++++++++ .../pangutext/android/factory/PanguWidget.kt | 47 +++++++++++------ 4 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguTextPatcher.kt diff --git a/docs-source/src/en/library/android.md b/docs-source/src/en/library/android.md index dc85671..cd8365c 100644 --- a/docs-source/src/en/library/android.md +++ b/docs-source/src/en/library/android.md @@ -150,6 +150,39 @@ However, please inject before the `LayoutInflater` instance is used to load the ::: +#### 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` or its 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`. @@ -356,6 +389,7 @@ Don't forget to add the declaration `xmlns:app="http://schemas.android.com/apk/r ::: 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 diff --git a/docs-source/src/zh-cn/library/android.md b/docs-source/src/zh-cn/library/android.md index a6c4f01..075271e 100644 --- a/docs-source/src/zh-cn/library/android.md +++ b/docs-source/src/zh-cn/library/android.md @@ -152,6 +152,38 @@ class MainActivity : Activity() { ::: +#### 使用修补工具 + +你可以使用 `PanguTextPatcher` 修补现有的 `View` 或 `ViewGroup` 实例。 + +修补整个根布局,`PanguTextPatcher` 会自动修补根布局下的所有 `TextView` 或继承于其的组件。 + +> 示例如下 + +```kotlin +// 假设你有一个根布局 +val root: ViewGroup +// 修补根布局 +PanguTextPatcher.patch(root) +``` + +修补单个 `View`,类型为 `TextView` 或继承于 `TextView` 的组件。 + +> 示例如下 + +```kotlin +// 假设这就是你的 TextView +val textView: TextView +// 修补单个 View +PanguTextPatcher.patch(textView) +``` + +::: warning + +在 `RecyclerView`、`ListView`、`ViewPager` 等回收式布局中使用 `PanguTextPatcher` 时,你需要在 `onCreateViewHolder` 或 `onBindViewHolder` 中获取到 `itemView` 后进行修补,否则不会生效。 + +::: + #### 手动注入或格式化文本 `PanguText` 同样支持手动注入,你可以在需要的 `TextView` 或 `EditText` 上手动进行注入。 @@ -351,7 +383,7 @@ textView.injectPanguText(config = config2) ::: -在自定义 `View` 中,你可以将你的 `View` 继承于 `PanguTextView` 接口以同样实现上述功能。 +在自定义 `View` 中,你可以将你的 `View` 继承于 `PanguTextView` 接口以同样实现上述功能,此功能对 [使用修补工具](#使用修补工具) 方案同样有效。 > 示例如下 diff --git a/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguTextPatcher.kt b/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguTextPatcher.kt new file mode 100644 index 0000000..19d0dab --- /dev/null +++ b/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguTextPatcher.kt @@ -0,0 +1,52 @@ +/* + * PanguText - A typographic solution for the optimal alignment of CJK characters, English words, and half-width digits. + * Copyright (C) 2019 HighCapable + * https://github.com/BetterAndroid/PanguText + * + * Apache License Version 2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is created by fankes on 2025/3/4. + */ +package com.highcapable.pangutext.android.factory + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import com.highcapable.betterandroid.ui.extension.view.walkThroughChildren +import com.highcapable.pangutext.android.PanguText +import com.highcapable.pangutext.android.PanguTextConfig + +/** + * Patcher for [PanguText]. + */ +object PanguTextPatcher { + + /** + * Patch [PanguText] to the view. + * @param view the view or view group. + * @param config the configuration of [PanguText]. + */ + @JvmOverloads + @JvmStatic + fun patch(view: View, config: PanguTextConfig = PanguText.globalConfig) { + when (view) { + is TextView -> PanguWidget.startInjection(view, config = config) + is ViewGroup -> + view.walkThroughChildren() + .filterIsInstance() + .forEach { PanguWidget.startInjection(it, config = config) } + } + } +} \ No newline at end of file diff --git a/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguWidget.kt b/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguWidget.kt index 79e5cf1..540d5e5 100644 --- a/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguWidget.kt +++ b/pangutext-android/src/main/java/com/highcapable/pangutext/android/factory/PanguWidget.kt @@ -85,44 +85,59 @@ internal object PanguWidget { } // Ignore if the instance is not a [TextView]. if (instance !is TextView) return null - var config = PanguText.globalConfig + return startInjection(instance, attrs) + } + + /** + * Start the injection of [PanguText] to the given [TextView]. + * @param instance the instance of [TextView]. + * @param attrs the attributes. + * @param config the configuration of [PanguText]. + * @return [TV] + */ + inline fun startInjection( + instance: TV, + attrs: AttributeSet? = null, + config: PanguTextConfig = PanguText.globalConfig + ): TV { + var sConfig = config if (instance is PanguTextView) { - val configCopy = config.copy() + val configCopy = sConfig.copy() instance.configurePanguText(configCopy) - config = configCopy - if (!config.isEnabled) return instance + sConfig = configCopy + if (!sConfig.isEnabled) return instance } else instance.obtainStyledAttributes(attrs, R.styleable.PanguTextHelper) { val isEnabled = it.getBooleanOrNull(R.styleable.PanguTextHelper_panguText_enabled) val isProcessedSpanned = it.getBooleanOrNull(R.styleable.PanguTextHelper_panguText_processedSpanned) val isAutoRemeasureText = it.getBooleanOrNull(R.styleable.PanguTextHelper_panguText_autoRemeasureText) val cjkSpacingRatio = it.getFloatOrNull(R.styleable.PanguTextHelper_panguText_cjkSpacingRatio) val excludePatterns = it.getStringOrNull(R.styleable.PanguTextHelper_panguText_excludePatterns) - ?.split(TEXT_REGEX_SPLITE_SYMBOL)?.mapNotNull { regex -> + ?.split(TEXT_REGEX_SPLITE_SYMBOL)?.mapNotNull { regex -> runCatching { regex.toRegex() }.onFailure { th -> Log.e(PangutextAndroidProperties.PROJECT_NAME, "Invalid exclude pattern of $instance: $regex", th) }.getOrNull() }?.toTypedArray() ?: emptyArray() if (isEnabled == false) return instance if (isProcessedSpanned != null || isAutoRemeasureText != null || cjkSpacingRatio != null || excludePatterns.isNotEmpty()) { - val configCopy = config.copy() - configCopy.isProcessedSpanned = isProcessedSpanned ?: config.isProcessedSpanned - configCopy.isAutoRemeasureText = isAutoRemeasureText ?: config.isAutoRemeasureText - configCopy.cjkSpacingRatio = cjkSpacingRatio ?: config.cjkSpacingRatio + val configCopy = sConfig.copy() + configCopy.isProcessedSpanned = isProcessedSpanned ?: sConfig.isProcessedSpanned + configCopy.isAutoRemeasureText = isAutoRemeasureText ?: sConfig.isAutoRemeasureText + configCopy.cjkSpacingRatio = cjkSpacingRatio ?: sConfig.cjkSpacingRatio if (excludePatterns.isNotEmpty()) { - config.excludePatterns.clear() - config.excludePatterns.addAll(excludePatterns) - }; config = configCopy + sConfig.excludePatterns.clear() + sConfig.excludePatterns.addAll(excludePatterns) + }; sConfig = configCopy } } when (instance.javaClass.name) { // Specialize those components because loading "hint" style after [doOnAttachRepeatable] causes problems. "com.google.android.material.textfield.TextInputEditText", "com.google.android.material.textfield.MaterialAutoCompleteTextView" -> { - instance.injectPanguText(config = config) - instance.doOnAttachRepeatable(config) { it.injectRealTimePanguText(injectHint = false, config) } + instance.injectPanguText(config = sConfig) + instance.doOnAttachRepeatable(sConfig) { it.injectRealTimePanguText(injectHint = false, sConfig) } } - else -> instance.doOnAttachRepeatable(config) { - it.injectRealTimePanguText(config = config) + else -> instance.doOnAttachRepeatable(sConfig) { + it.injectRealTimePanguText(config = sConfig) } }; return instance }