com.lamcreations.scaffold.common.utils.CollapsingTextHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.lamcreations.scaffold.common.utils.CollapsingTextHelper.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.lamcreations.scaffold.common.utils;

import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.text.TextDirectionHeuristicsCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import com.lamcreations.scaffold.R;

public class CollapsingTextHelper {

    private static final boolean USE_SCALING_TEXTURE;
    private final View mView;
    private float mExpandedFraction;
    private final Rect mExpandedBounds;
    private final Rect mCollapsedBounds;
    private int mExpandedTextVerticalGravity = 16;
    private int mCollapsedTextVerticalGravity = 16;
    private float mExpandedTextSize;
    private float mCollapsedTextSize;
    private int mExpandedTextColor;
    private int mCollapsedTextColor;
    private float mExpandedTop;
    private float mCollapsedTop;
    private CharSequence mText;
    private CharSequence mTextToDraw;
    private float mTextWidth;
    private boolean mIsRtl;
    private boolean mUseTexture;
    private Bitmap mExpandedTitleTexture;
    private Paint mTexturePaint;
    private float mTextureAscent;
    private float mTextureDescent;
    private float mCurrentLeft;
    private float mCurrentRight;
    private float mCurrentTop;
    private float mScale;
    private float mCurrentTextSize;
    private final TextPaint mTextPaint;
    private Interpolator mPositionInterpolator;
    private Interpolator mTextSizeInterpolator;

    public CollapsingTextHelper(View view) {
        this.mView = view;
        this.mTextPaint = new TextPaint();
        this.mTextPaint.setAntiAlias(true);
        this.mCollapsedBounds = new Rect();
        this.mExpandedBounds = new Rect();
    }

    public void setTextSizeInterpolator(Interpolator interpolator) {
        this.mTextSizeInterpolator = interpolator;
        this.recalculate();
    }

    public void setPositionInterpolator(Interpolator interpolator) {
        this.mPositionInterpolator = interpolator;
        this.recalculate();
    }

    public void setExpandedTextSize(float textSize) {
        if (this.mExpandedTextSize != textSize) {
            this.mExpandedTextSize = textSize;
            this.recalculate();
        }

    }

    public void setCollapsedTextSize(float textSize) {
        if (this.mCollapsedTextSize != textSize) {
            this.mCollapsedTextSize = textSize;
            this.recalculate();
        }

    }

    public void setCollapsedTextColor(int textColor) {
        if (this.mCollapsedTextColor != textColor) {
            this.mCollapsedTextColor = textColor;
            this.recalculate();
        }

    }

    public void setExpandedTextColor(int textColor) {
        if (this.mExpandedTextColor != textColor) {
            this.mExpandedTextColor = textColor;
            this.recalculate();
        }

    }

    public void setExpandedBounds(int left, int top, int right, int bottom) {
        this.mExpandedBounds.set(left, top, right, bottom);
    }

    public void setCollapsedBounds(int left, int top, int right, int bottom) {
        this.mCollapsedBounds.set(left, top, right, bottom);
    }

    public void setExpandedTextVerticalGravity(int gravity) {
        gravity &= 112;
        if (this.mExpandedTextVerticalGravity != gravity) {
            this.mExpandedTextVerticalGravity = gravity;
            this.recalculate();
        }

    }

    public void setCollapsedTextVerticalGravity(int gravity) {
        gravity &= 112;
        if (this.mCollapsedTextVerticalGravity != gravity) {
            this.mCollapsedTextVerticalGravity = gravity;
            this.recalculate();
        }

    }

