Add FloatingActionButtonLayout

This commit is contained in:
seven332 committed 2015-05-02 22:42:58 +08:00
1 parent 5916e57bc8
commit bab22977f2
13 files changed
+475 -33

No files matched your search

@@ -15,6 +15,7 @@
package com.hippo.drawable;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -24,6 +25,7 @@ import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import com.hippo.ehviewer.R;
import com.hippo.util.MathUtils;
@@ -127,6 +129,30 @@ public class AddDeleteDrawable extends Drawable {
invalidateSelf();
}
public void setAdd(long duration) {
setShape(false, duration);
}
public void setDelete(long duration) {
setShape(true, duration);
}
public void setShape(boolean delete, long duration) {
if (!((!delete && mProgress == 0f) || (delete && mProgress == 1f))) {
float endProgress = delete ? 1f : 0f;
if (duration <= 0) {
setProgress(endProgress);
} else {
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
oa.setDuration(duration);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa.setAutoCancel(true);
}
oa.start();
}
}
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
@@ -46,6 +46,7 @@ import com.hippo.util.Log;
import com.hippo.util.MathUtils;
import com.hippo.util.ViewUtils;
import com.hippo.widget.FloatingActionButton;
import com.hippo.widget.FloatingActionButtonLayout;
// TODO disable click action when animating
public class GalleryListScene extends Scene implements SearchBar.Helper,
@@ -72,6 +73,7 @@ public class GalleryListScene extends Scene implements SearchBar.Helper,
private RecyclerView mContentRecyclerView;
private SearchLayout mSearchLayout;
private RecyclerView mSearchRecyclerView;
private FloatingActionButtonLayout mFabLayout;
private FloatingActionButton mCornerFab;
private ViewTransition mViewTransition;
@@ -102,7 +104,8 @@ public class GalleryListScene extends Scene implements SearchBar.Helper,
mContentRecyclerView = mContentLayout.getRecyclerView();
mSearchLayout = (SearchLayout) findViewById(R.id.search_layout);
mSearchRecyclerView = mSearchLayout.getRecyclerView();
mCornerFab = (FloatingActionButton) findViewById(R.id.fab);
mFabLayout = (FloatingActionButtonLayout) findViewById(R.id.fab_layout);
mCornerFab = mFabLayout.getPrimaryFab();
mViewTransition = new ViewTransition(mContentLayout, mSearchLayout);
@@ -139,9 +142,9 @@ public class GalleryListScene extends Scene implements SearchBar.Helper,
// Search Layout
mSearchLayout.setFitPaddingBottom(b);
// Corner Fab
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mCornerFab.getLayoutParams();
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mFabLayout.getLayoutParams();
lp.bottomMargin = b + mCornerFabOriginalBottom;
mCornerFab.setLayoutParams(lp);
mFabLayout.setLayoutParams(lp);
}
private void setFabState(int fabState) {
@@ -204,7 +207,8 @@ public class GalleryListScene extends Scene implements SearchBar.Helper,
public void onClick(View v) {
if (v == mCornerFab) {
if (mFabState == FAB_STATE_NORMAL) {
//
mFabLayout.toggle();
mAddDeleteDrawable.setShape(mFabLayout.getExpanded(), ANIMATE_TIME);
} else if (mFabState == FAB_STATE_SEARCH) {
onApplySearch(mSearchBar.getText());
}
@@ -61,7 +61,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
TextView.OnEditorActionListener, TextWatcher,
SearchEditText.SearchEditTextListener, Messenger.Receiver {
private static final long ANIMATION_TIME = 300;
private static final long ANIMATE_TIME = 300l;
private Path mPath = new Path();
private int mWidth;
@@ -227,7 +227,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
// start animator
if (mDrawerArrowDrawable.getProgress() != 1f) {
ObjectAnimator oa1 = ObjectAnimator.ofFloat(mDrawerArrowDrawable, "progress", 0f, 1f);
oa1.setDuration(ANIMATION_TIME);
oa1.setDuration(ANIMATE_TIME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa1.setAutoCancel(true);
}
@@ -235,7 +235,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
}
if (mAddDeleteDrawable.getProgress() != 1f) {
ObjectAnimator oa2 = ObjectAnimator.ofFloat(mAddDeleteDrawable, "progress", 0f, 1f);
oa2.setDuration(ANIMATION_TIME);
oa2.setDuration(ANIMATE_TIME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa2.setAutoCancel(true);
}
@@ -257,7 +257,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
// start animator
if (mDrawerArrowDrawable.getProgress() != 0f) {
ObjectAnimator oa1 = ObjectAnimator.ofFloat(mDrawerArrowDrawable, "progress", 1f, 0f);
oa1.setDuration(ANIMATION_TIME);
oa1.setDuration(ANIMATE_TIME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa1.setAutoCancel(true);
}
@@ -265,7 +265,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
}
if (mAddDeleteDrawable.getProgress() != 0f) {
ObjectAnimator oa2 = ObjectAnimator.ofFloat(mAddDeleteDrawable, "progress", 1f, 0f);
oa2.setDuration(ANIMATION_TIME);
oa2.setDuration(ANIMATE_TIME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa2.setAutoCancel(true);
}
@@ -286,7 +286,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
// Show suggestions list
if (mProgress != 1f) {
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", 0f, 1f);
oa.setDuration(ANIMATION_TIME);
oa.setDuration(ANIMATE_TIME);
oa.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -313,7 +313,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
// Hide suggestions list
if (mProgress != 0f) {
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", 1f, 0f);
oa.setDuration(ANIMATION_TIME);
oa.setDuration(ANIMATE_TIME);
oa.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -37,36 +37,32 @@ public class FloatingActionButton extends View {
private GasketDrawer mGasketDrawer;
private Drawable mDrawable;
private int mDrawableWidth;
private Drawable.Callback mDrawableCallback;
public FloatingActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0, 0);
init(context, attrs);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
init(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, defStyleRes);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mDrawableWidth = UiUtils.dp2pix(context, 24);
private void init(Context context, AttributeSet attrs) {
mDrawableWidth = context.getResources().getDimensionPixelOffset(R.dimen.fab_drawable_size);
mDrawableCallback = new DrawableCallback();
TypedArray a;
a = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyleAttr, defStyleRes);
a = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton);
int bgColor = a.getColor(R.styleable.FloatingActionButton_color, Color.BLACK);
mDrawable = a.getDrawable(R.styleable.FloatingActionButton_drawable);
setDrawable(a.getDrawable(R.styleable.FloatingActionButton_drawable));
boolean forceNoElevation = a.getBoolean(R.styleable.FloatingActionButton_forceNoElevation, false);
a.recycle();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !forceNoElevation) {
mGasketDrawer = new GasketDrawerLollipop(this, bgColor);
} else {
mGasketDrawer = new GasketDrawerOld(this, bgColor);
@@ -83,8 +79,7 @@ public class FloatingActionButton extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(ViewUtils.getSuitableSize(getSuggestedMinimumWidth(), widthMeasureSpec),
ViewUtils.getSuitableSize(getSuggestedMinimumHeight(), heightMeasureSpec));
setMeasuredDimension(ViewUtils.getSuitableSize(getSuggestedMinimumWidth(), widthMeasureSpec), ViewUtils.getSuitableSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
@Override
@@ -113,6 +108,13 @@ public class FloatingActionButton extends View {
}
public void setDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
if (drawable != null) {
drawable.setCallback(mDrawableCallback);
}
mDrawable = drawable;
invalidate();
}
@@ -125,6 +127,24 @@ public class FloatingActionButton extends View {
void setColor(int color);
}
private class DrawableCallback implements Drawable.Callback {
@Override
public void invalidateDrawable(Drawable who) {
if (who == mDrawable) {
invalidate();
}
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
// Empty
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
// Empty
}
}
static class GasketDrawerOld implements GasketDrawer {
@@ -163,6 +183,7 @@ public class FloatingActionButton extends View {
ViewUtils.removeHardwareAccelerationSupport(mView);
}
@Override
public void onStateChange(int[] stateSet) {
boolean enabled = false;
boolean pressed = false;
@@ -199,6 +220,7 @@ public class FloatingActionButton extends View {
mView.invalidate();
}
@Override
public void draw(Canvas canvas) {
mBounds.left = mGasketPadding;
mBounds.top = mGasketPadding;
@@ -207,6 +229,7 @@ public class FloatingActionButton extends View {
canvas.drawOval(mBounds, mPaint);
}
@Override
public void setColor(int color) {
mColor = color;
mDarkerColor = UiUtils.getDarkerColor(color);
@@ -0,0 +1,267 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* 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
* 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.hippo.widget;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.hippo.animation.SimpleAnimatorListener;
import com.hippo.ehviewer.R;
public class FloatingActionButtonLayout extends ViewGroup {
private static long ANIMATE_TIME = 300l;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_CHECKED = "expanded";
private int mInterval;
private boolean mFirst = true;
private boolean mExpanded = false;
private float mMainFabCenterY = -1f;
public FloatingActionButtonLayout(Context context) {
super(context);
init(context);
}
public FloatingActionButtonLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public FloatingActionButtonLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mInterval = context.getResources().getDimensionPixelOffset(R.dimen.floating_action_bar_layout_interval);
}
@Override
public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) {
if (!(child instanceof FloatingActionButton)) {
throw new IllegalStateException("FloatingActionBarLayout should only " +
"contain FloatingActionButton, but try to add "+ child.getClass().getName());
}
super.addView(child, index, params);
}
public FloatingActionButton getPrimaryFab() {
View v = getChildAt(getChildCount() - 1);
if (v == null) {
return null;
} else {
return (FloatingActionButton) v;
}
}
public int getSecondaryFabCount() {
return Math.max(0, getChildCount() - 1);
}
public FloatingActionButton getSecondaryFabAt(int index) {
if (index < 0 || index >= getSecondaryFabCount()) {
return null;
}
return (FloatingActionButton) getChildAt(index);
}
private int getChildMeasureSpec(int parentMeasureSpec) {
int parentMode = MeasureSpec.getMode(parentMeasureSpec);
int parentSize = MeasureSpec.getSize(parentMeasureSpec);
int childMode;
int childSize;
switch (parentMode) {
default:
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
childMode = MeasureSpec.AT_MOST;
childSize = parentSize;
break;
case MeasureSpec.UNSPECIFIED:
childMode = MeasureSpec.UNSPECIFIED;
childSize = parentSize;
}
return MeasureSpec.makeMeasureSpec(childSize, childMode);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mFirst) {
mFirst = false;
boolean expanded = mExpanded;
int count = getChildCount() - 1;
for (int i = 0; i < count; i++) {
getChildAt(i).setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
}
}
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec);
measureChildren(childWidthMeasureSpec, childHeightMeasureSpec);
int maxWidth = 0;
int maxHeight = 0;
boolean firstChild = true;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = maxHeight + child.getMeasuredHeight();
if (firstChild) {
firstChild = false;
} else {
maxHeight += mInterval;
}
}
// Account for padding too
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
// Check against minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int interval = mInterval;
int top = getPaddingTop();
int paddingLeft = getPaddingLeft();
int vaildWidth = getMeasuredWidth() - paddingLeft - getPaddingRight();
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int left = paddingLeft + ((vaildWidth - childWidth) / 2);
child.layout(left, top, left + childWidth, top + childHeight);
if (i == count - 1) {
mMainFabCenterY = top + (childHeight / 2f);
}
top += (childHeight + interval);
}
}
public void toggle() {
setExpanded(!mExpanded);
}
public boolean getExpanded() {
return mExpanded;
}
public void setExpanded(boolean expanded) {
setExpanded(expanded, true);
}
public void setExpanded(boolean expanded, boolean animation) {
if (mExpanded != expanded) {
mExpanded = expanded;
if (mMainFabCenterY == -1f) {
// It is before first onLayout
int count = getChildCount() - 1;
for (int i = 0; i < count; i++) {
getChildAt(i).setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
}
} else {
int count = getChildCount() - 1;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (animation) {
setChildAnimation(child, expanded);
} else {
child.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
if (expanded) {
child.setAlpha(1f);
}
}
}
}
}
}
private void setChildAnimation(final View child, final boolean expanded) {
float startTranslationY;
float endTranslationY;
float startAlpha;
float endAlpha;
if (expanded) {
startTranslationY = mMainFabCenterY -
(child.getTop() + (child.getHeight() / 2));
endTranslationY = 0f;
startAlpha = 0f;
endAlpha = 1f;
} else {
startTranslationY = 0f;
endTranslationY = mMainFabCenterY -
(child.getTop() + (child.getHeight() / 2));
startAlpha = 1f;
endAlpha = 0f;
}
PropertyValuesHolder translationYPvh = PropertyValuesHolder.ofFloat(
"translationY", startTranslationY, endTranslationY);
PropertyValuesHolder alphaPvh = PropertyValuesHolder.ofFloat(
"alpha", startAlpha, endAlpha);
ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(child, translationYPvh, alphaPvh);
oa.setDuration(ANIMATE_TIME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa.setAutoCancel(true);
}
oa.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
if (expanded) {
child.setVisibility(View.VISIBLE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (!expanded) {
child.setVisibility(View.INVISIBLE);
}
}
});
oa.start();
}
}