Переглянути джерело

修改登录隐私协议样式

Pq 3 роки тому
батько
коміт
f7b8a3e957

+ 1050 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/SpannableStringUtils.java

@@ -0,0 +1,1050 @@
+package com.cooleshow.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.MaskFilterSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IntDef;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+/**
+ * <pre>
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 16/12/13
+ *     desc  : SpannableString相关工具类
+ * </pre>
+ */
+public final class SpannableStringUtils {
+
+    private SpannableStringUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    public static final int ALIGN_BOTTOM = 0;
+
+    public static final int ALIGN_BASELINE = 1;
+
+    public static final int ALIGN_CENTER = 2;
+
+    public static final int ALIGN_TOP = 3;
+
+    @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Align {
+    }
+
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    public static class Builder {
+
+        private int defaultValue = 0x12000000;
+
+        private CharSequence text;
+        private int flag;
+        @ColorInt
+        private int foregroundColor;
+        @ColorInt
+        private int backgroundColor;
+        @ColorInt
+        private int quoteColor;
+        private int stripeWidth;
+        private int quoteGapWidth;
+        private boolean isLeadingMargin;
+        private int first;
+        private int rest;
+        private int margin;
+        private boolean isBullet;
+        private int bulletColor;
+        private int bulletRadius;
+        private int bulletGapWidth;
+        private int fontSize;
+        private boolean fontSizeIsDp;
+        private float proportion;
+        private float xProportion;
+        private boolean isStrikethrough;
+        private boolean isUnderline;
+        private boolean isSuperscript;
+        private boolean isSubscript;
+        private boolean isBold;
+        private boolean isItalic;
+        private boolean isBoldItalic;
+        private String fontFamily;
+        private Typeface typeface;
+        private Alignment alignment;
+        private boolean imageIsBitmap;
+        private Bitmap bitmap;
+        private boolean imageIsDrawable;
+        private Drawable drawable;
+        private boolean imageIsUri;
+        private Uri uri;
+        private boolean imageIsResourceId;
+        @DrawableRes
+        private int resourceId;
+        @Align
+        int align;
+
+        private ClickableSpan clickSpan;
+        private String url;
+
+        private boolean isBlur;
+        private float blurRadius;
+        private BlurMaskFilter.Blur style;
+
+        private SpannableStringBuilder mBuilder;
+
+        public Builder() {
+            flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
+            foregroundColor = defaultValue;
+            backgroundColor = defaultValue;
+            quoteColor = defaultValue;
+            margin = -1;
+            fontSize = -1;
+            proportion = -1;
+            xProportion = -1;
+            align = ALIGN_BOTTOM;
+            mBuilder = new SpannableStringBuilder();
+        }
+
+        /**
+         * 设置标识
+         *
+         * @param flag <ul>
+         *             <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li>
+         *             <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li>
+         *             <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li>
+         *             <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li>
+         *             </ul>
+         * @return {@link Builder}
+         */
+        public Builder setFlag(int flag) {
+            this.flag = flag;
+            return this;
+        }
+
+        /**
+         * 设置前景色
+         *
+         * @param color 前景色
+         * @return {@link Builder}
+         */
+        public Builder setForegroundColor(@ColorInt int color) {
+            this.foregroundColor = color;
+            return this;
+        }
+
+        /**
+         * 设置背景色
+         *
+         * @param color 背景色
+         * @return {@link Builder}
+         */
+        public Builder setBackgroundColor(@ColorInt int color) {
+            this.backgroundColor = color;
+            return this;
+        }
+
+        /**
+         * 设置引用线的颜色
+         *
+         * @param color 引用线的颜色
+         * @return {@link Builder}
+         */
+        public Builder setQuoteColor(@ColorInt int color) {
+            this.quoteColor = color;
+            this.stripeWidth = 2;
+            this.quoteGapWidth = 2;
+            return this;
+        }
+
+        /**
+         * 设置引用线的颜色
+         *
+         * @param color         引用线的颜色
+         * @param stripeWidth   引用线线宽
+         * @param quoteGapWidth 引用线和文字间距
+         * @return {@link Builder}
+         */
+        public Builder setQuoteColor(@ColorInt int color, int stripeWidth, int quoteGapWidth) {
+            this.quoteColor = color;
+            this.stripeWidth = stripeWidth;
+            this.quoteGapWidth = quoteGapWidth;
+            return this;
+        }
+
+        /**
+         * 设置缩进
+         *
+         * @param first 首行缩进
+         * @param rest  剩余行缩进
+         * @return {@link Builder}
+         */
+        public Builder setLeadingMargin(int first, int rest) {
+            this.first = first;
+            this.rest = rest;
+            isLeadingMargin = true;
+            return this;
+        }
+
+        /**
+         * 设置间距
+         *
+         * @param margin 间距
+         * @return {@link Builder}
+         */
+        public Builder setMargin(int margin) {
+            this.margin = margin;
+            this.text = " " + this.text;
+            return this;
+        }
+
+        /**
+         * 设置列表标记
+         *
+         * @param gapWidth 列表标记和文字间距离
+         * @return {@link Builder}
+         */
+        public Builder setBullet(@ColorInt int gapWidth) {
+            this.bulletColor = 0;
+            this.bulletRadius = 3;
+            this.bulletGapWidth = gapWidth;
+            isBullet = true;
+            return this;
+        }
+
+        /**
+         * 设置列表标记
+         *
+         * @param color    列表标记的颜色
+         * @param radius   列表标记颜色
+         * @param gapWidth 列表标记和文字间距离
+         * @return {@link Builder}
+         */
+        public Builder setBullet(@ColorInt int color, int radius, int gapWidth) {
+            this.bulletColor = color;
+            this.bulletRadius = radius;
+            this.bulletGapWidth = gapWidth;
+            isBullet = true;
+            return this;
+        }
+
+        /**
+         * 设置字体尺寸
+         *
+         * @param size 尺寸
+         * @return {@link Builder}
+         */
+        public Builder setFontSize(int size) {
+            this.fontSize = size;
+            this.fontSizeIsDp = false;
+            return this;
+        }
+
+        /**
+         * 设置字体尺寸
+         *
+         * @param size 尺寸
+         * @param isDp 是否使用dip
+         * @return {@link Builder}
+         */
+        public Builder setFontSize(int size, boolean isDp) {
+            this.fontSize = size;
+            this.fontSizeIsDp = isDp;
+            return this;
+        }
+
+        /**
+         * 设置字体比例
+         *
+         * @param proportion 比例
+         * @return {@link Builder}
+         */
+        public Builder setFontProportion(float proportion) {
+            this.proportion = proportion;
+            return this;
+        }
+
+        /**
+         * 设置字体横向比例
+         *
+         * @param proportion 比例
+         * @return {@link Builder}
+         */
+        public Builder setFontXProportion(float proportion) {
+            this.xProportion = proportion;
+            return this;
+        }
+
+        /**
+         * 设置删除线
+         *
+         * @return {@link Builder}
+         */
+        public Builder setStrikethrough() {
+            this.isStrikethrough = true;
+            return this;
+        }
+
+        /**
+         * 设置下划线
+         *
+         * @return {@link Builder}
+         */
+        public Builder setUnderline() {
+            this.isUnderline = true;
+            return this;
+        }
+
+        /**
+         * 设置上标
+         *
+         * @return {@link Builder}
+         */
+        public Builder setSuperscript() {
+            this.isSuperscript = true;
+            return this;
+        }
+
+        /**
+         * 设置下标
+         *
+         * @return {@link Builder}
+         */
+        public Builder setSubscript() {
+            this.isSubscript = true;
+            return this;
+        }
+
+        /**
+         * 设置粗体
+         *
+         * @return {@link Builder}
+         */
+        public Builder setBold() {
+            isBold = true;
+            return this;
+        }
+
+        /**
+         * 设置斜体
+         *
+         * @return {@link Builder}
+         */
+        public Builder setItalic() {
+            isItalic = true;
+            return this;
+        }
+
+        /**
+         * 设置粗斜体
+         *
+         * @return {@link Builder}
+         */
+        public Builder setBoldItalic() {
+            isBoldItalic = true;
+            return this;
+        }
+
+        /**
+         * 设置字体系列
+         *
+         * @param fontFamily 字体系列
+         *                   <ul>
+         *                   <li>monospace</li>
+         *                   <li>serif</li>
+         *                   <li>sans-serif</li>
+         *                   </ul>
+         * @return {@link Builder}
+         */
+        public Builder setFontFamily(@NonNull String fontFamily) {
+            this.fontFamily = fontFamily;
+            return this;
+        }
+
+        /**
+         * 设置字体
+         *
+         * @param typeface 字体
+         * @return {@link Builder}
+         */
+        public Builder setTypeface(@NonNull Typeface typeface) {
+            this.typeface = typeface;
+            return this;
+        }
+
+        /**
+         * 设置对齐
+         *
+         * @param alignment 对其方式
+         *                  <ul>
+         *                  <li>{@link Alignment#ALIGN_NORMAL}正常</li>
+         *                  <li>{@link Alignment#ALIGN_OPPOSITE}相反</li>
+         *                  <li>{@link Alignment#ALIGN_CENTER}居中</li>
+         *                  </ul>
+         * @return {@link Builder}
+         */
+        public Builder setAlign(@NonNull Alignment alignment) {
+            this.alignment = alignment;
+            return this;
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param bitmap 图片位图
+         * @return {@link Builder}
+         */
+        public Builder setBitmap(@NonNull Bitmap bitmap) {
+            return setBitmap(bitmap, align);
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param bitmap 图片位图
+         * @param align  对齐
+         *               <ul>
+         *               <li>{@link Align#ALIGN_TOP}顶部对齐</li>
+         *               <li>{@link Align#ALIGN_CENTER}居中对齐</li>
+         *               <li>{@link Align#ALIGN_BASELINE}基线对齐</li>
+         *               <li>{@link Align#ALIGN_BOTTOM}底部对齐</li>
+         *               </ul>
+         * @return {@link Builder}
+         */
+        public Builder setBitmap(@NonNull Bitmap bitmap, @Align int align) {
+            this.bitmap = bitmap;
+            this.align = align;
+            this.text = " " + this.text;
+            imageIsBitmap = true;
+            return this;
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param drawable 图片资源
+         * @return {@link Builder}
+         */
+        public Builder setDrawable(@NonNull Drawable drawable) {
+            return setDrawable(drawable, align);
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param drawable 图片资源
+         * @param align    对齐
+         *                 <ul>
+         *                 <li>{@link Align#ALIGN_TOP}顶部对齐</li>
+         *                 <li>{@link Align#ALIGN_CENTER}居中对齐</li>
+         *                 <li>{@link Align#ALIGN_BASELINE}基线对齐</li>
+         *                 <li>{@link Align#ALIGN_BOTTOM}底部对齐</li>
+         *                 </ul>
+         * @return {@link Builder}
+         */
+        public Builder setDrawable(@NonNull Drawable drawable, @Align int align) {
+            this.drawable = drawable;
+            this.align = align;
+            this.text = " " + this.text;
+            imageIsDrawable = true;
+            return this;
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param uri 图片uri
+         * @return {@link Builder}
+         */
+        public Builder setUri(@NonNull Uri uri) {
+            setUri(uri, ALIGN_BOTTOM);
+            return this;
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param uri   图片uri
+         * @param align 对齐
+         *              <ul>
+         *              <li>{@link Align#ALIGN_TOP}顶部对齐</li>
+         *              <li>{@link Align#ALIGN_CENTER}居中对齐</li>
+         *              <li>{@link Align#ALIGN_BASELINE}基线对齐</li>
+         *              <li>{@link Align#ALIGN_BOTTOM}底部对齐</li>
+         *              </ul>
+         * @return {@link Builder}
+         */
+        public Builder setUri(@NonNull Uri uri, @Align int align) {
+            this.uri = uri;
+            this.align = align;
+            this.text = " " + this.text;
+            imageIsUri = true;
+            return this;
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param resourceId 图片资源id
+         * @return {@link Builder}
+         */
+        public Builder setResourceId(@DrawableRes int resourceId) {
+            return setResourceId(resourceId, align);
+        }
+
+        /**
+         * 设置图片
+         *
+         * @param resourceId 图片资源id
+         * @param align      对齐
+         * @return {@link Builder}
+         */
+        public Builder setResourceId(@DrawableRes int resourceId, @Align int align) {
+            this.resourceId = resourceId;
+            this.align = align;
+            this.text = " " + this.text;
+            imageIsResourceId = true;
+            return this;
+        }
+
+        /**
+         * 设置点击事件
+         * <p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p>
+         *
+         * @param clickSpan 点击事件
+         * @return {@link Builder}
+         */
+        public Builder setClickSpan(@NonNull ClickableSpan clickSpan) {
+            this.clickSpan = clickSpan;
+            return this;
+        }
+
+        /**
+         * 设置超链接
+         * <p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p>
+         *
+         * @param url 超链接
+         * @return {@link Builder}
+         */
+        public Builder setUrl(@NonNull String url) {
+            this.url = url;
+            return this;
+        }
+
+        /**
+         * 设置模糊
+         * <p>尚存bug,其他地方存在相同的字体的话,相同字体出现在之前的话那么就不会模糊,出现在之后的话那会一起模糊</p>
+         * <p>推荐还是把所有字体都模糊这样使用</p>
+         *
+         * @param radius 模糊半径(需大于0)
+         * @param style  模糊样式<ul>
+         *               <li>{@link BlurMaskFilter.Blur#NORMAL}</li>
+         *               <li>{@link BlurMaskFilter.Blur#SOLID}</li>
+         *               <li>{@link BlurMaskFilter.Blur#OUTER}</li>
+         *               <li>{@link BlurMaskFilter.Blur#INNER}</li>
+         *               </ul>
+         * @return {@link Builder}
+         */
+        public Builder setBlur(float radius, BlurMaskFilter.Blur style) {
+            this.blurRadius = radius;
+            this.style = style;
+            this.isBlur = true;
+            return this;
+        }
+
+        /**
+         * 追加样式一行字符串
+         *
+         * @param text 样式字符串文本
+         * @return {@link Builder}
+         */
+        public Builder appendLine(@NonNull CharSequence text) {
+            return append(text + LINE_SEPARATOR);
+        }
+
+        /**
+         * 追加样式字符串
+         *
+         * @param text 样式字符串文本
+         * @return {@link Builder}
+         */
+        public Builder append(@NonNull CharSequence text) {
+            setSpan();
+            this.text = text;
+            return this;
+        }
+
+        /**
+         * 创建样式字符串
+         *
+         * @return 样式字符串
+         */
+        public SpannableStringBuilder create() {
+            setSpan();
+            return mBuilder;
+        }
+
+        /**
+         * 设置样式
+         */
+        private void setSpan() {
+            if (text == null || text.length() == 0) return;
+            int start = mBuilder.length();
+            mBuilder.append(this.text);
+            int end = mBuilder.length();
+            if (backgroundColor != defaultValue) {
+                mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
+                backgroundColor = defaultValue;
+            }
+            if (foregroundColor != defaultValue) {
+                mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
+                foregroundColor = defaultValue;
+            }
+            if (isLeadingMargin) {
+                mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
+                isLeadingMargin = false;
+            }
+            if (margin != -1) {
+                mBuilder.setSpan(new MarginSpan(margin), start, end, flag);
+                margin = -1;
+            }
+            if (quoteColor != defaultValue) {
+                mBuilder.setSpan(new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth), start, end, flag);
+                quoteColor = defaultValue;
+            }
+            if (isBullet) {
+                mBuilder.setSpan(new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth), start, end, flag);
+                isBullet = false;
+            }
+            if (fontSize != -1) {
+                mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
+                fontSize = -1;
+                fontSizeIsDp = false;
+            }
+            if (proportion != -1) {
+                mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
+                proportion = -1;
+            }
+            if (xProportion != -1) {
+                mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
+                xProportion = -1;
+            }
+            if (isStrikethrough) {
+                mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
+                isStrikethrough = false;
+            }
+            if (isUnderline) {
+                mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
+                isUnderline = false;
+            }
+            if (isSuperscript) {
+                mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
+                isSuperscript = false;
+            }
+            if (isSubscript) {
+                mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
+                isSubscript = false;
+            }
+            if (isBold) {
+                mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
+                isBold = false;
+            }
+            if (isItalic) {
+                mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
+                isItalic = false;
+            }
+            if (isBoldItalic) {
+                mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
+                isBoldItalic = false;
+            }
+            if (fontFamily != null) {
+                mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
+                fontFamily = null;
+            }
+            if (typeface != null) {
+                mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
+                typeface = null;
+            }
+            if (alignment != null) {
+                mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
+                alignment = null;
+            }
+            if (imageIsBitmap || imageIsDrawable || imageIsUri || imageIsResourceId) {
+                if (imageIsBitmap) {
+                    mBuilder.setSpan(new CustomImageSpan(Utils.getApp(), bitmap, align), start, end, flag);
+                    bitmap = null;
+                    imageIsBitmap = false;
+                } else if (imageIsDrawable) {
+                    mBuilder.setSpan(new CustomImageSpan(drawable, align), start, end, flag);
+                    drawable = null;
+                    imageIsDrawable = false;
+                } else if (imageIsUri) {
+                    mBuilder.setSpan(new CustomImageSpan(Utils.getApp(), uri, align), start, end, flag);
+                    uri = null;
+                    imageIsUri = false;
+                } else {
+                    mBuilder.setSpan(new CustomImageSpan(Utils.getApp(), resourceId, align), start, end, flag);
+                    resourceId = 0;
+                    imageIsResourceId = false;
+                }
+            }
+            if (clickSpan != null) {
+                mBuilder.setSpan(clickSpan, start, end, flag);
+                clickSpan = null;
+            }
+            if (url != null) {
+                mBuilder.setSpan(new URLSpan(url), start, end, flag);
+                url = null;
+            }
+            if (isBlur) {
+                mBuilder.setSpan(new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)), start, end, flag);
+                isBlur = false;
+            }
+            flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
+        }
+    }
+
+    static class MarginSpan extends ReplacementSpan {
+
+        private final int margin;
+
+        private MarginSpan(int margin) {
+            super();
+            this.margin = margin;
+        }
+
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) {
+            text = " ";
+            return margin;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
+
+        }
+    }
+
+    static class CustomQuoteSpan implements LeadingMarginSpan {
+
+        private final int color;
+        private final int stripeWidth;
+        private final int gapWidth;
+
+        private CustomQuoteSpan(@ColorInt int color, int stripeWidth, int gapWidth) {
+            super();
+            this.color = color;
+            this.stripeWidth = stripeWidth;
+            this.gapWidth = gapWidth;
+        }
+
+        public int getLeadingMargin(boolean first) {
+            return stripeWidth + gapWidth;
+        }
+
+        public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
+                                      int top, int baseline, int bottom,
+                                      CharSequence text, int start, int end,
+                                      boolean first, Layout layout) {
+            Paint.Style style = p.getStyle();
+            int color = p.getColor();
+
+            p.setStyle(Paint.Style.FILL);
+            p.setColor(this.color);
+
+            c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
+
+            p.setStyle(style);
+            p.setColor(color);
+        }
+    }
+
+    static class CustomBulletSpan implements LeadingMarginSpan {
+
+        private final int color;
+        private final int radius;
+        private final int gapWidth;
+
+        private static Path sBulletPath = null;
+
+        private CustomBulletSpan(int color, int radius, int gapWidth) {
+            this.color = color;
+            this.radius = radius;
+            this.gapWidth = gapWidth;
+        }
+
+        public int getLeadingMargin(boolean first) {
+            return 2 * radius + gapWidth;
+        }
+
+        public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
+                                      int top, int baseline, int bottom,
+                                      CharSequence text, int start, int end,
+                                      boolean first, Layout l) {
+            if (((Spanned) text).getSpanStart(this) == start) {
+                Paint.Style style = p.getStyle();
+                int oldColor = 0;
+                oldColor = p.getColor();
+                p.setColor(color);
+                p.setStyle(Paint.Style.FILL);
+                if (c.isHardwareAccelerated()) {
+                    if (sBulletPath == null) {
+                        sBulletPath = new Path();
+                        // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
+                        sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
+                    }
+                    c.save();
+                    c.translate(x + dir * radius, (top + bottom) / 2.0f);
+                    c.drawPath(sBulletPath, p);
+                    c.restore();
+                } else {
+                    c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
+                }
+                p.setColor(oldColor);
+                p.setStyle(style);
+            }
+        }
+    }
+
+    @SuppressLint("ParcelCreator")
+    static class CustomTypefaceSpan extends TypefaceSpan {
+
+        private final Typeface newType;
+
+        private CustomTypefaceSpan(Typeface type) {
+            super("");
+            newType = type;
+        }
+
+        @Override
+        public void updateDrawState(TextPaint textPaint) {
+            apply(textPaint, newType);
+        }
+
+        @Override
+        public void updateMeasureState(TextPaint paint) {
+            apply(paint, newType);
+        }
+
+        private static void apply(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);
+        }
+    }
+
+    static class CustomImageSpan extends CustomDynamicDrawableSpan {
+        private Drawable mDrawable;
+        private Uri mContentUri;
+        private int mResourceId;
+        private Context mContext;
+
+        CustomImageSpan(Context context, Bitmap b, int verticalAlignment) {
+            super(verticalAlignment);
+            mContext = context;
+            mDrawable = context != null
+                    ? new BitmapDrawable(context.getResources(), b)
+                    : new BitmapDrawable(b);
+            int width = mDrawable.getIntrinsicWidth();
+            int height = mDrawable.getIntrinsicHeight();
+            mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
+        }
+
+        CustomImageSpan(Drawable d, int verticalAlignment) {
+            super(verticalAlignment);
+            mDrawable = d;
+            mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(),
+                    mDrawable.getIntrinsicHeight());
+        }
+
+        CustomImageSpan(Context context, Uri uri, int verticalAlignment) {
+            super(verticalAlignment);
+            mContext = context;
+            mContentUri = uri;
+        }
+
+        CustomImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) {
+            super(verticalAlignment);
+            mContext = context;
+            mResourceId = resourceId;
+        }
+
+        @Override
+        public Drawable getDrawable() {
+            Drawable drawable = null;
+            if (mDrawable != null) {
+                drawable = mDrawable;
+            } else if (mContentUri != null) {
+                Bitmap bitmap = null;
+                try {
+                    InputStream is = mContext.getContentResolver().openInputStream(
+                            mContentUri);
+                    bitmap = BitmapFactory.decodeStream(is);
+                    drawable = new BitmapDrawable(mContext.getResources(), bitmap);
+                    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
+                            drawable.getIntrinsicHeight());
+                    if (is != null) {
+                        is.close();
+                    }
+                } catch (Exception e) {
+                    Log.e("sms", "Failed to loaded content " + mContentUri, e);
+                }
+            } else {
+                try {
+                    drawable = ContextCompat.getDrawable(mContext, mResourceId);
+                    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
+                            drawable.getIntrinsicHeight());
+                } catch (Exception e) {
+                    Log.e("sms", "Unable to find resource: " + mResourceId);
+                }
+            }
+            return drawable;
+        }
+    }
+
+    static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
+
+        static final int ALIGN_BOTTOM = 0;
+
+        static final int ALIGN_BASELINE = 1;
+
+        static final int ALIGN_CENTER = 2;
+
+        static final int ALIGN_TOP = 3;
+
+        final int mVerticalAlignment;
+
+        CustomDynamicDrawableSpan() {
+            mVerticalAlignment = ALIGN_BOTTOM;
+        }
+
+        CustomDynamicDrawableSpan(int verticalAlignment) {
+            mVerticalAlignment = verticalAlignment;
+        }
+
+        public abstract Drawable getDrawable();
+
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text,
+                           int start, int end,
+                           Paint.FontMetricsInt fm) {
+            Drawable d = getCachedDrawable();
+            Rect rect = d.getBounds();
+            final int fontHeight = (int) (paint.getFontMetrics().descent - paint.getFontMetrics().ascent);
+            if (fm != null) { // this is the fucking code which I waste 3 days
+                if (rect.height() > fontHeight) {
+                    if (mVerticalAlignment == ALIGN_TOP) {
+                        fm.descent += rect.height() - fontHeight;
+                    } else if (mVerticalAlignment == ALIGN_CENTER) {
+                        fm.ascent -= (rect.height() - fontHeight) / 2;
+                        fm.descent += (rect.height() - fontHeight) / 2;
+                    } else if (mVerticalAlignment == ALIGN_BASELINE) {
+                        fm.ascent -= rect.height() - fontHeight + fm.descent;
+                    } else {
+                        fm.ascent -= rect.height() - fontHeight;
+                    }
+                }
+            }
+
+            return rect.right;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text,
+                         int start, int end, float x,
+                         int top, int y, int bottom, @NonNull Paint paint) {
+            Drawable d = getCachedDrawable();
+            Rect rect = d.getBounds();
+            canvas.save();
+            final float fontHeight = paint.getFontMetrics().descent - paint.getFontMetrics().ascent;
+            int transY = bottom - rect.bottom;
+            if (rect.height() < fontHeight) { // this is the fucking code which I waste 3 days
+                if (mVerticalAlignment == ALIGN_BASELINE) {
+                    transY -= paint.getFontMetricsInt().descent;
+                } else if (mVerticalAlignment == ALIGN_CENTER) {
+                    transY -= (fontHeight - rect.height()) / 2;
+                } else if (mVerticalAlignment == ALIGN_TOP) {
+                    transY -= fontHeight - rect.height();
+                }
+            } else {
+                if (mVerticalAlignment == ALIGN_BASELINE) {
+                    transY -= paint.getFontMetricsInt().descent;
+                }
+            }
+            canvas.translate(x, transY);
+            d.draw(canvas);
+            canvas.restore();
+        }
+
+        private Drawable getCachedDrawable() {
+            WeakReference<Drawable> wr = mDrawableRef;
+            Drawable d = null;
+            if (wr != null)
+                d = wr.get();
+            if (d == null) {
+                d = getDrawable();
+                mDrawableRef = new WeakReference<>(d);
+            }
+            return getDrawable();
+        }
+        private WeakReference<Drawable> mDrawableRef;
+    }
+}

+ 35 - 0
usercenter/src/main/java/com/cooleshow/usercenter/ui/activity/LoginActivity.kt

@@ -1,14 +1,21 @@
 package com.cooleshow.usercenter.ui.activity
 
 import android.content.Intent
+import android.graphics.Color
 import android.os.Bundle
+import android.text.TextPaint
 import android.text.TextUtils
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.view.View
 import com.alibaba.android.arouter.facade.annotation.Route
 import com.cooleshow.base.ext.onClick
 import com.cooleshow.base.ui.activity.BaseMVPActivity
+import com.cooleshow.base.utils.SpannableStringUtils
 import com.cooleshow.base.utils.ToastUtils
 import com.cooleshow.base.utils.helper.QMUIStatusBarHelper
 import com.cooleshow.provider.router.RouterPath
+import com.cooleshow.usercenter.R
 import com.cooleshow.usercenter.UserConstants
 import com.cooleshow.usercenter.bean.UserInfo
 import com.cooleshow.usercenter.databinding.ActivityLoginBinding
@@ -38,10 +45,12 @@ class LoginActivity : BaseMVPActivity<ActivityLoginBinding, LoginPresenter>(),
         tv_login_by_code.onClick {
             finish()
         }
+
         var phone: String? = intent.getStringExtra(UserConstants.PHONE_NUM_KEY);
         if (!TextUtils.isEmpty(phone)) {
             viewBinding.etPhoneNum.setText(phone);
         }
+        setPrivacyText()
         mLoginBtn.setOnClickListener {
             if (TextUtils.isEmpty(et_phone_num?.text)) {
                 ToastUtils.showShort("手机号不可为空")
@@ -72,4 +81,30 @@ class LoginActivity : BaseMVPActivity<ActivityLoginBinding, LoginPresenter>(),
         setResult(RESULT_OK, intent)
         finish()
     }
+
+    private fun setPrivacyText() {
+        viewBinding.tvPrivacy.movementMethod = LinkMovementMethod.getInstance()
+        val builder = SpannableStringUtils.Builder()
+        viewBinding.tvPrivacy.highlightColor = Color.TRANSPARENT
+        builder.append(getString(R.string.login_privacy_tip))
+            .setClickSpan(object : ClickableSpan() {
+                override fun onClick(view: View) {}
+                override fun updateDrawState(ds: TextPaint) {
+                    super.updateDrawState(ds)
+                    ds.color = resources.getColor(com.cooleshow.base.R.color.color_2dc7aa)
+                    ds.isUnderlineText = false
+                }
+            }).append("和")
+            .setForegroundColor(resources.getColor(com.cooleshow.base.R.color.color_999999))
+            .append(getString(R.string.login_privacy_tip2))
+            .setClickSpan(object : ClickableSpan() {
+                override fun onClick(view: View) {}
+                override fun updateDrawState(ds: TextPaint) {
+                    super.updateDrawState(ds)
+                    ds.color = resources.getColor(com.cooleshow.base.R.color.color_2dc7aa)
+                    ds.isUnderlineText = false
+                }
+            })
+        viewBinding.tvPrivacy.text = builder.create()
+    }
 }

+ 38 - 0
usercenter/src/main/java/com/cooleshow/usercenter/ui/activity/VerifyCodeLoginActivity.java

@@ -1,14 +1,19 @@
 package com.cooleshow.usercenter.ui.activity;
 
 import android.content.Intent;
+import android.graphics.Color;
 import android.os.Bundle;
+import android.text.TextPaint;
 import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
 import android.view.View;
 
 import com.alibaba.android.arouter.facade.annotation.Route;
 import com.alibaba.android.arouter.launcher.ARouter;
 import com.cooleshow.base.common.AppManager;
 import com.cooleshow.base.ui.activity.BaseMVPActivity;
+import com.cooleshow.base.utils.SpannableStringUtils;
 import com.cooleshow.base.utils.ToastUtils;
 import com.cooleshow.base.utils.helper.QMUIStatusBarHelper;
 import com.cooleshow.provider.router.RouterPath;
@@ -46,6 +51,7 @@ public class VerifyCodeLoginActivity extends BaseMVPActivity<ActivityVerifyCodeL
     protected void initView() {
         viewBinding.tvSendVerifyCode.setOnClickListener(this);
         viewBinding.tvLoginByCode.setOnClickListener(this);
+        setPrivacyText();
     }
 
 
@@ -88,4 +94,36 @@ public class VerifyCodeLoginActivity extends BaseMVPActivity<ActivityVerifyCodeL
             }
         }
     }
+
+    private void setPrivacyText() {
+        viewBinding.tvPrivacy.setMovementMethod(LinkMovementMethod.getInstance());
+        SpannableStringUtils.Builder builder = new SpannableStringUtils.Builder();
+        viewBinding.tvPrivacy.setHighlightColor(Color.TRANSPARENT);
+        builder.append(getString(R.string.login_privacy_tip))
+                .setClickSpan(new ClickableSpan() {
+                    @Override
+                    public void onClick(View view) {
+
+                    }
+
+                    public void updateDrawState(@NonNull TextPaint ds) {
+                        super.updateDrawState(ds);
+                        ds.setColor(getResources().getColor(com.cooleshow.base.R.color.color_2dc7aa));
+                        ds.setUnderlineText(false);
+                    }
+                }).append("和").setForegroundColor(getResources().getColor(com.cooleshow.base.R.color.color_999999))
+                .append(getString(R.string.login_privacy_tip2))
+                .setClickSpan(new ClickableSpan() {
+                    @Override
+                    public void onClick(View view) {
+                    }
+
+                    public void updateDrawState(@NonNull TextPaint ds) {
+                        super.updateDrawState(ds);
+                        ds.setColor(getResources().getColor(com.cooleshow.base.R.color.color_2dc7aa));
+                        ds.setUnderlineText(false);
+                    }
+                });
+        viewBinding.tvPrivacy.setText(builder.create());
+    }
 }

+ 1 - 0
usercenter/src/main/res/layout/activity_login.xml

@@ -156,6 +156,7 @@
         app:layout_constraintTop_toBottomOf="@+id/tv_login_by_code" />
 
     <TextView
+        android:id="@+id/tv_privacy"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:includeFontPadding="false"

+ 1 - 0
usercenter/src/main/res/layout/activity_verify_code_login.xml

@@ -132,6 +132,7 @@
         app:layout_constraintTop_toBottomOf="@+id/tv_login_by_code" />
 
     <TextView
+        android:id="@+id/tv_privacy"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:includeFontPadding="false"

+ 2 - 0
usercenter/src/main/res/values/strings.xml

@@ -18,4 +18,6 @@
     <string name="please_input_pwd2">请输入您的登录密码</string>
     <string name="please_input_pwd3">请再次输入您的登录密码</string>
     <string name="input_support">1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM.~!?@#$%^*()_=|</string>
+    <string name="login_privacy_tip">《用户注册协议》</string>
+    <string name="login_privacy_tip2">《隐私政策》</string>
 </resources>