com.tr4android.support.extension.widget.LabelView.java Source code

Java tutorial

Introduction

Here is the source code for com.tr4android.support.extension.widget.LabelView.java

Source

/*
 * Copyright (C) 2016 Thomas Robert Altstidl
 *
 * 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.tr4android.support.extension.widget;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.TextView;

import com.tr4android.appcompat.extension.R;
import com.tr4android.support.extension.animation.AnimationUtils;
import com.tr4android.support.extension.drawable.RoundRectDrawable;
import com.tr4android.support.extension.drawable.ShadowDrawableWrapper;
import com.tr4android.support.extension.utils.ViewCompatUtils;

/**
 * A TextView subclass that supports elevation and touch feedback pre-Lollipop
 * and has easy-to-use animations
 */
public class LabelView extends TextView {
    private static final int SHOW_HIDE_ANIM_DURATION = 200;

    private int mBackgroundColor;
    private int mRippleColor;
    private float mCornerRadius;
    private float mElevation;

    private boolean mIsHiding;
    private float mAnimationOffset;

    public LabelView(Context context) {
        this(context, null);
    }

    public LabelView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LabelView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.LabelView, defStyleAttr, 0);
        // Background values
        mBackgroundColor = attr.getColor(R.styleable.LabelView_labelBackgroundColor, 0);
        mRippleColor = attr.getColor(R.styleable.LabelView_labelRippleColor, 0);
        mCornerRadius = attr.getDimension(R.styleable.LabelView_labelCornerRadius, 0f);
        mElevation = attr.getDimension(R.styleable.LabelView_labelElevation, 0f);
        // Padding values
        int paddingHorizontal = attr.getDimensionPixelSize(R.styleable.LabelView_labelPaddingHorizontal, 0);
        int paddingVertical = attr.getDimensionPixelSize(R.styleable.LabelView_labelPaddingVertical, 0);
        attr.recycle();

        setFocusable(true);
        setClickable(true);
        initBackground();
        setCompatPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
    }

    /**
     * Sets the translation distance used for the show and hide animations
     * @param offset the offset for animations
     */
    public void setAnimationOffset(float offset) {
        mAnimationOffset = offset;
    }

    /**
     * Gets the translation distance used for the show and hide animations
     * @return the offset for animations
     */
    public float getAnimationOffset() {
        return mAnimationOffset;
    }

    /**
     * Compatibility method for properly setting padding
     * This takes into account the additional padding required by the shadow on pre-Lollipop devices
     */
    public void setCompatPadding(int left, int top, int right, int bottom) {
        // Adjust padding by shadow
        final Rect shadowPadding = new Rect();
        getBackground().getPadding(shadowPadding);
        setPadding(left + shadowPadding.left, top + shadowPadding.top, right + shadowPadding.right,
                bottom + shadowPadding.bottom);
    }

    /**
     * Hides the label using a translation animation
     */
    public void hide() {
        if (mIsHiding || getVisibility() != VISIBLE) {
            // Already hiding or hidden, skip the call
            return;
        }
        if (!ViewCompat.isLaidOut(this)) {
            // View isn't laid out yet
            setVisibility(GONE);
        }
        // Use platform dependent animations
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            hideIceCreamSandwich();
        } else {
            hideEclairMr1();
        }
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void hideIceCreamSandwich() {
        animate().cancel();
        animate().translationX(mAnimationOffset).alpha(0f).setDuration(SHOW_HIDE_ANIM_DURATION)
                .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
                .setListener(new AnimatorListenerAdapter() {
                    private boolean mCancelled;

                    @Override
                    public void onAnimationStart(Animator animation) {
                        mIsHiding = true;
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                        mIsHiding = false;
                        mCancelled = true;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mIsHiding = false;
                        // cancelled animations don't change visibility
                        if (!mCancelled) {
                            setVisibility(GONE);
                        }
                    }
                });
    }

    private void hideEclairMr1() {
        Animation anim = createAnimationSet(1.0f, 0.0f, 0, Math.round(mAnimationOffset));
        anim.setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
        anim.setDuration(SHOW_HIDE_ANIM_DURATION);
        anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
            @Override
            public void onAnimationStart(Animation animation) {
                mIsHiding = true;
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                mIsHiding = false;
                setVisibility(GONE);
            }
        });
        startAnimation(anim);
    }

    /**
     * Shows the label using a translation animation
     */
    public void show() {
        if (!mIsHiding && getVisibility() == VISIBLE) {
            // Already showing or shown, skip the call
            return;
        }
        if (!ViewCompat.isLaidOut(this)) {
            // View isn't laid out yet
            return;
        }
        // Use platform dependent animations
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            showIceCreamSandwich();
        } else {
            showEclairMr1();
        }
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void showIceCreamSandwich() {
        animate().cancel();
        if (getVisibility() != VISIBLE) {
            setTranslationX(mAnimationOffset);
            setAlpha(0f);
        }
        animate().translationX(0f).alpha(1f).setDuration(SHOW_HIDE_ANIM_DURATION)
                .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        setVisibility(VISIBLE);
                    }
                });
    }

    private void showEclairMr1() {
        clearAnimation();
        setVisibility(VISIBLE);
        Animation anim = createAnimationSet(0.0f, 1.0f, Math.round(mAnimationOffset), 0);
        anim.setDuration(SHOW_HIDE_ANIM_DURATION);
        anim.setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
        startAnimation(anim);
    }

    private void initBackground() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            initBackgroundLollipop();
        } else {
            initBackgroundEclairMr1();
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void initBackgroundLollipop() {
        RoundRectDrawable shapeDrawable = new RoundRectDrawable(mBackgroundColor, mCornerRadius);
        RippleDrawable rippleDrawable = new RippleDrawable(ColorStateList.valueOf(mRippleColor), shapeDrawable,
                null);

        setElevation(mElevation);
        ViewCompatUtils.setBackground(this, rippleDrawable);
    }

    private void initBackgroundEclairMr1() {
        RoundRectDrawable shapeDrawable = new RoundRectDrawable(mBackgroundColor, mCornerRadius);
        shapeDrawable.setRoundRectTintList(createColorStateList(mRippleColor, mBackgroundColor));

        // Shadow drawable that wraps the above drawable
        ShadowDrawableWrapper shadowDrawable = new ShadowDrawableWrapper(getResources(), shapeDrawable,
                mCornerRadius, mElevation, mElevation);
        shadowDrawable.setAddPaddingForCorners(false);
        ViewCompatUtils.setBackground(this, shadowDrawable);
    }

    // Helper that creates an AnimationSet with the specified translation
    private static Animation createAnimationSet(float fromAlpha, float toAlpha, int fromXDelta, int toXDelta) {
        AnimationSet anim = new AnimationSet(true);
        anim.addAnimation(new AlphaAnimation(fromAlpha, toAlpha));
        anim.addAnimation(new TranslateAnimation(fromXDelta, toXDelta, 0, 0));
        return anim;
    }

    // Helper that creates a ColorStateList with the selected ripple color
    private static ColorStateList createColorStateList(int rippleColor, int backgroundColor) {
        final int compositeColor = ColorUtils.compositeColors(rippleColor, backgroundColor);
        return new ColorStateList(new int[][] { // states
                new int[] { android.R.attr.state_focused }, new int[] { android.R.attr.state_pressed }, new int[] {} // state_default
        }, new int[] { // colors
                compositeColor, compositeColor, backgroundColor });
    }
}