package carbon.beta;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import carbon.Carbon;
import carbon.R;
import carbon.animation.AnimatedView;
import carbon.animation.StateAnimator;
import carbon.component.Component;
import carbon.component.ComponentView;
import carbon.drawable.ripple.RippleDrawable;
import carbon.drawable.ripple.RippleView;
import carbon.internal.ElevationComparator;
import carbon.internal.RevealAnimator;
import carbon.shadow.Shadow;
import carbon.shadow.ShadowGenerator;
import carbon.shadow.ShadowShape;
import carbon.shadow.ShadowView;
import carbon.widget.InsetView;
import carbon.widget.MaxSizeView;
import carbon.widget.OnInsetsChangedListener;
import carbon.widget.RenderingMode;
import carbon.widget.RenderingModeView;
import carbon.widget.RevealView;
import carbon.widget.RoundedCornersView;
import carbon.widget.StateAnimatorView;
import carbon.widget.TouchMarginView;
public class AppBarLayout extends android.support.design.widget.AppBarLayout
implements ShadowView, RippleView, TouchMarginView, StateAnimatorView, AnimatedView, InsetView, RoundedCornersView, MaxSizeView, RevealView {
private OnTouchListener onDispatchTouchListener;
public AppBarLayout(Context context) {
super(context);
initAppBarLayout(null, R.attr.carbon_appBarLayoutStyle);
}
public AppBarLayout(Context context, AttributeSet attrs) {
super(Carbon.getThemedContext(context, attrs, R.styleable.AppBarLayout, R.attr.carbon_appBarLayoutStyle, R.styleable.CollapsingToolbarLayout_carbon_theme), attrs);
initAppBarLayout(attrs, R.attr.carbon_appBarLayoutStyle);
}
private static int[] rippleIds = new int[]{
R.styleable.AppBarLayout_carbon_rippleColor,
R.styleable.AppBarLayout_carbon_rippleStyle,
R.styleable.AppBarLayout_carbon_rippleHotspot,
R.styleable.AppBarLayout_carbon_rippleRadius
};
private static int[] animationIds = new int[]{
R.styleable.AppBarLayout_carbon_inAnimation,
R.styleable.AppBarLayout_carbon_outAnimation
};
private static int[] touchMarginIds = new int[]{
R.styleable.AppBarLayout_carbon_touchMargin,
R.styleable.AppBarLayout_carbon_touchMarginLeft,
R.styleable.AppBarLayout_carbon_touchMarginTop,
R.styleable.AppBarLayout_carbon_touchMarginRight,
R.styleable.AppBarLayout_carbon_touchMarginBottom
};
private static int[] insetIds = new int[]{
R.styleable.AppBarLayout_carbon_inset,
R.styleable.AppBarLayout_carbon_insetLeft,
R.styleable.AppBarLayout_carbon_insetTop,
R.styleable.AppBarLayout_carbon_insetRight,
R.styleable.AppBarLayout_carbon_insetBottom,
R.styleable.AppBarLayout_carbon_insetColor
};
private static int[] maxSizeIds = new int[]{
R.styleable.AppBarLayout_carbon_maxWidth,
R.styleable.AppBarLayout_carbon_maxHeight,
};
private static int[] elevationIds = new int[]{
R.styleable.AppBarLayout_carbon_elevation,
R.styleable.AppBarLayout_carbon_elevationShadowColor
};
private void initAppBarLayout(AttributeSet attrs, int defStyleAttr) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.AppBarLayout, defStyleAttr, 0);
Carbon.initRippleDrawable(this, a, rippleIds);
Carbon.initElevation(this, a, elevationIds);
Carbon.initAnimations(this, a, animationIds);
Carbon.initTouchMargin(this, a, touchMarginIds);
Carbon.initInset(this, a, insetIds);
Carbon.initMaxSize(this, a, maxSizeIds);
setCornerRadius(a.getDimension(R.styleable.AppBarLayout_carbon_cornerRadius, 0));
a.recycle();
setChildrenDrawingOrderEnabled(true);
setClipToPadding(false);
}
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private boolean drawCalled = false;
RevealAnimator revealAnimator;
@Override
public Animator createCircularReveal(int x, int y, float startRadius, float finishRadius) {
if (Carbon.IS_LOLLIPOP && renderingMode == RenderingMode.Auto) {
Animator circularReveal = ViewAnimationUtils.createCircularReveal(this, x, y, startRadius, finishRadius);
circularReveal.setDuration(Carbon.getDefaultRevealDuration());
return circularReveal;
} else {
revealAnimator = new RevealAnimator(x, y, startRadius, finishRadius);
revealAnimator.setDuration(Carbon.getDefaultRevealDuration());
revealAnimator.addUpdateListener(animation -> {
RevealAnimator reveal = ((RevealAnimator) animation);
reveal.radius = (float) reveal.getAnimatedValue();
reveal.mask.reset();
reveal.mask.addCircle(reveal.x, reveal.y, Math.max((Float) reveal.getAnimatedValue(), 1), Path.Direction.CW);
postInvalidate();
});
revealAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
revealAnimator = null;
}
@Override
public void onAnimationEnd(Animator animation) {
revealAnimator = null;
}
});
return revealAnimator;
}
}
@Override
protected void dispatchDraw(@NonNull Canvas canvas) {
boolean r = revealAnimator != null && revealAnimator.isRunning();
boolean c = cornerRadius > 0;
// draw not called, we have to handle corners here
if (isInEditMode() && !drawCalled && (r || c) && getWidth() > 0 && getHeight() > 0) {
Bitmap layer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas layerCanvas = new Canvas(layer);
internalDispatchDraw(layerCanvas);
Bitmap mask = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas maskCanvas = new Canvas(mask);
Paint maskPaint = new Paint(0xffffffff);
maskCanvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), cornerRadius, cornerRadius, maskPaint);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int maskPixel = mask.getPixel(x, y);
layer.setPixel(x, y, Color.alpha(maskPixel) > 0 ? layer.getPixel(x, y) : 0);
}
}
canvas.drawBitmap(layer, 0, 0, paint);
} else if (!drawCalled && (r || c) && getWidth() > 0 && getHeight() > 0 && !Carbon.IS_LOLLIPOP || renderingMode == RenderingMode.Software) {
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
if (r) {
int saveCount2 = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(revealAnimator.x - revealAnimator.radius, revealAnimator.y - revealAnimator.radius, revealAnimator.x + revealAnimator.radius, revealAnimator.y + revealAnimator.radius);
internalDispatchDraw(canvas);
canvas.restoreToCount(saveCount2);
} else {
internalDispatchDraw(canvas);
}
paint.setXfermode(Carbon.CLEAR_MODE);
if (c)
canvas.drawPath(cornersMask, paint);
if (r)
canvas.drawPath(revealAnimator.mask, paint);
paint.setXfermode(null);
canvas.restoreToCount(saveCount);
} else {
internalDispatchDraw(canvas);
}
drawCalled = false;
}
private void internalDispatchDraw(@NonNull Canvas canvas) {
Collections.sort(getViews(), new ElevationComparator());
super.dispatchDraw(canvas);
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Over)
rippleDrawable.draw(canvas);
if (insetColor != 0) {
paint.setColor(insetColor);
paint.setAlpha(255);
if (insetLeft != 0)
canvas.drawRect(0, 0, insetLeft, getHeight(), paint);
if (insetTop != 0)
canvas.drawRect(0, 0, getWidth(), insetTop, paint);
if (insetRight != 0)
canvas.drawRect(getWidth() - insetRight, 0, getWidth(), getHeight(), paint);
if (insetBottom != 0)
canvas.drawRect(0, getHeight() - insetBottom, getWidth(), getHeight(), paint);
}
}
@Override
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
if (child instanceof ShadowView && (!Carbon.IS_LOLLIPOP || ((RenderingModeView) child).getRenderingMode() == RenderingMode.Software || ((ShadowView) child).getElevationShadowColor() != null)) {
ShadowView shadowView = (ShadowView) child;
shadowView.drawShadow(canvas);
}
if (child instanceof RippleView) {
RippleView rippleView = (RippleView) child;
RippleDrawable rippleDrawable = rippleView.getRippleDrawable();
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless) {
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(child.getLeft(), child.getTop());
canvas.concat(child.getMatrix());
rippleDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
return super.drawChild(canvas, child, drawingTime);
}
@Override
public boolean gatherTransparentRegion(Region region) {
getViews();
return super.gatherTransparentRegion(region);
}
@Override
protected int getChildDrawingOrder(int childCount, int child) {
return views.size() > child ? indexOfChild(views.get(child)) : child;
}
protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
final Rect frame = new Rect();
child.getHitRect(frame);
return frame.contains((int) x, (int) y);
}
// -------------------------------
// corners
// -------------------------------
private float cornerRadius;
private Path cornersMask;
public float getCornerRadius() {
return cornerRadius;
}
public void setCornerRadius(float cornerRadius) {
this.cornerRadius = cornerRadius;
if (getWidth() > 0 && getHeight() > 0)
updateCorners();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
layoutAnchoredViews();
if (!changed)
return;
if (getWidth() == 0 || getHeight() == 0)
return;
updateCorners();
if (rippleDrawable != null)
rippleDrawable.setBounds(0, 0, getWidth(), getHeight());
}
private void updateCorners() {
if (cornerRadius > 0) {
cornerRadius = Math.min(cornerRadius, Math.min(getWidth(), getHeight()) / 2.0f);
if (Carbon.IS_LOLLIPOP && renderingMode == RenderingMode.Auto) {
setClipToOutline(true);
setOutlineProvider(ShadowShape.viewOutlineProvider);
} else {
cornersMask = new Path();
cornersMask.addRoundRect(new RectF(0, 0, getWidth(), getHeight()), cornerRadius, cornerRadius, Path.Direction.CW);
cornersMask.setFillType(Path.FillType.INVERSE_WINDING);
}
} else if (Carbon.IS_LOLLIPOP) {
setOutlineProvider(ViewOutlineProvider.BOUNDS);
}
}
@Override
public void draw(@NonNull Canvas canvas) {
drawCalled = true;
boolean r = revealAnimator != null;
boolean c = cornerRadius > 0;
if (isInEditMode() && (r || c) && getWidth() > 0 && getHeight() > 0) {
Bitmap layer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas layerCanvas = new Canvas(layer);
super.draw(layerCanvas);
Bitmap mask = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas maskCanvas = new Canvas(mask);
Paint maskPaint = new Paint(0xffffffff);
maskCanvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), cornerRadius, cornerRadius, maskPaint);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int maskPixel = mask.getPixel(x, y);
layer.setPixel(x, y, Color.alpha(maskPixel) > 0 ? layer.getPixel(x, y) : 0);
}
}
canvas.drawBitmap(layer, 0, 0, paint);
} else if ((r || c) && getWidth() > 0 && getHeight() > 0 && !Carbon.IS_LOLLIPOP || renderingMode == RenderingMode.Software) {
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
if (r) {
int saveCount2 = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(revealAnimator.x - revealAnimator.radius, revealAnimator.y - revealAnimator.radius, revealAnimator.x + revealAnimator.radius, revealAnimator.y + revealAnimator.radius);
super.draw(canvas);
canvas.restoreToCount(saveCount2);
} else {
super.draw(canvas);
}
paint.setXfermode(Carbon.CLEAR_MODE);
if (c)
canvas.drawPath(cornersMask, paint);
if (r)
canvas.drawPath(revealAnimator.mask, paint);
paint.setXfermode(null);
canvas.restoreToCount(saveCount);
} else {
super.draw(canvas);
}
}
// -------------------------------
// ripple
// -------------------------------
private RippleDrawable rippleDrawable;
private Transformation t = new Transformation();
@Override
public boolean dispatchTouchEvent(@NonNull MotionEvent event) {
Animation a = getAnimation();
if (a != null) {
a.getTransformation(event.getEventTime(), t);
float[] loc = new float[]{event.getX(), event.getY()};
t.getMatrix().mapPoints(loc);
event.setLocation(loc[0], loc[1]);
}
if (onDispatchTouchListener != null && onDispatchTouchListener.onTouch(this, event))
return true;
if (rippleDrawable != null && event.getAction() == MotionEvent.ACTION_DOWN)
rippleDrawable.setHotspot(event.getX(), event.getY());
return super.dispatchTouchEvent(event);
}
@Override
public RippleDrawable getRippleDrawable() {
return rippleDrawable;
}
@Override
public void setRippleDrawable(RippleDrawable newRipple) {
if (rippleDrawable != null) {
rippleDrawable.setCallback(null);
if (rippleDrawable.getStyle() == RippleDrawable.Style.Background)
super.setBackgroundDrawable(rippleDrawable.getBackground());
}
if (newRipple != null) {
newRipple.setCallback(this);
newRipple.setBounds(0, 0, getWidth(), getHeight());
if (newRipple.getStyle() == RippleDrawable.Style.Background)
super.setBackgroundDrawable((Drawable) newRipple);
}
rippleDrawable = newRipple;
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || rippleDrawable == who;
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
super.invalidateDrawable(drawable);
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).invalidate();
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).invalidate();
}
@Override
public void invalidate(@NonNull Rect dirty) {
super.invalidate(dirty);
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).invalidate(dirty);
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).invalidate(dirty);
}
@Override
public void invalidate(int l, int t, int r, int b) {
super.invalidate(l, t, r, b);
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).invalidate(l, t, r, b);
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).invalidate(l, t, r, b);
}
@Override
public void invalidate() {
super.invalidate();
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).invalidate();
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).invalidate();
}
@Override
public void postInvalidateDelayed(long delayMilliseconds) {
super.postInvalidateDelayed(delayMilliseconds);
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).postInvalidateDelayed(delayMilliseconds);
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).postInvalidateDelayed(delayMilliseconds);
}
@Override
public void postInvalidateDelayed(long delayMilliseconds, int left, int top, int right, int bottom) {
super.postInvalidateDelayed(delayMilliseconds, left, top, right, bottom);
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).postInvalidateDelayed(delayMilliseconds, left, top, right, bottom);
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).postInvalidateDelayed(delayMilliseconds, left, top, right, bottom);
}
@Override
public void postInvalidate() {
super.postInvalidate();
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).postInvalidate();
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).postInvalidate();
}
@Override
public void postInvalidate(int left, int top, int right, int bottom) {
super.postInvalidate(left, top, right, bottom);
if (getParent() == null || !(getParent() instanceof View))
return;
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
((View) getParent()).postInvalidate(left, top, right, bottom);
if (getElevation() > 0 || getCornerRadius() > 0)
((View) getParent()).postInvalidate(left, top, right, bottom);
}
@Override
public void setBackground(Drawable background) {
setBackgroundDrawable(background);
}
@Override
public void setBackgroundDrawable(Drawable background) {
if (background instanceof RippleDrawable) {
setRippleDrawable((RippleDrawable) background);
return;
}
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Background) {
rippleDrawable.setCallback(null);
rippleDrawable = null;
}
super.setBackgroundDrawable(background);
}
// -------------------------------
// elevation
// -------------------------------
private float elevation = 0;
private float translationZ = 0;
private Shadow ambientShadow, spotShadow;
private ColorStateList shadowColor;
private PorterDuffColorFilter shadowColorFilter;
private RectF shadowMaskRect = new RectF();
@Override
public float getElevation() {
return elevation;
}
@Override
public void setElevation(float elevation) {
if (Carbon.IS_LOLLIPOP) {
if (shadowColor == null && renderingMode == RenderingMode.Auto) {
super.setElevation(elevation);
super.setTranslationZ(translationZ);
} else {
super.setElevation(0);
super.setTranslationZ(0);
}
} else if (elevation != this.elevation && getParent() != null) {
((View) getParent()).postInvalidate();
}
this.elevation = elevation;
}
@Override
public float getTranslationZ() {
return translationZ;
}
public void setTranslationZ(float translationZ) {
if (translationZ == this.translationZ)
return;
if (Carbon.IS_LOLLIPOP) {
if (shadowColor == null && renderingMode == RenderingMode.Auto) {
super.setTranslationZ(translationZ);
} else {
super.setTranslationZ(0);
}
} else if (translationZ != this.translationZ && getParent() != null) {
((View) getParent()).postInvalidate();
}
this.translationZ = translationZ;
}
@Override
public ShadowShape getShadowShape() {
if (cornerRadius == getWidth() / 2 && getWidth() == getHeight())
return ShadowShape.CIRCLE;
if (cornerRadius > 0)
return ShadowShape.ROUND_RECT;
return ShadowShape.RECT;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
}
@Override
public boolean hasShadow() {
return getElevation() + getTranslationZ() >= 0.01f && getWidth() > 0 && getHeight() > 0;
}
@Override
public void drawShadow(Canvas canvas) {
float alpha = getAlpha() * Carbon.getDrawableAlpha(getBackground()) / 255.0f * Carbon.getBackgroundTintAlpha(this) / 255.0f;
if (alpha == 0)
return;
if (!hasShadow())
return;
float z = getElevation() + getTranslationZ();
if (ambientShadow == null || ambientShadow.elevation != z || ambientShadow.cornerRadius != cornerRadius) {
ambientShadow = ShadowGenerator.generateShadow(this, z / getResources().getDisplayMetrics().density / 4);
spotShadow = ShadowGenerator.generateShadow(this, z / getResources().getDisplayMetrics().density);
}
int saveCount = 0;
boolean maskShadow = getBackground() != null && alpha != 1;
boolean r = revealAnimator != null && revealAnimator.isRunning();
if (maskShadow) {
saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
} else if (r) {
saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.clipRect(
getLeft() + revealAnimator.x - revealAnimator.radius, getTop() + revealAnimator.y - revealAnimator.radius,
getLeft() + revealAnimator.x + revealAnimator.radius, getTop() + revealAnimator.y + revealAnimator.radius);
}
paint.setAlpha((int) (Shadow.ALPHA * alpha));
Matrix matrix = getMatrix();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(this.getLeft(), this.getTop());
canvas.concat(matrix);
ambientShadow.draw(canvas, this, paint, shadowColorFilter);
canvas.restore();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(this.getLeft(), this.getTop() + z / 2);
canvas.concat(matrix);
spotShadow.draw(canvas, this, paint, shadowColorFilter);
canvas.restore();
if (saveCount != 0) {
canvas.translate(this.getLeft(), this.getTop());
canvas.concat(matrix);
paint.setXfermode(Carbon.CLEAR_MODE);
}
if (maskShadow) {
shadowMaskRect.set(0, 0, getWidth(), getHeight());
canvas.drawRoundRect(shadowMaskRect, cornerRadius, cornerRadius, paint);
}
if (r) {
canvas.drawPath(revealAnimator.mask, paint);
}
if (saveCount != 0) {
canvas.restoreToCount(saveCount);
paint.setXfermode(null);
}
}
@Override
public void setElevationShadowColor(ColorStateList shadowColor) {
this.shadowColor = shadowColor;
shadowColorFilter = shadowColor != null ? new PorterDuffColorFilter(shadowColor.getColorForState(getDrawableState(), shadowColor.getDefaultColor()), PorterDuff.Mode.MULTIPLY) : Shadow.DEFAULT_FILTER;
setElevation(elevation);
setTranslationZ(translationZ);
}
@Override
public void setElevationShadowColor(int color) {
shadowColor = ColorStateList.valueOf(color);
shadowColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY);
setElevation(elevation);
setTranslationZ(translationZ);
}
@Override
public ColorStateList getElevationShadowColor() {
return shadowColor;
}
// -------------------------------
// touch margin
// -------------------------------
private Rect touchMargin = new Rect();
@Override
public void setTouchMargin(int left, int top, int right, int bottom) {
touchMargin.set(left, top, right, bottom);
}
@Override
public void setTouchMarginLeft(int margin) {
touchMargin.left = margin;
}
@Override
public void setTouchMarginTop(int margin) {
touchMargin.top = margin;
}
@Override
public void setTouchMarginRight(int margin) {
touchMargin.right = margin;
}
@Override
public void setTouchMarginBottom(int margin) {
touchMargin.bottom = margin;
}
@Override
public Rect getTouchMargin() {
return touchMargin;
}
public void getHitRect(@NonNull Rect outRect) {
if (touchMargin == null) {
super.getHitRect(outRect);
return;
}
outRect.set(getLeft() - touchMargin.left, getTop() - touchMargin.top, getRight() + touchMargin.right, getBottom() + touchMargin.bottom);
}
// -------------------------------
// state animators
// -------------------------------
private StateAnimator stateAnimator = new StateAnimator(this);
@Override
public StateAnimator getStateAnimator() {
return stateAnimator;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (rippleDrawable != null && rippleDrawable.getStyle() != RippleDrawable.Style.Background)
rippleDrawable.setState(getDrawableState());
if (stateAnimator != null)
stateAnimator.setState(getDrawableState());
}
// -------------------------------
// animations
// -------------------------------
private Animator inAnim = null, outAnim = null;
private Animator animator;
public Animator animateVisibility(final int visibility) {
if (visibility == View.VISIBLE && (getVisibility() != View.VISIBLE || animator != null)) {
if (animator != null)
animator.cancel();
if (inAnim != null) {
animator = inAnim;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator a) {
animator.removeListener(this);
animator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
animator.removeListener(this);
animator = null;
}
});
animator.start();
}
setVisibility(visibility);
} else if (visibility != View.VISIBLE && (getVisibility() == View.VISIBLE || animator != null)) {
if (animator != null)
animator.cancel();
if (outAnim == null) {
setVisibility(visibility);
return null;
}
animator = outAnim;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator a) {
if (((ValueAnimator) a).getAnimatedFraction() == 1)
setVisibility(visibility);
animator.removeListener(this);
animator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
animator.removeListener(this);
animator = null;
}
});
animator.start();
}
return animator;
}
public Animator getAnimator() {
return animator;
}
public Animator getOutAnimator() {
return outAnim;
}
public void setOutAnimator(Animator outAnim) {
if (this.outAnim != null)
this.outAnim.setTarget(null);
this.outAnim = outAnim;
if (outAnim != null)
outAnim.setTarget(this);
}
public Animator getInAnimator() {
return inAnim;
}
public void setInAnimator(Animator inAnim) {
if (this.inAnim != null)
this.inAnim.setTarget(null);
this.inAnim = inAnim;
if (inAnim != null)
inAnim.setTarget(this);
}
// -------------------------------
// insets
// -------------------------------
int insetLeft = INSET_NULL, insetTop = INSET_NULL, insetRight = INSET_NULL, insetBottom = INSET_NULL;
int insetColor;
private OnInsetsChangedListener onInsetsChangedListener;
public int getInsetColor() {
return insetColor;
}
public void setInsetColor(int insetsColor) {
this.insetColor = insetsColor;
}
public void setInset(int left, int top, int right, int bottom) {
insetLeft = left;
insetTop = top;
insetRight = right;
insetBottom = bottom;
}
public int getInsetLeft() {
return insetLeft;
}
public void setInsetLeft(int insetLeft) {
this.insetLeft = insetLeft;
}
public int getInsetTop() {
return insetTop;
}
public void setInsetTop(int insetTop) {
this.insetTop = insetTop;
}
public int getInsetRight() {
return insetRight;
}
public void setInsetRight(int insetRight) {
this.insetRight = insetRight;
}
public int getInsetBottom() {
return insetBottom;
}
public void setInsetBottom(int insetBottom) {
this.insetBottom = insetBottom;
}
@Override
protected boolean fitSystemWindows(@NonNull Rect insets) {
if (insetLeft == INSET_NULL)
insetLeft = insets.left;
if (insetTop == INSET_NULL)
insetTop = insets.top;
if (insetRight == INSET_NULL)
insetRight = insets.right;
if (insetBottom == INSET_NULL)
insetBottom = insets.bottom;
insets.set(insetLeft, insetTop, insetRight, insetBottom);
if (onInsetsChangedListener != null)
onInsetsChangedListener.onInsetsChanged();
postInvalidate();
return super.fitSystemWindows(insets);
}
public void setOnInsetsChangedListener(OnInsetsChangedListener onInsetsChangedListener) {
this.onInsetsChangedListener = onInsetsChangedListener;
}
// -------------------------------
// ViewGroup utils
// -------------------------------
List<View> views = new ArrayList<>();
public List<View> getViews() {
views.clear();
for (int i = 0; i < getChildCount(); i++)
views.add(getChildAt(i));
return views;
}
public void setOnDispatchTouchListener(OnTouchListener onDispatchTouchListener) {
this.onDispatchTouchListener = onDispatchTouchListener;
}
public Component findComponentById(int id) {
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child instanceof ComponentView && ((ComponentView) child).getComponent().getView().getId() == id)
return ((ComponentView) child).getComponent();
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return null;
}
public List<Component> findComponentsById(int id) {
List<Component> result = new ArrayList<>();
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child instanceof ComponentView && ((ComponentView) child).getComponent().getView().getId() == id)
result.add(((ComponentView) child).getComponent());
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return result;
}
public Component findComponentOfType(Class type) {
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child instanceof ComponentView && ((ComponentView) child).getComponent().getClass().equals(type))
return ((ComponentView) child).getComponent();
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return null;
}
public List<Component> findComponentsOfType(Class type) {
List<Component> result = new ArrayList<>();
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child instanceof ComponentView && ((ComponentView) child).getComponent().getClass().equals(type))
result.add(((ComponentView) child).getComponent());
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return result;
}
public View findViewOfType(Class type) {
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child.getClass().equals(type))
return child;
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return null;
}
public List<View> findViewsOfType(Class type) {
List<View> result = new ArrayList<>();
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child.getClass().equals(type))
result.add(child);
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return result;
}
public List<View> findViewsById(int id) {
List<View> result = new ArrayList<>();
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child.getId() == id)
result.add(child);
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return result;
}
public List<View> findViewsWithTag(Object tag) {
List<View> result = new ArrayList<>();
List<ViewGroup> groups = new ArrayList<>();
groups.add(this);
while (!groups.isEmpty()) {
ViewGroup group = groups.remove(0);
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (tag.equals(child.getTag()))
result.add(child);
if (child instanceof ViewGroup)
groups.add((ViewGroup) child);
}
}
return result;
}
// -------------------------------
// layout params
// -------------------------------
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(super.generateDefaultLayoutParams());
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
private void layoutAnchoredViews() {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.anchorView != 0) {
View anchorView = findViewById(lp.anchorView);
if (anchorView != null && anchorView != child) {
int left = child.getLeft();
int right = child.getRight();
int top = child.getTop();
int bottom = child.getBottom();
if ((lp.anchorGravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
top = anchorView.getBottom() - lp.height / 2;
bottom = top + lp.height;
}
if ((lp.anchorGravity & Gravity.TOP) == Gravity.TOP) {
top = anchorView.getTop() - lp.height / 2;
bottom = top + lp.height;
}
if ((GravityCompat.getAbsoluteGravity(lp.anchorGravity, ViewCompat.getLayoutDirection(child)) & Gravity.LEFT) == Gravity.LEFT) {
left = anchorView.getLeft() - lp.width / 2;
right = left + lp.width;
}
if ((GravityCompat.getAbsoluteGravity(lp.anchorGravity, ViewCompat.getLayoutDirection(child)) & Gravity.RIGHT) == Gravity.RIGHT) {
left = anchorView.getRight() - lp.width / 2;
right = left + lp.width;
}
child.layout(left, top, right, bottom);
}
}
}
}
}
public static class LayoutParams extends android.support.design.widget.AppBarLayout.LayoutParams {
public int anchorView;
private int anchorGravity;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_Layout);
anchorView = a.getResourceId(R.styleable.AppBarLayout_Layout_carbon_anchor, -1);
anchorGravity = a.getInt(R.styleable.AppBarLayout_Layout_carbon_anchorGravity, -1);
a.recycle();
}
public LayoutParams(int w, int h) {
super(w, h);
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(LayoutParams source) {
super((MarginLayoutParams) source);
this.anchorView = source.anchorView;
this.anchorGravity = source.anchorGravity;
}
public int getAnchorGravity() {
return anchorGravity;
}
public void setAnchorGravity(int anchorGravity) {
this.anchorGravity = anchorGravity;
}
public int getAnchorView() {
return anchorView;
}
public void setAnchorView(int anchorView) {
this.anchorView = anchorView;
}
}
// -------------------------------
// maximum width & height
// -------------------------------
int maxWidth = Integer.MAX_VALUE, maxHeight = Integer.MAX_VALUE;
@Override
public int getMaximumWidth() {
return maxWidth;
}
@Override
public void setMaximumWidth(int maxWidth) {
this.maxWidth = maxWidth;
requestLayout();
}
@Override
public int getMaximumHeight() {
return maxHeight;
}
@Override
public void setMaximumHeight(int maxHeight) {
this.maxHeight = maxHeight;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() > maxWidth || getMeasuredHeight() > maxHeight) {
if (getMeasuredWidth() > maxWidth)
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY);
if (getMeasuredHeight() > maxHeight)
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
// -------------------------------
// rendering mode
// -------------------------------
private RenderingMode renderingMode = RenderingMode.Auto;
@Override
public void setRenderingMode(RenderingMode mode) {
this.renderingMode = mode;
setElevation(elevation);
setTranslationZ(translationZ);
updateCorners();
}
@Override
public RenderingMode getRenderingMode() {
return renderingMode;
}
}