diff --git a/docs/api/style/appearance.md b/docs/api/style/appearance.md index 5877ac8c00b..4894ced4b9a 100644 --- a/docs/api/style/appearance.md +++ b/docs/api/style/appearance.md @@ -174,6 +174,8 @@ | ------ | -------- | --- | | number \| string | 否 | Android、iOS +> Android API 28 以下仅支持 `normal`(`400`) 和 `bold`(`700`)两种字重,其它字重效果需配合 `fontFamily` 实现。 + # letterSpacing 文本字符间距 diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java index b114f0c8521..a2e27d495a4 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInput.java @@ -19,10 +19,17 @@ import android.graphics.BlendMode; import android.graphics.BlendModeColorFilter; +import android.graphics.Paint; +import android.graphics.Typeface; +import androidx.annotation.Nullable; import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.uimanager.HippyViewBase; import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; import com.tencent.mtt.hippy.uimanager.RenderManager; +import com.tencent.renderer.NativeRender; +import com.tencent.renderer.NativeRendererManager; +import com.tencent.renderer.component.text.FontAdapter; +import com.tencent.renderer.component.text.TypeFaceUtil; import com.tencent.renderer.node.RenderNode; import com.tencent.mtt.hippy.utils.ContextHolder; import com.tencent.mtt.hippy.utils.LogUtils; @@ -57,6 +64,7 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import java.util.Objects; @SuppressWarnings({"deprecation", "unused"}) public class HippyTextInput extends AppCompatEditText implements HippyViewBase, @@ -75,6 +83,11 @@ public class HippyTextInput extends AppCompatEditText implements HippyViewBase, private int mLastRootViewVisibleHeight = -1; //当前RootView的上一次大小 private boolean mIsKeyBoardShow = false; //键盘是否在显示 private ReactContentSizeWatcher mReactContentSizeWatcher = null; + private boolean mItalic = false; + private int mFontWeight = TypeFaceUtil.WEIGHT_NORMAL; + @Nullable + private String mFontFamily; + private Paint mTextPaint; public HippyTextInput(Context context) { super(context); @@ -639,4 +652,53 @@ public void refreshSoftInput() { } } } + + public void setFontStyle(String style) { + if (TypeFaceUtil.TEXT_FONT_STYLE_ITALIC.equals(style) != mItalic) { + mItalic = !mItalic; + updateTypeface(); + } + } + + public void setFontFamily(String family) { + if (!Objects.equals(mFontFamily, family)) { + mFontFamily = family; + updateTypeface(); + } + } + + public void setFontWeight(String weight) { + int fontWeight; + if (TextUtils.isEmpty(weight) || TypeFaceUtil.TEXT_FONT_STYLE_NORMAL.equals(weight)) { + // case normal + fontWeight = TypeFaceUtil.WEIGHT_NORMAL; + } else if (TypeFaceUtil.TEXT_FONT_STYLE_BOLD.equals(weight)) { + // case bold + fontWeight = TypeFaceUtil.WEIGHT_BOLE; + } else { + // case number + try { + fontWeight = Math.min(Math.max(1, Integer.parseInt(weight)), 1000); + } catch (NumberFormatException ignored) { + fontWeight = TypeFaceUtil.WEIGHT_NORMAL; + } + } + if (fontWeight != mFontWeight) { + mFontWeight = fontWeight; + updateTypeface(); + } + } + + private void updateTypeface() { + if (mTextPaint == null) { + mTextPaint = new Paint(); + } else { + mTextPaint.reset(); + } + NativeRender nativeRenderer = NativeRendererManager.getNativeRenderer(getContext()); + FontAdapter fontAdapter = nativeRenderer == null ? null : nativeRenderer.getFontAdapter(); + TypeFaceUtil.apply(mTextPaint, mItalic, mFontWeight, mFontFamily, fontAdapter); + setTypeface(mTextPaint.getTypeface(), mTextPaint.isFakeBoldText() ? Typeface.BOLD : Typeface.NORMAL); + } + } diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java index 73db6e0fc0e..465efe4178a 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/textinput/HippyTextInputController.java @@ -208,66 +208,19 @@ public void setKeyboardType(HippyTextInput hippyTextInput, String keyboardType) hippyTextInput.refreshSoftInput(); } - private static int parseFontWeight(String fontWeightString) { - // This should be much faster than using regex to verify input and Integer.parseInt - return fontWeightString.length() == 3 && fontWeightString.endsWith("00") - && fontWeightString.charAt(0) <= '9' - && fontWeightString.charAt(0) >= '1' ? 100 * (fontWeightString.charAt(0) - '0') - : -1; - } - - @HippyControllerProps(name = NodeProps.FONT_STYLE, defaultType = HippyControllerProps.STRING, defaultString = "normal") + @HippyControllerProps(name = NodeProps.FONT_STYLE, defaultType = HippyControllerProps.STRING) public void setFontStyle(HippyTextInput view, String fontStyleString) { - if (TextUtils.isEmpty(fontStyleString)) { - return; - } - int fontStyle = -1; - if ("italic".equals(fontStyleString)) { - fontStyle = Typeface.ITALIC; - } else if ("normal".equals(fontStyleString)) { - fontStyle = Typeface.NORMAL; - } - - Typeface currentTypeface = view.getTypeface(); - if (currentTypeface == null) { - currentTypeface = Typeface.DEFAULT; - } - if (fontStyle != currentTypeface.getStyle()) { - view.setTypeface(currentTypeface, fontStyle); - } + view.setFontStyle(fontStyleString); } - @HippyControllerProps(name = NodeProps.FONT_WEIGHT, defaultType = HippyControllerProps.STRING, defaultString = "normal") + @HippyControllerProps(name = NodeProps.FONT_WEIGHT, defaultType = HippyControllerProps.STRING) public void setFontWeight(HippyTextInput view, String fontWeightString) { - int fontWeightNumeric = fontWeightString != null ? parseFontWeight(fontWeightString) : -1; - int fontWeight = -1; - if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) { - fontWeight = Typeface.BOLD; - } else //noinspection ConstantConditions - if ("normal".equals(fontWeightString) || (fontWeightNumeric != -1 - && fontWeightNumeric < 500)) { - fontWeight = Typeface.NORMAL; - } - Typeface currentTypeface = view.getTypeface(); - if (currentTypeface == null) { - currentTypeface = Typeface.DEFAULT; - } - if (fontWeight != currentTypeface.getStyle()) { - view.setTypeface(currentTypeface, fontWeight); - } + view.setFontWeight(fontWeightString); } - @HippyControllerProps(name = NodeProps.FONT_FAMILY, defaultType = HippyControllerProps.STRING, defaultString = "normal") + @HippyControllerProps(name = NodeProps.FONT_FAMILY, defaultType = HippyControllerProps.STRING) public void setFontFamily(HippyTextInput view, String fontFamily) { - if (TextUtils.isEmpty(fontFamily)) { - return; - } - int style = Typeface.NORMAL; - if (view.getTypeface() != null) { - style = view.getTypeface().getStyle(); - } - Typeface newTypeface = Typeface.create(fontFamily, style); - view.setTypeface(newTypeface); + view.setFontFamily(fontFamily); } private static final InputFilter[] EMPTY_FILTERS = new InputFilter[0]; diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextStyleSpan.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextStyleSpan.java index b69bc76f82a..f02c7d1e191 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextStyleSpan.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextStyleSpan.java @@ -21,14 +21,14 @@ public class TextStyleSpan extends MetricAffectingSpan { - private final int mStyle; + private final boolean mItalic; private final int mWeight; private final String mFontFamily; private final FontAdapter mFontAdapter; - public TextStyleSpan(int fontStyle, int fontWeight, String fontFamily, + public TextStyleSpan(boolean italic, int fontWeight, String fontFamily, FontAdapter adapter) { - mStyle = fontStyle; + mItalic = italic; mWeight = fontWeight; mFontFamily = fontFamily; mFontAdapter = adapter; @@ -36,11 +36,11 @@ public TextStyleSpan(int fontStyle, int fontWeight, String fontFamily, @Override public void updateDrawState(TextPaint textPaint) { - TypeFaceUtil.apply(textPaint, mStyle, mWeight, mFontFamily, mFontAdapter); + TypeFaceUtil.apply(textPaint, mItalic, mWeight, mFontFamily, mFontAdapter); } @Override public void updateMeasureState(TextPaint textPaint) { - TypeFaceUtil.apply(textPaint, mStyle, mWeight, mFontFamily, mFontAdapter); + TypeFaceUtil.apply(textPaint, mItalic, mWeight, mFontFamily, mFontAdapter); } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java index 1971746d376..ce7d5a415fe 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TypeFaceUtil.java @@ -18,8 +18,11 @@ import android.graphics.Paint; import android.graphics.Typeface; +import android.os.Build; import android.text.TextUtils; +import android.util.SparseArray; +import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.Nullable; import com.tencent.mtt.hippy.utils.ContextHolder; @@ -30,103 +33,153 @@ public class TypeFaceUtil { + public static final int WEIGHT_NORMAL = 400; + public static final int WEIGHT_BOLE = 700; + public static final String TEXT_FONT_STYLE_ITALIC = "italic"; + public static final String TEXT_FONT_STYLE_BOLD = "bold"; + public static final String TEXT_FONT_STYLE_NORMAL = "normal"; private static final String TAG = "TypeFaceUtil"; private static final String[] EXTENSIONS = {"", "_bold", "_italic", "_bold_italic"}; private static final String[] FONT_EXTENSIONS = {".ttf", ".otf"}; private static final String FONTS_PATH = "fonts/"; - private static final Map sFontCache = new HashMap<>(); + private static final Map> sFontCache = new HashMap<>(); + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) + private static final boolean SUPPORT_FONT_WEIGHT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; + /** + * @deprecated use {@link #getTypeface(String, int, boolean, FontAdapter)} instead + */ + @Deprecated public static Typeface getTypeface(String fontFamilyName, int style, @Nullable FontAdapter fontAdapter) { - Typeface typeface = - (fontAdapter != null) ? fontAdapter.getCustomTypeface(fontFamilyName, style) : null; + boolean italic = (style & Typeface.ITALIC) != 0; + int weightNumber = (style & Typeface.BOLD) != 0 ? WEIGHT_BOLE : WEIGHT_NORMAL; + return getTypeface(fontFamilyName, weightNumber, italic, fontAdapter); + } + + public static Typeface getTypeface(String fontFamilyName, int weight, boolean italic, + @Nullable FontAdapter fontAdapter) { + final int style = toStyle(weight, italic); + Typeface typeface = (fontAdapter != null) ? fontAdapter.getCustomTypeface(fontFamilyName, style) : null; if (typeface == null) { - String cache = fontFamilyName + style; - typeface = sFontCache.get(cache); - if (typeface == null) { - typeface = createTypeface(fontFamilyName, style, fontAdapter); + final int key = SUPPORT_FONT_WEIGHT ? ((weight << 1) | (italic ? 1 : 0)) : style; + SparseArray cache = sFontCache.get(fontFamilyName); + if (cache == null) { + cache = new SparseArray<>(4); + sFontCache.put(fontFamilyName, cache); + } else { + typeface = cache.get(key); } - if (typeface != null) { - sFontCache.put(cache, typeface); + if (typeface == null) { + typeface = createTypeface(fontFamilyName, weight, italic, fontAdapter); + if (typeface != null) { + cache.put(key, typeface); + } } } return typeface; } - private static Typeface createTypeface(String fontFamilyName, int style, + private static Typeface createTypeface(String fontFamilyName, int weight, boolean italic, @Nullable FontAdapter fontAdapter) { - Typeface typeface = null; + final int style = toStyle(weight, italic); final String extension = EXTENSIONS[style]; - for (String fileExtension : FONT_EXTENSIONS) { - String fileName = FONTS_PATH + fontFamilyName + extension + fileExtension; - try { - typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), - fileName); - if (typeface != null) { - return typeface; - } - } catch (Exception e) { - // If create type face from asset failed, other builder can also be used - LogUtils.w(TAG, e.getMessage()); - } - if (style == Typeface.NORMAL) { + final String[] familyNameList; + if (fontFamilyName.indexOf(',') == -1) { + familyNameList = new String[]{fontFamilyName}; + } else { + familyNameList = fontFamilyName.split("\\s*,\\s*"); + } + for (String splitName : familyNameList) { + if (TextUtils.isEmpty(splitName)) { continue; } - // try to load font file without extension - fileName = FONTS_PATH + fontFamilyName + fileExtension; - try { - typeface = Typeface.create( - Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), - fileName), style); - if (typeface != null) { - return typeface; + for (String fileExtension : FONT_EXTENSIONS) { + String fileName = FONTS_PATH + splitName + extension + fileExtension; + try { + Typeface typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); + if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { + return typeface; + } + } catch (Exception e) { + // If create type face from asset failed, other builder can also be used + LogUtils.w(TAG, e.getMessage()); } - } catch (Exception e) { - LogUtils.w(TAG, e.getMessage()); - } - } - if (typeface == null && fontAdapter != null) { - final String filePath = fontAdapter.getCustomFontFilePath(fontFamilyName, style); - if (!TextUtils.isEmpty(filePath)) { + if (style == Typeface.NORMAL) { + continue; + } + // try to load font file without extension + fileName = FONTS_PATH + splitName + fileExtension; try { - typeface = Typeface.createFromFile(filePath); + Typeface typeface = Typeface.createFromAsset(ContextHolder.getAppContext().getAssets(), fileName); + if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { + if (SUPPORT_FONT_WEIGHT) { + return Typeface.create(typeface, weight, italic); + } + // "bold" has no effect on api level < P, prefer to use `Paint.setFakeBoldText(boolean)` + return italic ? Typeface.create(typeface, Typeface.ITALIC) : typeface; + } } catch (Exception e) { - // If create type face from asset file, other builder can also be used LogUtils.w(TAG, e.getMessage()); } } + if (fontAdapter != null) { + final String filePath = fontAdapter.getCustomFontFilePath(splitName, style); + if (!TextUtils.isEmpty(filePath)) { + try { + Typeface typeface = Typeface.createFromFile(filePath); + if (typeface != null && !typeface.equals(Typeface.DEFAULT)) { + return typeface; + } + } catch (Exception e) { + // If create type face from asset file, other builder can also be used + LogUtils.w(TAG, e.getMessage()); + } + } + } } - if (typeface == null) { - typeface = Typeface.create(fontFamilyName, style); + + final Typeface systemDefault = Typeface.create(Typeface.DEFAULT, style); + for (String splitName : familyNameList) { + Typeface typeface = Typeface.create(splitName, style); + if (typeface != null && !typeface.equals(systemDefault)) { + return SUPPORT_FONT_WEIGHT ? Typeface.create(typeface, weight, italic) : typeface; + } } - return typeface; + return SUPPORT_FONT_WEIGHT ? Typeface.create(systemDefault, weight, italic) : systemDefault; + } + + private static int toStyle(int weight, boolean italic) { + return weight < WEIGHT_BOLE ? + (italic ? Typeface.ITALIC : Typeface.NORMAL) : + (italic ? Typeface.BOLD_ITALIC : Typeface.BOLD); } + /** + * @deprecated use {@link #apply(Paint, boolean, int, String, FontAdapter)} instead + */ + @Deprecated public static void apply(Paint paint, int style, int weight, String family, @Nullable FontAdapter fontAdapter) { - int oldStyle; - Typeface typeface = paint.getTypeface(); - if (typeface == null) { - oldStyle = 0; + boolean italic = style == Typeface.ITALIC; + int weightNumber = weight == Typeface.BOLD ? WEIGHT_BOLE : WEIGHT_NORMAL; + apply(paint, italic, weightNumber, family, fontAdapter); + } + + public static void apply(Paint paint, boolean italic, int weight, String familyName, + @Nullable FontAdapter fontAdapter) { + Typeface typeface; + if (TextUtils.isEmpty(familyName)) { + final Typeface base = paint.getTypeface(); + typeface = SUPPORT_FONT_WEIGHT + ? Typeface.create(base, weight, italic) + : Typeface.create(base, toStyle(weight, italic)); } else { - oldStyle = typeface.getStyle(); + typeface = getTypeface(familyName, weight, italic, fontAdapter); } - int want = 0; - if ((weight == Typeface.BOLD) || ((oldStyle & Typeface.BOLD) != 0)) { - want |= Typeface.BOLD; - } - if ((style == Typeface.ITALIC) || ((oldStyle & Typeface.ITALIC) != 0)) { - want |= Typeface.ITALIC; - } - if (family != null) { - typeface = TypeFaceUtil.getTypeface(family, want, fontAdapter); - } else if (typeface != null) { - typeface = Typeface.create(typeface, want); - } - if (typeface != null) { - paint.setTypeface(typeface); - } else { - paint.setTypeface(Typeface.defaultFromStyle(want)); + if (weight >= WEIGHT_BOLE && typeface != null && !typeface.isBold()) { + paint.setFakeBoldText(true); } + paint.setTypeface(typeface); } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java index eeaea5e552f..bc5b6826bd8 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java @@ -21,7 +21,6 @@ import static com.tencent.mtt.hippy.dom.node.NodeProps.TEXT_SHADOW_RADIUS; import android.graphics.Color; -import android.graphics.Typeface; import android.os.Build; import android.text.BidiFormatter; import android.text.BoringLayout; @@ -53,10 +52,12 @@ import com.tencent.renderer.component.text.TextShadowSpan; import com.tencent.renderer.component.text.TextStyleSpan; import com.tencent.renderer.component.text.TextVerticalAlignSpan; +import com.tencent.renderer.component.text.TypeFaceUtil; import com.tencent.renderer.utils.FlexUtils.FlexMeasureMode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Objects; public class TextVirtualNode extends VirtualNode { @@ -68,10 +69,7 @@ public class TextVirtualNode extends VirtualNode { public final static String V_ALIGN_BASELINE = "baseline"; public final static String V_ALIGN_BOTTOM = "bottom"; - private static final int TEXT_BOLD_MIN_VALUE = 500; private static final int TEXT_SHADOW_COLOR_DEFAULT = 0x55000000; - private static final String TEXT_FONT_STYLE_ITALIC = "italic"; - private static final String TEXT_FONT_STYLE_BOLD = "bold"; private static final String TEXT_DECORATION_UNDERLINE = "underline"; private static final String TEXT_DECORATION_LINE_THROUGH = "line-through"; private static final String MODE_HEAD = "head"; @@ -88,8 +86,8 @@ public class TextVirtualNode extends VirtualNode { protected int mColor = Color.BLACK; protected int mNumberOfLines; - protected int mFontStyle = Typeface.NORMAL; - protected int mFontWeight = Typeface.NORMAL; + protected boolean mItalic = false; + protected int mFontWeight = TypeFaceUtil.WEIGHT_NORMAL; protected int mFontSize = (int) Math.ceil(PixelUtil.dp2px(NodeProps.FONT_SIZE_SP)); protected int mShadowColor = TEXT_SHADOW_COLOR_DEFAULT; protected float mShadowOffsetDx = 0.0f; @@ -137,12 +135,10 @@ public TextVirtualNode(int rootId, int id, int pid, int index, @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.FONT_STYLE, defaultType = HippyControllerProps.STRING) public void setFontStyle(String style) { - if (TEXT_FONT_STYLE_ITALIC.equals(style)) { - mFontStyle = Typeface.ITALIC; - } else { - mFontStyle = Typeface.NORMAL; + if (TypeFaceUtil.TEXT_FONT_STYLE_ITALIC.equals(style) != mItalic) { + mItalic = !mItalic; + markDirty(); } - markDirty(); } @SuppressWarnings("unused") @@ -174,26 +170,34 @@ public void setFontSize(float size) { @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.FONT_FAMILY, defaultType = HippyControllerProps.STRING) public void setFontFamily(String family) { - mFontFamily = family; - markDirty(); + if (!Objects.equals(mFontFamily, family)) { + mFontFamily = family; + markDirty(); + } } @SuppressWarnings("unused") @HippyControllerProps(name = NodeProps.FONT_WEIGHT, defaultType = HippyControllerProps.STRING) public void setFontWeight(String weight) { - int fontWeight = 0; - if (!TextUtils.isEmpty(weight) && weight.length() == 3 - && weight.endsWith("00") - && weight.charAt(0) <= '9' - && weight.charAt(0) >= '1') { - fontWeight = 100 * (weight.charAt(0) - '0'); - } - if (fontWeight >= TEXT_BOLD_MIN_VALUE || TEXT_FONT_STYLE_BOLD.equals(weight)) { - mFontWeight = Typeface.BOLD; + int fontWeight; + if (TextUtils.isEmpty(weight) || TypeFaceUtil.TEXT_FONT_STYLE_NORMAL.equals(weight)) { + // case normal + fontWeight = TypeFaceUtil.WEIGHT_NORMAL; + } else if (TypeFaceUtil.TEXT_FONT_STYLE_BOLD.equals(weight)) { + // case bold + fontWeight = TypeFaceUtil.WEIGHT_BOLE; } else { - mFontWeight = Typeface.NORMAL; + // case number + try { + fontWeight = Math.min(Math.max(1, Integer.parseInt(weight)), 1000); + } catch (NumberFormatException ignored) { + fontWeight = TypeFaceUtil.WEIGHT_NORMAL; + } + } + if (fontWeight != mFontWeight) { + mFontWeight = fontWeight; + markDirty(); } - markDirty(); } @SuppressWarnings("unused") @@ -425,8 +429,7 @@ protected void createSpanOperationImpl(@NonNull List ops, size = (int) (size * mFontAdapter.getFontScale()); } ops.add(new SpanOperation(start, end, new AbsoluteSizeSpan(size))); - ops.add(new SpanOperation(start, end, - new TextStyleSpan(mFontStyle, mFontWeight, mFontFamily, mFontAdapter))); + ops.add(new SpanOperation(start, end, new TextStyleSpan(mItalic, mFontWeight, mFontFamily, mFontAdapter))); if (mHasUnderlineTextDecoration) { ops.add(new SpanOperation(start, end, new UnderlineSpan())); }