Android自定義控件之SpanTextView

LasBozeman 8年前發布 | 7K 次閱讀 安卓開發 Android開發 移動開發

先看效果圖

是不是沒有看出來什么,不要著急,往下看

概述

不可質疑,在android中的TextView是個非常強大的控件,作為一個幾年的developer,還是對其知之甚少;最近公司的一個新項目中有這樣一個gui需求, 在一行text的顯示中顯示幾個標簽,類似于效果圖中的邊框,要怎么實現呢;總不可能在添加一個TextView顯示吧,首先因為標簽個數不定,其次這樣實現真的 很low,那么怎么辦呢,我們肯定會想到android中的SpannableString,這個東西能通過設置span很好的渲染TextView中的局部內容,于是乎自己查了些資料 ,對于我這種懶人,我被驚訝到了,我的天啊,怎么會有這么多的Span定義,有字體、顏色、背景色、大小等等。而且在使用的時候需要指定是什么位置 的text,為了以后工作方便,于是決定,進一步封裝;效果和官方定義的效果差不多(如效果圖),下面我們就介紹封裝過程和使用方法。

優缺點

優點

  • 涵蓋了大部分的Span
  • 可以方便對不同Span組合應用
  • 不需要去數對第幾個字符設置效果
  • 添加了設置背景的功能
  • 可以方便設置自定義的Span

缺點

  • 和TextView結合不是很好,只能依賴append方法
  • 對固定的text引用不佳

實現源碼

  • 集成自TextView并添加一個spanedable方法
public Spanedable spanedable(CharSequence text) {
        return new Spanedable(text);
    }
  • 添加內部類Spanedable,并添加如下屬性
private final Map<Object, Integer> spans = new HashMap<>(); //存儲組合span
    SpannableStringBuilder ss; //
    CharSequence text; //需要渲染的text
  • 內部類Spanedable,構造方法為default,不能外部創建其對象
Spanedable(CharSequence text) {
        this.text = text;
        this.ss = new SpannableStringBuilder(text);
    }
  • 大部分的span實現都是一致的,這里以StyleSpan和TextAppearanceSpan為例
