如何使用支持库字体功能作为 TextView 内容的一部分(使用 spannable)?
How to use support library fonts feature as a part of the TextView content (using spannable)?
背景
支持库(文档 here)允许您使用 "res/font" 文件夹中的 TTF 字体文件,或者 XML :
app:fontFamily="@font/lato_black"
或通过代码:
val typeface = ResourcesCompat.getFont(context, R.font.lato_black)
问题
虽然我知道可以使用 spannable 技术在部分 TextView 内容中设置不同的样式(例如粗体、斜体、颜色等...),但我发现设置不同的唯一方法字体,是通过使用 OS 的内置字体,如图 here 所示,但我看不到加载字体的新方法。
我试过的
我试图找到一种在两者之间进行转换的方法,但没有成功。当然,我也尝试在文档中寻找可能的功能,我也尝试在网上找到它。
问题
如何为TextView的不同部分设置不同的字体?
例如,在文本"Hello world"中,设置"Hello"的字体为"lato_black",其余默认。
编辑:因为我从 2 个不同的人那里得到了大致相同的答案,所以我不能接受其中一个。为他们的努力给了他们+1,我稍微改变了问题:
我如何轻松地将字体样式设置为文本的一部分,同时让 strings.xml 文件使用自定义字体标签定义它。
例如,这可以在 strings.xml 文件中按照我上面的要求进行设置:
<string name="something" ><customFont fontResource="lato_black">Hello</customFont> world</string>
然后,在代码中,您要做的就是使用如下内容:
textView.setText (Html.fromHtml(text, null, CustomFontTagHandler()))
我认为这很重要,因为翻译后的字符串可能与英文的差异太大,因此您不能只解析字符串的文本然后选择在何处设置自定义字体。它必须在字符串文件中。
Custom Class for apply fonrFamilySpan
public class MultipleFamilyTypeface extends TypefaceSpan {
private final Typeface typeFace;
public MultipleFamilyTypeface(String family, Typeface type) {
super(family);
typeFace = type;
}
@Override
public void updateDrawState(TextPaint ds) {
applyTypeFace(ds, typeFace);
}
@Override
public void updateMeasureState(TextPaint paint) {
applyTypeFace(paint, typeFace);
}
private static void applyTypeFace(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
应用字体
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String firstWord = "Hello ";
String secondWord = "Word ";
String thirdWord = "Normal ";
TextView textViewTest = findViewById(R.id.textViewTest);
Spannable spannable = new SpannableString(firstWord + secondWord + thirdWord);
Typeface CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.akronim);
Typeface SECOND_CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.baloo_thambi);
spannable.setSpan(new MultipleFamilyTypeface("akronim", CUSTOM_TYPEFACE), 0, firstWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new MultipleFamilyTypeface("baloo_thambi", SECOND_CUSTOM_TYPEFACE), firstWord.length(), firstWord.length() + secondWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textViewTest.setText(spannable);
}
}
OutPut
编辑自定义标签的方法二
在gradle
中添加implementation 'org.jsoup:jsoup:1.11.3'
List<String> myCustomTag = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textViewTest = findViewById(R.id.textViewTest);
// mention list custom tag that you used
myCustomTag.add("akronim");
myCustomTag.add("baloo_thambi");
myCustomTag.add("xyz");
String html = "<akronim>Hello</akronim>"
+ "<baloo_thambi> Word </baloo_thambi>"
+ " Normal "
+ " <xyz> testing </xyz> "
+ "<akronim>Styles</akronim>";
textViewTest.setText(processToFontStyle(html));
}
public Spannable processToFontStyle(String text) {
Document doc = Jsoup.parse(text);
Elements tags = doc.getAllElements();
String cleanText = doc.text();
Log.d("ClearTextTag", "Text " + cleanText);
Spannable spannable = new SpannableString(cleanText);
List<String> tagsFromString = new ArrayList<>();
List<Integer> startTextPosition = new ArrayList<>();
List<Integer> endTextPosition = new ArrayList<>();
for (Element tag : tags) {
String nodeText = tag.text();
if (myCustomTag.contains(tag.tagName())) {
int startingIndex = cleanText.indexOf(nodeText);
tagsFromString.add(tag.tagName());
startTextPosition.add(startingIndex);
endTextPosition.add(startingIndex + nodeText.length());
}
}
Typeface CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.akronim);
Typeface SECOND_CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.baloo_thambi);
Typeface XYZ_CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.architects_daughter);
for (int i = 0; i < tagsFromString.size(); i++) {
String fontName = tagsFromString.get(i);
Typeface selected = null;
switch (fontName) {
case "akronim":
selected = CUSTOM_TYPEFACE;
break;
case "baloo_thambi":
selected = SECOND_CUSTOM_TYPEFACE;
break;
case "xyz":
selected = XYZ_CUSTOM_TYPEFACE;
break;
}
if (selected != null)
spannable.setSpan(new MultipleFamilyTypeface(fontName, selected), startTextPosition.get(i), endTextPosition.get(i), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable;
}
OutPut
试试这个...它在我的情况下工作得很好,你可以根据你的要求进行更改。
目前,它的工作原理是,例如:- Hello World
、font lato_light
中的 Hello
和 font lato_bold
中的 remaining
。
protected final SpannableStringBuilder decorateTitle(String text, @IdRes int view) {
List<TextUtils.Option> options = new ArrayList<>();
int index = text.indexOf(' ');
if (index >= 0) {
options.add(new TextUtils.Option(ResourcesCompat.getFont(this, R.font.lato_light),
ContextCompat.getColor(this, R.color.toolbar_title_text),
0, index));
options.add(new TextUtils.Option(ResourcesCompat.getFont(this, R.font.lato_bold),
ContextCompat.getColor(this, R.color.primary_text),
index, text.length()));
} else options.add(new TextUtils.Option(ResourcesCompat.getFont(this, R.font.lato_bold),
ContextCompat.getColor(this, R.color.primary_text),
0, text.length()));
SpannableStringBuilder stringBuilder = TextUtils.stringSpanning(options, text);
if (view != 0) {
((TextView) findViewById(view)).setText(stringBuilder);
}
return stringBuilder;
}
在Javaclass添加这个方法passString u want to decorate
&view in xml
public void onSuccess(@NonNull String title) {
decorateTitle(title, R.id.listing_toolbar_title);
}
TextUtils.java
public final class TextUtils {
public static String trim(String text) {
text = text.trim();
return text.replaceAll("\s+", " ");
}
public static String sanitize(String text) {
if (text == null || text.isEmpty()) return text;
if (text.contains("\ufffd")) {
text = text.replaceAll("\ufffd", "");
}
if (text.contains(" ")) {
return sanitize(text.split("\s"));
} else if (text.contains("_")) {
return sanitize(text.split("_"));
} else if (text.contains("-")) {
return sanitize(text.split("-"));
}
if (!Character.isUpperCase(text.charAt(0))) {
return text.substring(0, 1).toUpperCase() + text.substring(1);
} else {
return text;
}
}
private static String sanitize(String[] strings) {
StringBuilder sb = new StringBuilder();
int lastIndex = strings.length - 1;
for (int i = 0; i < strings.length; i++) {
String str = strings[i];
if (str.length() > 0) {
if (Character.isLetter(str.charAt(0))
&& !Character.isUpperCase(str.charAt(0))) {
sb.append(str.substring(0, 1).toUpperCase()).append(str.substring(1));
} else {
sb.append(str);
}
if (i != lastIndex) sb.append(" ");
}
}
return sb.toString();
}
public static String fillWithUnderscore(String text) {
if (text.contains(" ")) {
String[] splitText = text.split(" ");
StringBuilder sb = new StringBuilder();
int lastIndex = splitText.length - 1;
for (int i = 0; i < splitText.length; i++) {
sb.append(splitText[i]);
if (i != lastIndex) sb.append("_");
}
return sb.toString();
} else return text;
}
public static String sanitizePrice(Double price) {
if (Objects.isNull(price) || price == 0) return "";
String pricing = String.format(Locale.getDefault(), "₹ %.0f", price);
StringBuilder input = new StringBuilder(pricing).reverse();
StringBuilder output = new StringBuilder("");
char[] digits = input.toString().toCharArray();
for (int i = 0; i < digits.length; i++) {
if (i < 3 || i % 2 == 0) {
output.append(digits[i]);
} else if (i % 2 != 0) {
output.append(" ").append(digits[i]);
}
}
return output.reverse().toString();
}
public static String sanitizeProductName(String productName) {
if (productName.contains("\ufffd")) {
return productName.replaceAll("\ufffd", "");
} else return productName;
}
///////////////////////////////////////////////////////////////////////////
// String Spanning
///////////////////////////////////////////////////////////////////////////
private static void applyCustomTypeFace(Paint paint, Typeface tf) {
paint.setTypeface(tf);
}
public static SpannableStringBuilder stringSpanning(List<Option> options, StringBuilder builder) {
return stringSpanning(options, builder.toString());
}
public static SpannableStringBuilder stringSpanning(List<Option> options, String text) {
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
for (Option option : options) {
spannable.setSpan(new CustomTypefaceSpan(option.getFont()),
option.getFromIndex(), option.getToIndex(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(option.getColor()),
option.getFromIndex(), option.getToIndex(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
return spannable;
}
static class CustomTypefaceSpan extends MetricAffectingSpan {
private final Typeface typeface;
CustomTypefaceSpan(Typeface typeface) {
this.typeface = typeface;
}
@Override
public void updateDrawState(TextPaint ds) {
applyCustomTypeFace(ds, typeface);
}
@Override
public void updateMeasureState(TextPaint paint) {
applyCustomTypeFace(paint, typeface);
}
}
public static class Option {
private Typeface font;
private int color;
private int fromIndex;
private int toIndex;
public Option(Typeface font, int color, int fromIndex, int toIndex) {
this.font = font;
this.color = color;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
public Option(Context context, @FontRes int font, @ColorRes int color, int fromIndex, int toIndex) {
this.font = ResourcesCompat.getFont(context, font);
this.color = ContextCompat.getColor(context, color);
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
public Typeface getFont() {
return font;
}
public void setFont(Typeface font) {
this.font = font;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getFromIndex() {
return fromIndex;
}
public void setFromIndex(int fromIndex) {
this.fromIndex = fromIndex;
}
public int getToIndex() {
return toIndex;
}
public void setToIndex(int toIndex) {
this.toIndex = toIndex;
}
}
public static Double toDouble(String text) {
StringBuilder collect = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (Character.isDigit(c))
collect.append(c);
}
return Double.parseDouble(collect.toString());
}
}
由于 MJM
和 TheMatrix
的两个答案几乎相同(但对我来说过于复杂)并且两个答案大约在同一时间,所以我不能只选择其中一个,所以我为每个人都授予了 +1,要求他们缩短它但支持 XML 标签以便于处理字符串文件。
现在,这里是如何为 TextView 中的部分文本设置自定义字体的更短版本:
class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() {
override fun updateDrawState(paint: TextPaint) {
paint.typeface=typeface
}
override fun updateMeasureState(paint: TextPaint) {
paint.typeface=typeface
}
}
示例用法:
val text = "Hello world"
val index = text.indexOf(' ')
val spannable = SpannableStringBuilder(text)
spannable.setSpan(CustomTypefaceSpan(ResourcesCompat.getFont(this, R.font.lato_light)), 0, index, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
spannable.setSpan(CustomTypefaceSpan(ResourcesCompat.getFont(this, R.font.lato_bold)), index, text.length, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
textView.text = spannable
编辑:似乎 Google 提供了有关此的视频,here :
class CustomTypefaceSpan(val font: Typeface?) : MetricAffectingSpan() {
override fun updateMeasureState(textPaint: TextPaint) = update(textPaint)
override fun updateDrawState(textPaint: TextPaint?) = update(textPaint)
private fun update(tp: TextPaint?) {
tp.apply {
val old = this!!.typeface
val oldStyle = old?.style ?: 0
val font = Typeface.create(font, oldStyle)
typeface = font
}
}
}
视频 here 中也谈到了 strings.xml 中处理它的解决方案,但使用注释而不是新的 HTML 标签。示例:
strings.xml
<string name="title"><annotation font="lato_light">Hello</annotation> <annotation font="lato_bold">world</annotation></string>
MainActivity.kt
val titleText = getText(R.string.title) as SpannedString
val spannable = SpannableStringBuilder(titleText)
val annotations = titleText.getSpans(0, titleText.length, android.text.Annotation::class.java)
for (annotation in annotations) {
if(annotation.key=="font"){
val fontName=annotation.value
val typeface= ResourcesCompat.getFont(this@MainActivity,resources.getIdentifier(fontName,"font",packageName))
spannable.setSpan(CustomTypefaceSpan(typeface),spannable.getSpanStart(annotation),spannable.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
textView.text = spannable
结果:
我仍然很确定可以使用 fromHtml,但它可能不值得。
我也想知道如果我们想同时使用基本的 HTML 标签和我们为字体设置的自定义标签,如果我们确实在那里使用 annotation
,应该怎么做。
扩展 android 开发者的 , one can make .font(){...}
extension function like .bold{}
, .backgroundColor{}
from android ktx:
inline fun SpannableStringBuilder.font(typeface: Typeface, builderAction: SpannableStringBuilder.() -> Unit): SpannableStringBuilder {
return inSpans(TypefaceSpan(typeface), builderAction = builderAction)
}
那么你可以使用:
val spannable = SpannableStringBuilder()
.append(getString(...))
.font(ResourcesCompat.getFont(context!!, R.font.myFont)!!) {
append(getString(...))
}
.bold{append(getString(...))}
textView.text = spannable
不要使用spannable.toString()
。
奖金:fontSize
Spannable
:
inline fun SpannableStringBuilder.fontSize(fontSize: Int, builderAction: SpannableStringBuilder.() -> Unit): SpannableStringBuilder {
return inSpans(AbsoluteSizeSpan(fontSize), builderAction = builderAction)
}
背景
支持库(文档 here)允许您使用 "res/font" 文件夹中的 TTF 字体文件,或者 XML :
app:fontFamily="@font/lato_black"
或通过代码:
val typeface = ResourcesCompat.getFont(context, R.font.lato_black)
问题
虽然我知道可以使用 spannable 技术在部分 TextView 内容中设置不同的样式(例如粗体、斜体、颜色等...),但我发现设置不同的唯一方法字体,是通过使用 OS 的内置字体,如图 here 所示,但我看不到加载字体的新方法。
我试过的
我试图找到一种在两者之间进行转换的方法,但没有成功。当然,我也尝试在文档中寻找可能的功能,我也尝试在网上找到它。
问题
如何为TextView的不同部分设置不同的字体?
例如,在文本"Hello world"中,设置"Hello"的字体为"lato_black",其余默认。
编辑:因为我从 2 个不同的人那里得到了大致相同的答案,所以我不能接受其中一个。为他们的努力给了他们+1,我稍微改变了问题:
我如何轻松地将字体样式设置为文本的一部分,同时让 strings.xml 文件使用自定义字体标签定义它。
例如,这可以在 strings.xml 文件中按照我上面的要求进行设置:
<string name="something" ><customFont fontResource="lato_black">Hello</customFont> world</string>
然后,在代码中,您要做的就是使用如下内容:
textView.setText (Html.fromHtml(text, null, CustomFontTagHandler()))
我认为这很重要,因为翻译后的字符串可能与英文的差异太大,因此您不能只解析字符串的文本然后选择在何处设置自定义字体。它必须在字符串文件中。
Custom Class for apply fonrFamilySpan
public class MultipleFamilyTypeface extends TypefaceSpan {
private final Typeface typeFace;
public MultipleFamilyTypeface(String family, Typeface type) {
super(family);
typeFace = type;
}
@Override
public void updateDrawState(TextPaint ds) {
applyTypeFace(ds, typeFace);
}
@Override
public void updateMeasureState(TextPaint paint) {
applyTypeFace(paint, typeFace);
}
private static void applyTypeFace(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
应用字体
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String firstWord = "Hello ";
String secondWord = "Word ";
String thirdWord = "Normal ";
TextView textViewTest = findViewById(R.id.textViewTest);
Spannable spannable = new SpannableString(firstWord + secondWord + thirdWord);
Typeface CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.akronim);
Typeface SECOND_CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.baloo_thambi);
spannable.setSpan(new MultipleFamilyTypeface("akronim", CUSTOM_TYPEFACE), 0, firstWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new MultipleFamilyTypeface("baloo_thambi", SECOND_CUSTOM_TYPEFACE), firstWord.length(), firstWord.length() + secondWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textViewTest.setText(spannable);
}
}
OutPut
编辑自定义标签的方法二
在gradle
中添加implementation 'org.jsoup:jsoup:1.11.3'
List<String> myCustomTag = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textViewTest = findViewById(R.id.textViewTest);
// mention list custom tag that you used
myCustomTag.add("akronim");
myCustomTag.add("baloo_thambi");
myCustomTag.add("xyz");
String html = "<akronim>Hello</akronim>"
+ "<baloo_thambi> Word </baloo_thambi>"
+ " Normal "
+ " <xyz> testing </xyz> "
+ "<akronim>Styles</akronim>";
textViewTest.setText(processToFontStyle(html));
}
public Spannable processToFontStyle(String text) {
Document doc = Jsoup.parse(text);
Elements tags = doc.getAllElements();
String cleanText = doc.text();
Log.d("ClearTextTag", "Text " + cleanText);
Spannable spannable = new SpannableString(cleanText);
List<String> tagsFromString = new ArrayList<>();
List<Integer> startTextPosition = new ArrayList<>();
List<Integer> endTextPosition = new ArrayList<>();
for (Element tag : tags) {
String nodeText = tag.text();
if (myCustomTag.contains(tag.tagName())) {
int startingIndex = cleanText.indexOf(nodeText);
tagsFromString.add(tag.tagName());
startTextPosition.add(startingIndex);
endTextPosition.add(startingIndex + nodeText.length());
}
}
Typeface CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.akronim);
Typeface SECOND_CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.baloo_thambi);
Typeface XYZ_CUSTOM_TYPEFACE = ResourcesCompat.getFont(this, R.font.architects_daughter);
for (int i = 0; i < tagsFromString.size(); i++) {
String fontName = tagsFromString.get(i);
Typeface selected = null;
switch (fontName) {
case "akronim":
selected = CUSTOM_TYPEFACE;
break;
case "baloo_thambi":
selected = SECOND_CUSTOM_TYPEFACE;
break;
case "xyz":
selected = XYZ_CUSTOM_TYPEFACE;
break;
}
if (selected != null)
spannable.setSpan(new MultipleFamilyTypeface(fontName, selected), startTextPosition.get(i), endTextPosition.get(i), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable;
}
OutPut
试试这个...它在我的情况下工作得很好,你可以根据你的要求进行更改。
目前,它的工作原理是,例如:- Hello World
、font lato_light
中的 Hello
和 font lato_bold
中的 remaining
。
protected final SpannableStringBuilder decorateTitle(String text, @IdRes int view) {
List<TextUtils.Option> options = new ArrayList<>();
int index = text.indexOf(' ');
if (index >= 0) {
options.add(new TextUtils.Option(ResourcesCompat.getFont(this, R.font.lato_light),
ContextCompat.getColor(this, R.color.toolbar_title_text),
0, index));
options.add(new TextUtils.Option(ResourcesCompat.getFont(this, R.font.lato_bold),
ContextCompat.getColor(this, R.color.primary_text),
index, text.length()));
} else options.add(new TextUtils.Option(ResourcesCompat.getFont(this, R.font.lato_bold),
ContextCompat.getColor(this, R.color.primary_text),
0, text.length()));
SpannableStringBuilder stringBuilder = TextUtils.stringSpanning(options, text);
if (view != 0) {
((TextView) findViewById(view)).setText(stringBuilder);
}
return stringBuilder;
}
在Javaclass添加这个方法passString u want to decorate
&view in xml
public void onSuccess(@NonNull String title) {
decorateTitle(title, R.id.listing_toolbar_title);
}
TextUtils.java
public final class TextUtils {
public static String trim(String text) {
text = text.trim();
return text.replaceAll("\s+", " ");
}
public static String sanitize(String text) {
if (text == null || text.isEmpty()) return text;
if (text.contains("\ufffd")) {
text = text.replaceAll("\ufffd", "");
}
if (text.contains(" ")) {
return sanitize(text.split("\s"));
} else if (text.contains("_")) {
return sanitize(text.split("_"));
} else if (text.contains("-")) {
return sanitize(text.split("-"));
}
if (!Character.isUpperCase(text.charAt(0))) {
return text.substring(0, 1).toUpperCase() + text.substring(1);
} else {
return text;
}
}
private static String sanitize(String[] strings) {
StringBuilder sb = new StringBuilder();
int lastIndex = strings.length - 1;
for (int i = 0; i < strings.length; i++) {
String str = strings[i];
if (str.length() > 0) {
if (Character.isLetter(str.charAt(0))
&& !Character.isUpperCase(str.charAt(0))) {
sb.append(str.substring(0, 1).toUpperCase()).append(str.substring(1));
} else {
sb.append(str);
}
if (i != lastIndex) sb.append(" ");
}
}
return sb.toString();
}
public static String fillWithUnderscore(String text) {
if (text.contains(" ")) {
String[] splitText = text.split(" ");
StringBuilder sb = new StringBuilder();
int lastIndex = splitText.length - 1;
for (int i = 0; i < splitText.length; i++) {
sb.append(splitText[i]);
if (i != lastIndex) sb.append("_");
}
return sb.toString();
} else return text;
}
public static String sanitizePrice(Double price) {
if (Objects.isNull(price) || price == 0) return "";
String pricing = String.format(Locale.getDefault(), "₹ %.0f", price);
StringBuilder input = new StringBuilder(pricing).reverse();
StringBuilder output = new StringBuilder("");
char[] digits = input.toString().toCharArray();
for (int i = 0; i < digits.length; i++) {
if (i < 3 || i % 2 == 0) {
output.append(digits[i]);
} else if (i % 2 != 0) {
output.append(" ").append(digits[i]);
}
}
return output.reverse().toString();
}
public static String sanitizeProductName(String productName) {
if (productName.contains("\ufffd")) {
return productName.replaceAll("\ufffd", "");
} else return productName;
}
///////////////////////////////////////////////////////////////////////////
// String Spanning
///////////////////////////////////////////////////////////////////////////
private static void applyCustomTypeFace(Paint paint, Typeface tf) {
paint.setTypeface(tf);
}
public static SpannableStringBuilder stringSpanning(List<Option> options, StringBuilder builder) {
return stringSpanning(options, builder.toString());
}
public static SpannableStringBuilder stringSpanning(List<Option> options, String text) {
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
for (Option option : options) {
spannable.setSpan(new CustomTypefaceSpan(option.getFont()),
option.getFromIndex(), option.getToIndex(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(option.getColor()),
option.getFromIndex(), option.getToIndex(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
return spannable;
}
static class CustomTypefaceSpan extends MetricAffectingSpan {
private final Typeface typeface;
CustomTypefaceSpan(Typeface typeface) {
this.typeface = typeface;
}
@Override
public void updateDrawState(TextPaint ds) {
applyCustomTypeFace(ds, typeface);
}
@Override
public void updateMeasureState(TextPaint paint) {
applyCustomTypeFace(paint, typeface);
}
}
public static class Option {
private Typeface font;
private int color;
private int fromIndex;
private int toIndex;
public Option(Typeface font, int color, int fromIndex, int toIndex) {
this.font = font;
this.color = color;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
public Option(Context context, @FontRes int font, @ColorRes int color, int fromIndex, int toIndex) {
this.font = ResourcesCompat.getFont(context, font);
this.color = ContextCompat.getColor(context, color);
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
public Typeface getFont() {
return font;
}
public void setFont(Typeface font) {
this.font = font;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getFromIndex() {
return fromIndex;
}
public void setFromIndex(int fromIndex) {
this.fromIndex = fromIndex;
}
public int getToIndex() {
return toIndex;
}
public void setToIndex(int toIndex) {
this.toIndex = toIndex;
}
}
public static Double toDouble(String text) {
StringBuilder collect = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (Character.isDigit(c))
collect.append(c);
}
return Double.parseDouble(collect.toString());
}
}
由于 MJM
和 TheMatrix
的两个答案几乎相同(但对我来说过于复杂)并且两个答案大约在同一时间,所以我不能只选择其中一个,所以我为每个人都授予了 +1,要求他们缩短它但支持 XML 标签以便于处理字符串文件。
现在,这里是如何为 TextView 中的部分文本设置自定义字体的更短版本:
class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() {
override fun updateDrawState(paint: TextPaint) {
paint.typeface=typeface
}
override fun updateMeasureState(paint: TextPaint) {
paint.typeface=typeface
}
}
示例用法:
val text = "Hello world"
val index = text.indexOf(' ')
val spannable = SpannableStringBuilder(text)
spannable.setSpan(CustomTypefaceSpan(ResourcesCompat.getFont(this, R.font.lato_light)), 0, index, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
spannable.setSpan(CustomTypefaceSpan(ResourcesCompat.getFont(this, R.font.lato_bold)), index, text.length, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
textView.text = spannable
编辑:似乎 Google 提供了有关此的视频,here :
class CustomTypefaceSpan(val font: Typeface?) : MetricAffectingSpan() {
override fun updateMeasureState(textPaint: TextPaint) = update(textPaint)
override fun updateDrawState(textPaint: TextPaint?) = update(textPaint)
private fun update(tp: TextPaint?) {
tp.apply {
val old = this!!.typeface
val oldStyle = old?.style ?: 0
val font = Typeface.create(font, oldStyle)
typeface = font
}
}
}
视频 here 中也谈到了 strings.xml 中处理它的解决方案,但使用注释而不是新的 HTML 标签。示例:
strings.xml
<string name="title"><annotation font="lato_light">Hello</annotation> <annotation font="lato_bold">world</annotation></string>
MainActivity.kt
val titleText = getText(R.string.title) as SpannedString
val spannable = SpannableStringBuilder(titleText)
val annotations = titleText.getSpans(0, titleText.length, android.text.Annotation::class.java)
for (annotation in annotations) {
if(annotation.key=="font"){
val fontName=annotation.value
val typeface= ResourcesCompat.getFont(this@MainActivity,resources.getIdentifier(fontName,"font",packageName))
spannable.setSpan(CustomTypefaceSpan(typeface),spannable.getSpanStart(annotation),spannable.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
textView.text = spannable
结果:
我仍然很确定可以使用 fromHtml,但它可能不值得。
我也想知道如果我们想同时使用基本的 HTML 标签和我们为字体设置的自定义标签,如果我们确实在那里使用 annotation
,应该怎么做。
扩展 android 开发者的 .font(){...}
extension function like .bold{}
, .backgroundColor{}
from android ktx:
inline fun SpannableStringBuilder.font(typeface: Typeface, builderAction: SpannableStringBuilder.() -> Unit): SpannableStringBuilder {
return inSpans(TypefaceSpan(typeface), builderAction = builderAction)
}
那么你可以使用:
val spannable = SpannableStringBuilder()
.append(getString(...))
.font(ResourcesCompat.getFont(context!!, R.font.myFont)!!) {
append(getString(...))
}
.bold{append(getString(...))}
textView.text = spannable
不要使用spannable.toString()
。
奖金:fontSize
Spannable
:
inline fun SpannableStringBuilder.fontSize(fontSize: Int, builderAction: SpannableStringBuilder.() -> Unit): SpannableStringBuilder {
return inSpans(AbsoluteSizeSpan(fontSize), builderAction = builderAction)
}