Android Span 進階

 在上篇文章《Android Span 架構介紹》,咱們講述了Android Span的基本概念和用法,這篇文章咱們就來擴展一下咱們對Android Span的瞭解,這必定會使你感到驚奇的,驚歎Android Span居然還能完成這些的效果,讓你在Android自定義View和動畫方面有更加深入的理解,可能會幫助你你想出更加簡潔的實現方式。
 本篇文章主要講述一下兩個方面的內容:git

  • 自定義Android Spangithub

  • 使用Android Span實現動畫canvas

 先貼一下本篇文章實現的自定義Span和動畫的效果圖
demo架構

demo

自定義Span

 咱們都知道,自定義View有兩種方式,一種是繼承特定的視圖類,好比你但願修改TextView的行爲,因此繼承了TextView;另外一種就是直接繼承View或者ViewGroup,這樣能夠實現全新的視圖和行爲。如同自定義View同樣,你有兩種自定義Span的方法,一種直接繼承特定類型的Span類,好比ForegroundColorSpan等,這樣你能夠在這些類的基礎上進行修改;另外一種就是繼承ReplaceSpan這樣的抽象類或者實現LetterLineBackgroundSpan這樣的接口,你只要實現它給出的接口,就能夠實現新的效果。
 咱們先來說解第一種方式。直接繼承現有的Span。文章開頭時展現的ActionBar動畫就是經過繼承ForegroundSpan來實現的。
 咱們主要重載了updateDrawsStategetForegroundColor,這樣就能夠經過改變setAlpha函數來改變顏色,讓字體從透明(alpha爲0)到某個特定顏色。dom

public class MutableForegroundColorSpan extends ForegroundColorSpan {
    private int mAlpha = 255;
    private int mForegroundColor;
    public MutableForegroundColorSpan(int alpha,int color) {
        super(color);
        mAlpha = alpha;
        mForegroundColor = color;
    }
    @Override
    public void updateDrawState(TextPaint ds) {
        ds.setColor(getForegroundColor());
    }
    public void setAlpha(int alpha) {
        mAlpha = alpha;
    }
    public void setForegroundColor(int foregroundColor) {
        mForegroundColor = foregroundColor;
    }
    public float getAlpha() {
        return mAlpha;
    }
    @Override
    public int getForegroundColor() {
        return Color.argb(mAlpha,Color.red(mForegroundColor),Color.green(mForegroundColor),Color.blue(mForegroundColor));
    }
}

 第二種方法是繼承Span架構中的抽象類或者是實現特定接口。須要注意的是,在上一篇文章中說的CharacterStyle,ParagraphStyle.UpdateAppearanceUpdateLayout都是沒有函數的,因此,咱們沒法直接繼承或者實現它們。除了第一篇文章中所介紹的那些Span可使用第一種方法進行繼承。咱們通常都繼承或者實現MetricAffectingSpan,ReplacementSpan或者LineBackgroundSpan
 好比咱們想給每一個字都添加一個不一樣顏色的背景,咱們就能夠繼承ReplacementSpanide

圖片描述

public class BubbleSpan extends ReplacementSpan {
    private Paint mPaint;
    static Random random = new Random();
    private int mWidth = -1;
    private RectF mRectF = new RectF();

    private int[] mColors = new int[20];

    public BubbleSpan() {
        initPaint();
        initColors();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
        mPaint.setAntiAlias(true);
    }

    private void initColors() {
        for(int index = 0 ; index < mColors.length ; index++) {
            mColors[index] = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
        }
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        //return text with relative to the Paint
        mWidth = (int) paint.measureText(text, start, end);
        return mWidth;
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        float charx = x;
        for(int i = start ; i<end; i++) {
            String charAt = extractText(text, i, i + 1);
            float charWidth = paint.measureText(charAt);
            mRectF.set(charx, top, charx += charWidth, bottom);
            mPaint.setColor(mColors[i % mColors.length]);
            //根據每一個字的位置繪製背景
            canvas.drawOval(mRectF, mPaint);
        }
        //繪製字體,若是不掉用這個函數,就不會顯示字體啦。
        canvas.drawText(text, start, end, x, y, paint);
    }

    private String extractText(CharSequence text, int start, int end) {
        return text.subSequence(start, end).toString();
    }
}

 咱們能夠看到,咱們要實現兩個函數:getSizedrawgetSize是得到字體的長度的,因此通常都是直接使用paint.measureText,而後draw中進行繪製,你能夠在這裏把每一個字的背景繪製出來,並且你必須也要把字體給繪製出來。若是你只想繪製背景,不想涉及字體的繪製,那麼就能夠直接實現LineBackgroundSpan接口。函數

