com.myhexaville.iconanimations.gooey_fab.GooeyFabCompatImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.myhexaville.iconanimations.gooey_fab.GooeyFabCompatImpl.java

Source

/*
 * Copyright (C) 2017 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.myhexaville.iconanimations.gooey_fab;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.StateListAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.myhexaville.iconanimations.R;

import static android.graphics.Paint.Style.FILL;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.widget.ListPopupWindow.WRAP_CONTENT;

/**
 * Created with love by ihor on 2017-01-27.
 */
final class GooeyFabCompatImpl extends FrameLayout {
    private static final String LOG_TAG = "GooeyFabCompatImpl";
    static final Interpolator ANIM_INTERPOLATOR = new FastOutLinearInInterpolator();
    static final int[] FOCUSED_ENABLED_STATE_SET = { android.R.attr.state_focused, android.R.attr.state_enabled };
    static final long PRESSED_ANIM_DURATION = 100;

    private ImageView mIcon;
    static final int POSITION_LARGE_FAB = -1;
    private final int mStartTanslationY;
    private final int mAnimationTanslationY;
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private boolean mIsLargeFab;

    private int mPositionFromBottom;
    private boolean mIsExpanded;
    private float mCenterY;
    private float mGooeyPart;
    private View mRipple;

    public GooeyFabCompatImpl(Context context, boolean isLarge, int positionFromBottom) {
        super(context);
        mIsLargeFab = isLarge;
        mPositionFromBottom = positionFromBottom;
        int position = mPositionFromBottom + 1;
        mStartTanslationY = getFabHeight() * position + (getBottomMargin() * position) + fourDp();
        mAnimationTanslationY = getFabWidth() * position + (getBottomMargin() * position);
        init();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;

        mCenterY = getCenterY();

        if (Build.VERSION.SDK_INT >= LOLLIPOP) {
            setOutlineProvider(new CustomOutline());
        }
    }

    private float getCenterY() {
        return mIsLargeFab ? getGooeyPartHeight() + (getFabWidth() / 2) : mHeight / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        RectF gooeyPart = new RectF(0, mCenterY - getArcAbsolute(), mWidth, mCenterY + getArcAbsolute());

        RectF backgroundCircle = new RectF(0, mCenterY - getFabWidth() / 2, mWidth, mCenterY + getFabWidth() / 2);

        canvas.drawArc(gooeyPart, 0, mIsLargeFab ? -180 : 180, false, mPaint);

        canvas.drawArc(backgroundCircle, 0, 360, false, mPaint);
    }