    public void setCollapsedTextAppearance(int resId) {
        TypedArray a = this.mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
            this.mCollapsedTextColor = a.getColor(R.styleable.TextAppearance_android_textColor, 0);
        }

        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
            this.mCollapsedTextSize = (float) a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize,
                    0);
        }

        a.recycle();
        this.recalculate();
    }

    public void setExpandedTextAppearance(int resId) {
        TypedArray a = this.mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
            this.mExpandedTextColor = a.getColor(R.styleable.TextAppearance_android_textColor, 0);
        }

        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
            this.mExpandedTextSize = (float) a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize,
                    0);
        }

        a.recycle();
        this.recalculate();
    }

    public void setExpansionFraction(float fraction) {
        fraction = MathUtils.constrain(fraction, 0.0F, 1.0F);
        if (fraction != this.mExpandedFraction) {
            this.mExpandedFraction = fraction;
            this.calculateOffsets();
        }
    }

    public float getExpansionFraction() {
        return this.mExpandedFraction;
    }

    public float getCollapsedTextSize() {
        return this.mCollapsedTextSize;
    }

    public float getExpandedTextSize() {
        return this.mExpandedTextSize;
    }

    private void calculateOffsets() {
        float fraction = this.mExpandedFraction;
        this.mCurrentLeft = interpolate((float) this.mExpandedBounds.left, (float) this.mCollapsedBounds.left,
                fraction, this.mPositionInterpolator);
        this.mCurrentTop = interpolate(this.mExpandedTop, this.mCollapsedTop, fraction, this.mPositionInterpolator);
        this.mCurrentRight = interpolate((float) this.mExpandedBounds.right, (float) this.mCollapsedBounds.right,
                fraction, this.mPositionInterpolator);
        this.setInterpolatedTextSize(
                interpolate(this.mExpandedTextSize, this.mCollapsedTextSize, fraction, this.mTextSizeInterpolator));
        if (this.mCollapsedTextColor != this.mExpandedTextColor) {
            this.mTextPaint.setColor(blendColors(this.mExpandedTextColor, this.mCollapsedTextColor, fraction));
        } else {
            this.mTextPaint.setColor(this.mCollapsedTextColor);
        }

        ViewCompat.postInvalidateOnAnimation(this.mView);
    }

    private void calculateBaselines() {
        this.mTextPaint.setTextSize(this.mCollapsedTextSize);
        float textHeight;
        float textOffset;
        switch (this.mCollapsedTextVerticalGravity) {
        case 16:
        default:
            textHeight = this.mTextPaint.descent() - this.mTextPaint.ascent();
            textOffset = textHeight / 2.0F - this.mTextPaint.descent();
            this.mCollapsedTop = (float) this.mCollapsedBounds.centerY() + textOffset;
            break;
        case 48:
            this.mCollapsedTop = (float) this.mCollapsedBounds.top - this.mTextPaint.ascent();
            break;
        case 80:
            this.mCollapsedTop = (float) this.mCollapsedBounds.bottom;
        }

        this.mTextPaint.setTextSize(this.mExpandedTextSize);
        switch (this.mExpandedTextVerticalGravity) {
        case 16:
        default:
            textHeight = this.mTextPaint.descent() - this.mTextPaint.ascent();
            textOffset = textHeight / 2.0F - this.mTextPaint.descent();
            this.mExpandedTop = (float) this.mExpandedBounds.centerY() + textOffset;
            break;
        case 48:
            this.mExpandedTop = (float) this.mExpandedBounds.top - this.mTextPaint.ascent();
            break;
        case 80:
            this.mExpandedTop = (float) this.mExpandedBounds.bottom;
        }

        this.mTextureAscent = this.mTextPaint.ascent();
        this.mTextureDescent = this.mTextPaint.descent();
        this.clearTexture();
    }

    public void draw(Canvas canvas) {
        int saveCount = canvas.save();
        if (this.mTextToDraw != null) {
            boolean isRtl = this.mIsRtl;
            float x = isRtl ? this.mCurrentRight : this.mCurrentLeft;
            float y = this.mCurrentTop;
            boolean drawTexture = this.mUseTexture && this.mExpandedTitleTexture != null;
            this.mTextPaint.setTextSize(this.mCurrentTextSize);
            float ascent;
            if (drawTexture) {
                ascent = this.mTextureAscent * this.mScale;
            } else {
                ascent = this.mTextPaint.ascent() * this.mScale;
            }

            if (drawTexture) {
                y += ascent;
            }

            if (this.mScale != 1.0F) {
                canvas.scale(this.mScale, this.mScale, x, y);
            }

            if (isRtl) {
                x -= this.mTextWidth;
            }

            if (drawTexture) {
                canvas.drawBitmap(this.mExpandedTitleTexture, x, y, this.mTexturePaint);
            } else {
                canvas.drawText(this.mTextToDraw, 0, this.mTextToDraw.length(), x, y, this.mTextPaint);
            }
        }

        canvas.restoreToCount(saveCount);
    }

    private boolean calculateIsRtl(CharSequence text) {
        boolean defaultIsRtl = ViewCompat.getLayoutDirection(this.mView) == ViewCompat.LAYOUT_DIRECTION_RTL;
        return (defaultIsRtl ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL
                : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length());
    }

    private void setInterpolatedTextSize(float textSize) {
        if (this.mText != null) {
            boolean updateDrawText = false;
            float availableWidth;
            float newTextSize;
            if (isClose(textSize, this.mCollapsedTextSize)) {
                availableWidth = (float) this.mCollapsedBounds.width();
                newTextSize = this.mCollapsedTextSize;
                this.mScale = 1.0F;
            } else {
                availableWidth = (float) this.mExpandedBounds.width();
                newTextSize = this.mExpandedTextSize;
                if (isClose(textSize, this.mExpandedTextSize)) {
                    this.mScale = 1.0F;
                } else {
                    this.mScale = textSize / this.mExpandedTextSize;
                }
            }

            if (availableWidth > 0.0F) {
                updateDrawText = this.mCurrentTextSize != newTextSize;
                this.mCurrentTextSize = newTextSize;
            }

            if (this.mTextToDraw == null || updateDrawText) {
                this.mTextPaint.setTextSize(this.mCurrentTextSize);
                CharSequence title = TextUtils.ellipsize(this.mText, this.mTextPaint, availableWidth,
                        TextUtils.TruncateAt.END);
                if (this.mTextToDraw == null || !this.mTextToDraw.equals(title)) {
                    this.mTextToDraw = title;
                }

                this.mIsRtl = this.calculateIsRtl(this.mTextToDraw);
                this.mTextWidth = this.mTextPaint.measureText(this.mTextToDraw, 0, this.mTextToDraw.length());
            }

            this.mUseTexture = USE_SCALING_TEXTURE && this.mScale != 1.0F;
            if (this.mUseTexture) {
                this.ensureExpandedTexture();
            }

            ViewCompat.postInvalidateOnAnimation(this.mView);
        }
    }

    private void ensureExpandedTexture() {
        if (this.mExpandedTitleTexture == null && !this.mExpandedBounds.isEmpty()
                && !TextUtils.isEmpty(this.mTextToDraw)) {
            this.mTextPaint.setTextSize(this.mExpandedTextSize);
            this.mTextPaint.setColor(this.mExpandedTextColor);
            int w = Math.round(this.mTextPaint.measureText(this.mTextToDraw, 0, this.mTextToDraw.length()));
            int h = Math.round(this.mTextPaint.descent() - this.mTextPaint.ascent());
            this.mTextWidth = (float) w;
            if (w > 0 || h > 0) {
                this.mExpandedTitleTexture = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                Canvas c = new Canvas(this.mExpandedTitleTexture);
                c.drawText(this.mTextToDraw, 0, this.mTextToDraw.length(), 0.0F,
                        (float) h - this.mTextPaint.descent(), this.mTextPaint);
                if (this.mTexturePaint == null) {
                    this.mTexturePaint = new Paint();
                    this.mTexturePaint.setAntiAlias(true);
                    this.mTexturePaint.setFilterBitmap(true);
                }

            }
        }
    }

    public void recalculate() {
        if (this.mView.getHeight() > 0 && this.mView.getWidth() > 0) {
            this.calculateBaselines();
            this.calculateOffsets();
        }

    }

    public void setText(CharSequence text) {
        if (text == null || !text.equals(this.mText)) {
            this.mText = text;
            this.mTextToDraw = null;
            this.clearTexture();
            this.recalculate();
        }

    }

    public CharSequence getText() {
        return this.mText;
    }

    private void clearTexture() {
        if (this.mExpandedTitleTexture != null) {
            this.mExpandedTitleTexture.recycle();
            this.mExpandedTitleTexture = null;
        }

    }

    private static boolean isClose(float value, float targetValue) {
        return Math.abs(value - targetValue) < 0.0010F;
    }

    public int getExpandedTextColor() {
        return this.mExpandedTextColor;
    }

    public int getCollapsedTextColor() {
        return this.mCollapsedTextColor;
    }

    private static int blendColors(int color1, int color2, float ratio) {
        float inverseRatio = 1.0F - ratio;
        float a = (float) Color.alpha(color1) * inverseRatio + (float) Color.alpha(color2) * ratio;
        float r = (float) Color.red(color1) * inverseRatio + (float) Color.red(color2) * ratio;
        float g = (float) Color.green(color1) * inverseRatio + (float) Color.green(color2) * ratio;
        float b = (float) Color.blue(color1) * inverseRatio + (float) Color.blue(color2) * ratio;
        return Color.argb((int) a, (int) r, (int) g, (int) b);
    }

    private static float interpolate(float startValue, float endValue, float fraction, Interpolator interpolator) {
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }

        return AnimationUtils.lerp(startValue, endValue, fraction);
    }

    static {
        USE_SCALING_TEXTURE = Build.VERSION.SDK_INT < 18;
    }

    public static class MathUtils {

        MathUtils() {
        }

        public static int constrain(int amount, int low, int high) {
            return amount < low ? low : (amount > high ? high : amount);
        }

        public static float constrain(float amount, float low, float high) {
            return amount < low ? low : (amount > high ? high : amount);
        }
    }

    public static class AnimationUtils {

        public static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
        public static final Interpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
        public static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();

        AnimationUtils() {
        }

        public static float lerp(float startValue, float endValue, float fraction) {
            return startValue + fraction * (endValue - startValue);
        }

        public static int lerp(int startValue, int endValue, float fraction) {
            return startValue + Math.round(fraction * (float) (endValue - startValue));
        }

        public static class AnimationListenerAdapter implements Animation.AnimationListener {

            AnimationListenerAdapter() {
            }

            public void onAnimationStart(Animation animation) {
            }

            public void onAnimationEnd(Animation animation) {
            }

            public void onAnimationRepeat(Animation animation) {
            }
        }
    }
}