Back to project page fabuless.
The source code is released under:
Apache License
If you think the Android project fabuless listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright (C) 2014 Sergej Shafarenka, halfbit.de *//from ww w . java 2s . com * 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.halfbit.fabview; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.PointF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.animation.AccelerateInterpolator; import android.widget.ImageView; public class FabView extends ImageView { private static final int TOP_LEFT = 1; private static final int TOP_RIGHT = 2; private static final int BOTTOM_LEFT = 3; private static final int BOTTOM_RIGHT = 4; private static final int NORMAL = 1; private static final int SMALL = 2; private static final int BORDER = 1; private static final int INSIDE = 2; protected final ShapeDrawable mBackgroundDrawable; protected final int mFabAttachTo; protected final int mFabAttachAt; protected final int mFabAttachType; protected final int mFabSize; protected final int mFabAttachPadding; protected final int mFabRevealAfterMs; protected int mBackgroundColor; protected int mBackgroundColorDarker; protected int mShadowOffset; protected int mFabRadius; protected View mAttachedToView; private final FabRevealer mFabRevealer; private final TouchSpotAnimator mTouchSpotAnimator; //-- constructors public FabView(Context context) { this(context, null, 0); } public FabView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FabView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setScaleType(ScaleType.CENTER_INSIDE); // http://www.google.com/design/spec/patterns/promoted-actions.html#promoted-actions-floating-action-button final float density = getResources().getDisplayMetrics().density; final TypedArray styles = context.obtainStyledAttributes(attrs, R.styleable.FabView, 0, 0); mFabAttachTo = styles.getResourceId(R.styleable.FabView_fab_attachTo, 0); mFabAttachAt = styles.getInt(R.styleable.FabView_fab_attachAt, TOP_RIGHT); mFabAttachType = styles.getInt(R.styleable.FabView_fab_attachType, BORDER); mFabSize = styles.getInt(R.styleable.FabView_fab_size, NORMAL); mFabAttachPadding = (int) styles.getDimension(R.styleable.FabView_fab_padding, 16 * density); mFabRevealAfterMs = styles.getInteger(R.styleable.FabView_fab_revealAfterMs, -1); styles.recycle(); switch (mFabSize) { case SMALL: mShadowOffset = (int) (2 * density); mFabRadius = (int) (20 * density); break; case NORMAL: default: mShadowOffset = (int) (3 * density); mFabRadius = (int) (28 * density); break; } mBackgroundDrawable = new ShapeDrawable(new OvalShape()); final Paint paint = mBackgroundDrawable.getPaint(); paint.setShadowLayer(mShadowOffset, 0f, 0f * density, 0x60000000); paint.setColor(mBackgroundColor); setLayerType(LAYER_TYPE_SOFTWARE, paint); mTouchSpotAnimator = new TouchSpotAnimator(); mFabRevealer = new FabRevealer(); if (mFabRevealAfterMs > -1) { setVisibility(GONE); getViewTreeObserver().addOnPreDrawListener(mFabRevealer); } } //-- overrides @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int size = 2 * mShadowOffset + 2 * mFabRadius; final int sizeSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); super.onMeasure(sizeSpec, sizeSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mBackgroundDrawable.setBounds(left + mShadowOffset, top + mShadowOffset, right - mShadowOffset, bottom - mShadowOffset); mTouchSpotAnimator.layout(left, top, right, bottom); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); setPivotX(w / 2); setPivotY(h / 2); } private OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (mAttachedToView == null) { ViewGroup parent = (ViewGroup) getParent(); mAttachedToView = parent.findViewById(mFabAttachTo); if (mAttachedToView == null) { throw new IllegalArgumentException("cannot find view to attach"); } } int transY, transX; if (mFabAttachType == BORDER) { // put to border switch (mFabAttachAt) { case TOP_LEFT: { transY = mAttachedToView.getTop() - getHeight() / 2; transX = mAttachedToView.getLeft() + mFabAttachPadding - mShadowOffset; break; } case TOP_RIGHT: { transY = mAttachedToView.getTop() - getHeight() / 2; transX = mAttachedToView.getRight() - getWidth() - mFabAttachPadding + mShadowOffset; break; } case BOTTOM_LEFT: { transY = mAttachedToView.getBottom() - getHeight() / 2; transX = mAttachedToView.getLeft() + mFabAttachPadding - mShadowOffset; break; } case BOTTOM_RIGHT: default: { transY = mAttachedToView.getBottom() - getHeight() / 2; transX = mAttachedToView.getRight() - getWidth() - mFabAttachPadding + mShadowOffset; break; } } } else if (mFabAttachType == INSIDE) { // put inside switch (mFabAttachAt) { case TOP_LEFT: { transY = mAttachedToView.getTop() + mFabAttachPadding - mShadowOffset; transX = mAttachedToView.getLeft() + mFabAttachPadding - mShadowOffset; break; } case TOP_RIGHT: { transY = mAttachedToView.getTop() + mFabAttachPadding - mShadowOffset; transX = mAttachedToView.getRight() - getHeight() - mFabAttachPadding + mShadowOffset; break; } case BOTTOM_LEFT: { transY = mAttachedToView.getBottom() - getHeight() - mFabAttachPadding + mShadowOffset; transX = mAttachedToView.getLeft() + mFabAttachPadding - mShadowOffset; break; } case BOTTOM_RIGHT: default: { transY = mAttachedToView.getBottom() - getHeight() - mFabAttachPadding + mShadowOffset; transX = mAttachedToView.getRight() - getHeight() - mFabAttachPadding + mShadowOffset; break; } } } else { throw new IllegalArgumentException("unsupported attachType: " + mFabAttachType); } setY(transY); setX(transX); } }; @Override protected void onDraw(Canvas canvas) { mBackgroundDrawable.draw(canvas); mTouchSpotAnimator.draw(canvas); super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { mTouchSpotAnimator.onTouchEvent(event); return super.onTouchEvent(event); } @Override public void setBackgroundColor(int color) { mBackgroundColor = color; mBackgroundColorDarker = getDarkerColor(color); if (mBackgroundDrawable != null) { mBackgroundDrawable.getPaint().setColor(color); } invalidate(); } @Override public void setBackgroundDrawable(Drawable background) { if (background instanceof ColorDrawable) { ColorDrawable cd = (ColorDrawable) background; setBackgroundColor(cd.getColor()); } else { throw new UnsupportedOperationException("only color drawable are supported for now"); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener); } @Override @SuppressWarnings("deprecation") protected void onDetachedFromWindow() { getViewTreeObserver().removeGlobalOnLayoutListener(mLayoutListener); super.onDetachedFromWindow(); } //-- inner classes protected class FabRevealer implements Runnable, OnPreDrawListener { public void show(boolean animate) { if (getVisibility() == VISIBLE) return; if (animate) { setScaleX(0); setScaleY(0); setVisibility(VISIBLE); animate() .setInterpolator(new AccelerateInterpolator()) .setDuration(300) .scaleX(1) .scaleY(1); } else { setVisibility(VISIBLE); } } //-- utility methods @Override public void run() { show(true); } @Override public boolean onPreDraw() { getHandler().postDelayed(mFabRevealer, mFabRevealAfterMs); getViewTreeObserver().removeOnPreDrawListener(this); return true; } } protected class TouchSpotAnimator implements AnimatorUpdateListener, AnimatorListener { private static final int ANIM_DURATION = 300; private final PointF mTouchPoint = new PointF(); private final Paint mTouchSpotPaint = new Paint(); private final Path mClipPath = new Path(); private final int mTargetRadius; private final int mTouchSpotColor; private int mTouchSpotRadius = 0; private ValueAnimator mTouchSpotAnimator; public TouchSpotAnimator() { mTouchSpotColor = 0x00000000; mTouchSpotPaint.setColor(mTouchSpotColor); mTouchSpotPaint.setStyle(Style.FILL); mTargetRadius = mFabRadius * 5 / 3; } public void layout(int left, int top, int right, int bottom) { mClipPath.reset(); mClipPath.addCircle((right - left) / 2, (bottom - top) / 2, mFabRadius, Direction.CW); } public void onTouchEvent(MotionEvent event) { final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mTouchPoint.x = event.getX(); mTouchPoint.y = event.getY(); break; case MotionEvent.ACTION_UP: animate(); break; } } public void draw(Canvas canvas) { if (mTouchSpotRadius > 0) { canvas.save(); canvas.clipPath(mClipPath); canvas.drawCircle(mTouchPoint.x, mTouchPoint.y, mTouchSpotRadius, mTouchSpotPaint); canvas.restore(); } } public void animate() { if (mTouchSpotAnimator == null) { mTouchSpotAnimator = new ValueAnimator(); mTouchSpotAnimator.setInterpolator(new AccelerateInterpolator()); mTouchSpotAnimator.addUpdateListener(this); mTouchSpotAnimator.addListener(this); } else { if (mTouchSpotAnimator.isRunning()) { mTouchSpotAnimator.cancel(); } } mTouchSpotAnimator.setFloatValues(0.2f, 1f); mTouchSpotAnimator.setDuration(ANIM_DURATION); mTouchSpotAnimator.start(); } @Override public void onAnimationUpdate(ValueAnimator animation) { final float factor = animation.getAnimatedFraction(); mTouchSpotRadius = (int) (mTargetRadius * factor); mTouchSpotPaint.setColor(transformAlpha(mTouchSpotColor, 0x00, 0x38, factor)); mBackgroundDrawable.getPaint().setColor(transformColor(mBackgroundColorDarker, mBackgroundColor, factor)); invalidate(); } @Override public void onAnimationEnd(Animator animation) { mTouchSpotRadius = 0; } @Override public void onAnimationCancel(Animator animation) { mTouchSpotRadius = 0; } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } } //-- utility methods protected static int transformColor(int fromColor, int toColor, float factor) { final float unfactor = 1f - factor; final int alpha = (int) (unfactor * Color.alpha(fromColor) + factor * Color.alpha(toColor)); final int red = (int) (unfactor * Color.red(fromColor) + factor * Color.red(toColor)); final int green = (int) (unfactor * Color.green(fromColor) + factor * Color.green(toColor)); final int blue = (int) (unfactor * Color.blue(fromColor) + factor * Color.blue(toColor)); return Color.argb(alpha, red, green, blue); } protected static int transformAlpha(int color, int fromAlpha, int toAlpha, float factor) { final float unfactor = 1f - factor; final int alpha = (int) (factor * fromAlpha + unfactor * toAlpha); return setAlpha(color, alpha); } protected static int setAlpha(int color, int alpha) { return ((color & 0x00FFFFFF) | (alpha << 24)); } protected static int getDarkerColor(int color) { float[] hsv = new float[3]; Color.colorToHSV(color, hsv); hsv[2] *= 0.85f; return Color.HSVToColor(hsv); } }