    public void showAnimation() {
        if (!mIsLargeFab) {
            if (!mIsExpanded) {
                mIcon.setAlpha(0f);
            }

            animate().translationYBy(mIsExpanded ? mAnimationTanslationY : -mAnimationTanslationY).setDuration(250)
                    .start();

            mIcon.animate().alpha(!mIsExpanded ? 1f : 0f).setDuration(500).start();
            mGooeyPart = getGooeyPartHeight();
            invalidate();
        } else {
            mIcon.animate().rotationBy(mIsExpanded ? -45 : 45).setDuration(187).start();
            ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(187);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float v = (float) animation.getAnimatedValue();
                    mGooeyPart = getGooeyPartHeight() * v;
                    invalidate();
                }
            });
            animator.start();
        }

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f).setDuration(187);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float v = (float) animation.getAnimatedValue();
                        mGooeyPart = getGooeyPartHeight() * v;
                        invalidate();
                    }
                });
                animator.start();
            }
        }, 187);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mIsExpanded = !mIsExpanded;
            }
        }, 250);

    }

    private void init() {
        inflate();
        addRipple();
        setWillNotDraw(false);
        setSize();
        setPaint();
        moveToStartingPosition();
        setParams();
        addIcon();
    }

    private void inflate() {
        inflate(getContext(), R.layout.fab_layout_compat, this);
        mRipple = findViewById(R.id.ripple);
    }

    private void addRipple() {
        LayoutParams params = new LayoutParams(getFabWidth(), getFabWidth());
        if (mIsLargeFab) {
            params.gravity = BOTTOM;
        } else {
            params.gravity = CENTER;
        }
        mRipple.setLayoutParams(params);
        if (isLollipopOrAbove()) {
            mRipple.setClipToOutline(true);
        }
    }

    private void addIcon() {
        mIcon = new ImageView(getContext());
        LayoutParams params = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
        if (mIsLargeFab) {
            params.gravity = BOTTOM | CENTER_HORIZONTAL;
            params.bottomMargin = dpToPixels(16);
        } else {
            params.gravity = CENTER;

        }
        mIcon.setLayoutParams(params);
        addView(mIcon);
    }

    private void setPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.parseColor("#008080"));
        mPaint.setStyle(FILL);
    }

    private void setSize() {
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(getFabWidth(), getFabHeight());
        layoutParams.bottomMargin = getBottomMargin();
        setLayoutParams(layoutParams);
    }

    private void setParams() {
        if (isLollipopOrAbove()) {
            onElevationsChanged(getFabElevation(true), dpToPixels(3));
        }
    }

    private void moveToStartingPosition() {
        if (!mIsLargeFab) {
            setTranslationY(mStartTanslationY);
        }
    }

    private int getFabWidth() {
        return getContext().getResources()
                .getDimensionPixelSize(mIsLargeFab ? R.dimen.fab_size : R.dimen.fab_size_small);
    }

    private int getFabHeight() {
        if (mIsLargeFab) {
            return getFabWidth() + getGooeyPartHeight();
        } else {
            return getFabWidth() + getGooeyPartHeight() * 2;
        }
    }

    private int getBottomMargin() {
        if (mIsLargeFab) {
            return 0;
        } else {
            return getContext().getResources().getDimensionPixelSize(R.dimen.small_fab_margin_bottom);
        }
    }

    private int getFabElevation(boolean isRest) {
        return getContext().getResources()
                .getDimensionPixelSize(isRest ? R.dimen.fab_elevation_rest : R.dimen.fab_elevation_pressed);
    }

    private int getGooeyPartHeight() {
        return getContext().getResources()
                .getDimensionPixelSize(mIsLargeFab ? R.dimen.fab_gooey_part : R.dimen.fab_gooey_part_small);
    }

    @TargetApi(LOLLIPOP)
    private class CustomOutline extends ViewOutlineProvider {

        CustomOutline() {
        }

        @Override
        public void getOutline(View view, Outline outline) {
            int top = mIsLargeFab ? getGooeyPartHeight() : (int) (mCenterY - getFabWidth() / 2);
            int bottom = mIsLargeFab ? mHeight : (int) (mCenterY + getFabWidth() / 2);

            outline.setOval(0, top, mWidth, bottom);
        }
    }

    private float getArcAbsolute() {
        return getFabWidth() / 2 + mGooeyPart;
    }

    void setIcon(Drawable icon) {
        mIcon.setImageDrawable(icon);
    }

    private int fourDp() {
        return getResources().getDimensionPixelSize(R.dimen.fab_gooey_part_small);
    }

    private int dpToPixels(int i) {
        return (int) (getResources().getDisplayMetrics().density * i);
    }

    @TargetApi(21)
    void onElevationsChanged(final float elevation, final float pressedTranslationZ) {
        final StateListAnimator stateListAnimator = new StateListAnimator();

        // Animate elevation and translationZ to our values when pressed
        AnimatorSet set = new AnimatorSet();
        set.play(ObjectAnimator.ofFloat(this, "elevation", elevation).setDuration(0)).with(ObjectAnimator
                .ofFloat(this, View.TRANSLATION_Z, pressedTranslationZ).setDuration(PRESSED_ANIM_DURATION));
        set.setInterpolator(ANIM_INTERPOLATOR);
        stateListAnimator.addState(PRESSED_ENABLED_STATE_SET, set);

        // Same deal for when we're focused
        set = new AnimatorSet();
        set.play(ObjectAnimator.ofFloat(this, "elevation", elevation).setDuration(0)).with(ObjectAnimator
                .ofFloat(this, View.TRANSLATION_Z, pressedTranslationZ).setDuration(PRESSED_ANIM_DURATION));
        set.setInterpolator(ANIM_INTERPOLATOR);
        stateListAnimator.addState(FOCUSED_ENABLED_STATE_SET, set);

        // Animate translationZ to 0 if not pressed
        set = new AnimatorSet();
        // Use an AnimatorSet to set a start delay since there is a bug with ValueAnimator that
        // prevents it from being cancelled properly when used with a StateListAnimator.
        AnimatorSet anim = new AnimatorSet();
        anim.play(ObjectAnimator.ofFloat(this, View.TRANSLATION_Z, 0f).setDuration(PRESSED_ANIM_DURATION))
                .after(PRESSED_ANIM_DURATION);
        set.play(ObjectAnimator.ofFloat(this, "elevation", elevation).setDuration(0)).with(anim);
        set.setInterpolator(ANIM_INTERPOLATOR);
        stateListAnimator.addState(ENABLED_STATE_SET, set);

        // Animate everything to 0 when disabled
        set = new AnimatorSet();
        set.play(ObjectAnimator.ofFloat(this, "elevation", 0f).setDuration(0))
                .with(ObjectAnimator.ofFloat(this, View.TRANSLATION_Z, 0f).setDuration(0));
        set.setInterpolator(ANIM_INTERPOLATOR);
        stateListAnimator.addState(EMPTY_STATE_SET, set);

        setStateListAnimator(stateListAnimator);
    }

    private boolean isLollipopOrAbove() {
        return Build.VERSION.SDK_INT >= 21;
    }
}