/**
         * 字體樣式
         *
         * @param style {@link android.graphics.Typeface}
         * @return
         */
        public Spanedable type(int style) { //傳入字體樣式,返回Spanedable的引用
            return type(style, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        /**
         * 字體樣式
         *
         * @param style
         * @param flags
         * @return
         */
        public Spanedable type(int style, int flags) {//傳入字體樣式和flags,返回Spanedable的引用
            spans.put(new StyleSpan(style), flags);
            return this;
        }

        /**
         * @param family //字體
         * @param style //字體樣式
         * @param size //文字大小
         * @param color //文字color
         * @param linkColor //鏈接color
         * @return
         */
        public Spanedable textAppearance(String family, int style, int size,
                                         ColorStateList color, ColorStateList linkColor) {
            return textAppearance(family, style, size, color, linkColor, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        /**
         * @param family
         * @param style
         * @param size
         * @param color
         * @param linkColor
         * @param flags
         * @return
         */
        public Spanedable textAppearance(String family, int style, int size, ColorStateList color, ColorStateList linkColor, int flags) {
            spans.put(new TextAppearanceSpan(family, style, size, color, linkColor), flags);
            return this;
        }
  • 新添加的background方法

    坐標圖:

    代碼實現如下(請參照坐標圖分析代碼實現的坐標計算):

/**
         * 設置背景
         * @param drawable
         * @return
         */
        public Spanedable background(Drawable drawable) { //設置背景drawable
            return background(drawable, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        /**
         * 設置背景
         * @param drawable
         * @param flags
         * @return
         */
        public Spanedable background(Drawable drawable, int flags) {//設置背景drawable和flags
            return background(drawable, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), flags);
        }

        /**
         * 設置背景
         * @param drawable
         * @param flags
         * @return
         */
        public Spanedable background(Drawable drawable, final int w, final int h, int flags) { //通過重寫ImageSpan的draw方法實現
            drawable.setBounds(0, 0, w, h); //設置drawable的bounds
            spans.put(new ImageSpan(drawable) {
                @Override
                public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
                                 int bottom, Paint paint) {
                    String sequence = text.subSequence(start, end).toString(); //渲染的text
                    Rect boundText = new Rect(); //測量text大小的rect
                    paint.getTextBounds(sequence, 0, sequence.length(), boundText); //填充boundText
                    Drawable b = getDrawable();
                    Rect bounds = b.getBounds();

                    int w = bounds.width() < boundText.width() ? boundText.width() : bounds.width(); //drawable最大寬度
                    int h = bounds.height();

                    /*
                    *設置drawable的最大高度
                    */
                    float fontHeight = boundText.height();
                    int maxHeight = (int) ((bottom - y) * 2 + fontHeight);
                    if (h < fontHeight) {
                        h = (int) fontHeight;
                    } else {
                        if (h > maxHeight) {
                            h = maxHeight;
                        }
                    }

                    b.setBounds(0, 0, w, h);

                    /*
                    paint.setColor(Color.WHITE);
                    canvas.drawRect(x + (bounds.width() - boundText.width()) / 2,
                            bottom - (bottom - y) - fontHeight,
                            (x + (bounds.width() - boundText.width()) / 2) + boundText.width(),
                            bottom - (bottom - y),
                            paint);
                    */
                    canvas.save();
                    int transY = top + (bottom - top - maxHeight) + (maxHeight - bounds.height()) / 2;
                    canvas.translate(x, transY); //平移畫布
                    b.draw(canvas);
                    canvas.restore();
                    paint.setColor(Color.BLACK);
                    canvas.drawText(sequence, x + (bounds.width() - boundText.width()) / 2, y, paint); //繪制文字
                }
            }, flags);
            return this;
        }
  • 添加自定義的span
/**
         * @param obj
         * @return
         */
        public Spanedable span(Object obj) {
            return span(obj, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        /**
         * @param obj
         * @param flags
         * @return
         */
        public Spanedable span(Object obj, int flags) {
            spans.put(obj, flags);
            return this;
        }
  • 刪除span
/**
         * @param obj
         */
        public void remove(Object obj) {
            ss.removeSpan(obj);
        }

        /**
         *
         */
        public void clear() {
            Iterator<Object> iterator = spans.keySet().iterator();
            while (iterator.hasNext()) {
                iterator.next();
                iterator.remove();
            }
        }
  • commit應用span
public TextView commit() {
            Iterator<Map.Entry<Object, Integer>> iterator = spans.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Object, Integer> next = iterator.next();
                ss.setSpan(next.getKey(), 0, ss.length(), next.getValue());
            }
            SpanTextView.this.append(ss);
            return SpanTextView.this;
        }

如何使用

  • xml代碼
<com.think.android.widget.SpanTextView
        android:id="@+id/spantextview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
  • java代碼
SpanTextView spanTextView = (SpanTextView) findViewById(R.id.spantextview);
        spanTextView.append("SpanTextView組合測試[");
        spanTextView.spanedable("ABCD").color(Color.RED).type(Typeface.ITALIC).absoluteSize(50, true).click(new SpanTextView.OnClickListener() {
            @Override
            public void onClick(CharSequence text) {
                Log.d(TAG, "onClick text = " + text);
            }
        }).commit();
        spanTextView.append("]組合測試");

后記

一切都是為了方便使用,也許這個控件封裝的還有很多不足之處,歡迎大家指正,我將表示最真誠的感謝

 

參考資料

Android中用Spannable在TextView中給文字加上邊框

 

來自:http://thinkdevos.net/blog/20160927/android-spantextview/

 

 本文由用戶 LasBozeman 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!