Java tutorial
/* * The MIT License (MIT) * * Copyright (c) 2015 Kurt Mbanje * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package mbanje.kurt.fabbutton; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.CoordinatorLayout.DefaultBehavior; import android.support.design.widget.Snackbar; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.util.AttributeSet; import android.view.View; import android.view.animation.Animation; import android.widget.FrameLayout; import java.util.List; /** * Created by kurt on 21 02 2015 . */ @DefaultBehavior(FabButton.Behavior.class) public class FabButton extends FrameLayout implements CircleImageView.OnFabViewListener { private CircleImageView circle; private ProgressRingView ring; private float ringWidthRatio = 0.14f; //of a possible 1f; private boolean indeterminate; private boolean autostartanim; private int endBitmapResource; private boolean showEndBitmap; private boolean hideProgressOnComplete; public FabButton(Context context) { super(context); init(context, null, 0); } public FabButton(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public FabButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } protected void init(Context context, AttributeSet attrs, int defStyle) { View v = View.inflate(context, R.layout.widget_fab_button, this); setClipChildren(false); circle = (CircleImageView) v.findViewById(R.id.fabbutton_circle); ring = (ProgressRingView) v.findViewById(R.id.fabbutton_ring); circle.setFabViewListener(this); ring.setFabViewListener(this); int color = Color.BLACK; int progressColor = Color.BLACK; int animDuration = 4000; int icon = -1; float maxProgress = 0; float progress = 0; if (attrs != null) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView); color = a.getColor(R.styleable.CircleImageView_android_color, Color.BLACK); progressColor = a.getColor(R.styleable.CircleImageView_fbb_progressColor, Color.BLACK); progress = a.getFloat(R.styleable.CircleImageView_android_progress, 0f); maxProgress = a.getFloat(R.styleable.CircleImageView_android_max, 100f); indeterminate = a.getBoolean(R.styleable.CircleImageView_android_indeterminate, false); autostartanim = a.getBoolean(R.styleable.CircleImageView_fbb_autoStart, true); animDuration = a.getInteger(R.styleable.CircleImageView_android_indeterminateDuration, animDuration); icon = a.getResourceId(R.styleable.CircleImageView_android_src, icon); ringWidthRatio = a.getFloat(R.styleable.CircleImageView_fbb_progressWidthRatio, ringWidthRatio); endBitmapResource = a.getResourceId(R.styleable.CircleImageView_fbb_endBitmap, R.drawable.ic_fab_complete); showEndBitmap = a.getBoolean(R.styleable.CircleImageView_fbb_showEndBitmap, false); hideProgressOnComplete = a.getBoolean(R.styleable.CircleImageView_fbb_hideProgressOnComplete, false); circle.setShowShadow(a.getBoolean(R.styleable.CircleImageView_fbb_showShadow, true)); a.recycle(); } circle.setColor(color); circle.setShowEndBitmap(showEndBitmap); circle.setRingWidthRatio(ringWidthRatio); ring.setProgressColor(progressColor); ring.setProgress(progress, true); ring.setMaxProgress(maxProgress); ring.setAutostartanim(autostartanim); ring.setAnimDuration(animDuration); ring.setRingWidthRatio(ringWidthRatio); ring.setIndeterminate(indeterminate); if (icon != -1) { circle.setIcon(icon, endBitmapResource); } } public void setIcon(int resource, int endIconResource) { circle.setIcon(resource, endIconResource); } public void setColor(int color) { circle.setColor(color); } public void setRingProgressColor(int color) { ring.setProgressColor(color); } public void setIcon(Drawable icon, Drawable endIcon) { circle.setIcon(icon, endIcon); } public void resetIcon() { circle.resetIcon(); } /** * sets the progress to indeterminate or not * @param indeterminate the flag */ public void setIndeterminate(boolean indeterminate) { this.indeterminate = indeterminate; ring.setIndeterminate(indeterminate); } public void setOnClickListener(OnClickListener listener) { ring.setOnClickListener(listener); circle.setOnClickListener(listener); } /** * shows the animation ring * @param show shows animation ring when set to true */ public void showProgress(boolean show) { circle.showRing(show); } public void hideProgressOnComplete(boolean hide) { hideProgressOnComplete = hide; } public void setEnabled(boolean enabled) { super.setEnabled(enabled); circle.setEnabled(enabled); ring.setEnabled(enabled); } /** * sets current progress * @param progress the current progress to set value too */ public void setProgress(float progress, boolean animate) { ring.setProgress(progress, animate); } @Override public void onProgressVisibilityChanged(boolean visible) { if (visible) { ring.setVisibility(View.VISIBLE); ring.startAnimation(); } else { ring.stopAnimation(true); ring.setVisibility(View.GONE); } } @Override public void onProgressCompleted() { circle.showCompleted(showEndBitmap, hideProgressOnComplete); if (hideProgressOnComplete) { ring.setVisibility(View.GONE); } } public static class Behavior extends CoordinatorLayout.Behavior<FabButton> { // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is // because we can use view translation properties which greatly simplifies the code. private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; private Rect mTmpRect; private boolean mIsAnimatingOut; private float mTranslationY; @Override public boolean layoutDependsOn(CoordinatorLayout parent, FabButton child, View dependency) { // We're dependent on all SnackbarLayouts (if enabled) return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, FabButton child, View dependency) { if (dependency instanceof Snackbar.SnackbarLayout) { updateFabTranslationForSnackbar(parent, child, dependency); } else if (dependency instanceof AppBarLayout) { final AppBarLayout appBarLayout = (AppBarLayout) dependency; if (mTmpRect == null) { mTmpRect = new Rect(); } // First, let's get the visible rect of the dependency final Rect rect = mTmpRect; ViewGroupUtils.getDescendantRect(parent, dependency, rect); if (rect.bottom <= getMinimumHeightForVisibleOverlappingContent(appBarLayout)) { // If the anchor's bottom is below the seam, we'll animate our FAB out if (!mIsAnimatingOut && child.getVisibility() == View.VISIBLE) { animateOut(child); } } else { // Else, we'll animate our FAB back in if (child.getVisibility() != View.VISIBLE) { animateIn(child); } } } return false; } final int getMinimumHeightForVisibleOverlappingContent(AppBarLayout bar) { int topInset = 0; int minHeight = ViewCompat.getMinimumHeight(bar); if (minHeight != 0) { return minHeight * 2 + topInset; } else { int childCount = bar.getChildCount(); return childCount >= 1 ? ViewCompat.getMinimumHeight(bar.getChildAt(childCount - 1)) * 2 + topInset : 0; } } private void updateFabTranslationForSnackbar(CoordinatorLayout parent, FabButton fab, View snackbar) { final float translationY = getFabTranslationYForSnackbar(parent, fab); if (translationY != mTranslationY) { // First, cancel any current animation ViewCompat.animate(fab).cancel(); if (Math.abs(translationY - mTranslationY) == snackbar.getHeight()) { // If we're travelling by the height of the Snackbar then we probably need to // animate to the value ViewCompat.animate(fab).translationY(translationY) .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR).setListener(null); } else { // Else we'll set use setTranslationY ViewCompat.setTranslationY(fab, translationY); } mTranslationY = translationY; } } private float getFabTranslationYForSnackbar(CoordinatorLayout parent, FabButton fab) { float minOffset = 0; final List<View> dependencies = parent.getDependencies(fab); for (int i = 0, z = dependencies.size(); i < z; i++) { final View view = dependencies.get(i); if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) { minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - view.getHeight()); } } return minOffset; } private void animateIn(FabButton button) { button.setVisibility(View.VISIBLE); if (Build.VERSION.SDK_INT >= 14) { ViewCompat.animate(button).scaleX(1f).scaleY(1f).alpha(1f) .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR).withLayer().setListener(null) .start(); } else { Animation anim = android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in); anim.setDuration(200); anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); button.startAnimation(anim); } } private void animateOut(final FabButton button) { if (Build.VERSION.SDK_INT >= 14) { ViewCompat.animate(button).scaleX(0f).scaleY(0f).alpha(0f) .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR).withLayer() .setListener(new ViewPropertyAnimatorListener() { @Override public void onAnimationStart(View view) { mIsAnimatingOut = true; } @Override public void onAnimationCancel(View view) { mIsAnimatingOut = false; } @Override public void onAnimationEnd(View view) { mIsAnimatingOut = false; view.setVisibility(View.GONE); } }).start(); } else { Animation anim = android.view.animation.AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out); anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); anim.setDuration(200); anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() { @Override public void onAnimationStart(Animation animation) { mIsAnimatingOut = true; } @Override public void onAnimationEnd(Animation animation) { mIsAnimatingOut = false; button.setVisibility(View.GONE); } }); button.startAnimation(anim); } } } }