public class RectSpan extends ReplacementSpan {
    private final Paint mPaint;
    private int mWidth;

    public RectSpan() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.BLUE);
        mPaint.setAntiAlias(true);
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        //return text with relative to the Paint
        mWidth = (int) paint.measureText(text, start, end);
        return mWidth;
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        //只繪製了外圍矩形,沒有繪製文字。
        canvas.drawRect(x, top, x + mWidth, bottom, mPaint);
    }
}

demo

經過Span實現動畫

 在上一節中,咱們已經實現了MutableForegroundColorSpan類,那麼如何使用它來實現動畫呢?這裏咱們就要使用到ObjectAnimatorProperty<T1,T2>。咱們首先定義MutableForegroundColorSpan使用的property學習

private static final Property<MutableForegroundColorSpan, Integer> MUTABLE_FOREGROUND_COLOR_SPAN_PROPERTY =
        new Property<MutableForegroundColorSpan, Integer>(Integer.class, "MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY") {

        @Override
                public void set(MutableForegroundColorSpan alphaForegroundColorSpanGroup, Integer value) {
                        alphaForegroundColorSpanGroup.setForegroundColor(value);
                }

        @Override
                public Integer get(MutableForegroundColorSpan span) {
                        return span.getForegroundColor();
                }
};

 而後咱們就可使用ObjectAnimatorMutableForegroundColorSpan實現屬性動畫了。字體

MutableForegroundColorSpan span = new MutableForegroundColorSpan(255, Color.BLUE);
final SpannableString spannableString = new SpannableString(CONTENT);
spannableString.setSpan(span, 0,4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(span, MUTABLE_FOREGROUND_COLOR_SPAN_PROPERTY, Color.BLACK, Color.RED);
objectAnimator.setEvaluator(new ArgbEvaluator());
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
        //refresh
                mTvTextView.setText(spannableString);
        }       
});     
objectAnimator.setInterpolator(mSmoothInterpolator);
objectAnimator.setDuration(600);
objectAnimator.start();

 這裏還只是單獨一個Span實例的動畫效果,你能夠對多個Span實例進行屬性動畫,從而實現更加複雜的動畫效果。就好比文章開始時的文字逐漸顯示的動畫效果。
 咱們能夠給每一個字都設置一個MutableForegroundColorSpan實例,並將這些實例都添加到一個對象中,而後在屬性動畫過程當中,亂序設置每一個實例的alpha的值,從而達到文字逐漸顯現的動畫。動畫

public class FireWorkGroup {
    private final float mProgress;
    private final ArrayList<MutableForegroundColorSpan> mSpans;
    private final ArrayList<Integer> mSpanIndexes;

    public FireWorkGroup() {
        mProgress = 0;
        mSpans  = new ArrayList<>();
        mSpanIndexes = new ArrayList<>();
    }

    public void addSpan(MutableForegroundColorSpan span) {
        span.setAlpha(0);
        mSpanIndexes.add(mSpans.size());
        mSpans.add(span);
    }
    public void init() {
        Collections.shuffle(mSpans);
    }
    public void setProgress(float progress) {
        int size = mSpans.size();
        float total  = 1.0f * size * progress;
        for (int index = 0 ; index < size ; index++) {
            MutableForegroundColorSpan span = mSpans.get(index);
            if (total > 1.0f) {
                span.setAlpha(255);
                total -= 1.0f;
            } else {
                span.setAlpha((int)(total * 255));
                total = 0.0f;
            }
        }
    }
    public float getProgress() {
        return mProgress;
    }
    public static final Property<FireWorkGroup, Float> FIREWORKS_GROUP_PROGRESS_PROPERTY =
            new Property<FireWorkGroup, Float>(Float.class, "FIREWORKS_GROUP_PROGRESS_PROPERTY") {

                @Override
                public void set(FireWorkGroup spanGroup, Float value) {
                    spanGroup.setProgress(value);
                }

                @Override
                public Float get(FireWorkGroup spanGroup) {
                    return spanGroup.getProgress();
                }
            };
}

後記

 上述的這些動畫都是我在第一篇文章提到的那篇博文中實現過的效果,我在學習過程當中又發現了一個實現了不少TextView相關動畫的開源項目HTextView。因此接下來的任務就是想使用Span機制去實現這個項目中的一些動畫效果。但願你們繼續支持個人文章,並積極指出文中的錯誤。
 !!!源碼都在個人github裏。

相關文章
相關標籤/搜索