package com.marshalchen.common.uimodule.arcmenu;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.Animation.AnimationListener;
import com.marshalchen.common.uimodule.widgets.R;
public class RayLayout extends ViewGroup {
/**
* children will be set the same size.
*/
private int mChildSize;
/* the distance between child Views */
private int mChildGap;
/* left space to place the switch button */
private int mLeftHolderWidth;
private boolean mExpanded = false;
public RayLayout(Context context) {
super(context);
}
public RayLayout(Context context, AttributeSet attrs) {
super(context, attrs);
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ArcLayout, 0, 0);
mChildSize = Math.max(a.getDimensionPixelSize(R.styleable.ArcLayout_arcChildSize, 0), 0);
a.recycle();
a = getContext().obtainStyledAttributes(attrs, R.styleable.RayLayout, 0, 0);
mLeftHolderWidth = Math.max(a.getDimensionPixelSize(R.styleable.RayLayout_arcLeftHolderWidth, 0), 0);
a.recycle();
}
}
private static int computeChildGap(final float width, final int childCount, final int childSize, final int minGap) {
return Math.max((int) (width / childCount - childSize), minGap);
}
private static Rect computeChildFrame(final boolean expanded, final int paddingLeft, final int childIndex,
final int gap, final int size) {
final int left = expanded ? (paddingLeft + childIndex * (gap + size) + gap) : ((paddingLeft - size) / 2);
return new Rect(left, 0, left + size, size);
}
@Override
protected int getSuggestedMinimumHeight() {
return mChildSize;
}
@Override
protected int getSuggestedMinimumWidth() {
return mLeftHolderWidth + mChildSize * getChildCount();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getSuggestedMinimumHeight(), MeasureSpec.EXACTLY));
final int count = getChildCount();
mChildGap = computeChildGap(getMeasuredWidth() - mLeftHolderWidth, count, mChildSize, 0);
for (int i = 0; i < count; i++) {
getChildAt(i).measure(MeasureSpec.makeMeasureSpec(mChildSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mChildSize, MeasureSpec.EXACTLY));
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = mLeftHolderWidth;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
Rect frame = computeChildFrame(mExpanded, paddingLeft, i, mChildGap, mChildSize);
getChildAt(i).layout(frame.left, frame.top, frame.right, frame.bottom);
}
}
/**
* refers to {@link LayoutAnimationController#getDelayForView(android.view.View view)}
*/
private static long computeStartOffset(final int childCount, final boolean expanded, final int index,
final float delayPercent, final long duration, Interpolator interpolator) {
final float delay = delayPercent * duration;
final long viewDelay = (long) (getTransformedIndex(expanded, childCount, index) * delay);
final float totalDelay = delay * childCount;
float normalizedDelay = viewDelay / totalDelay;
normalizedDelay = interpolator.getInterpolation(normalizedDelay);
return (long) (normalizedDelay * totalDelay);
}
private static int getTransformedIndex(final boolean expanded, final int count, final int index) {
return count - 1 - index;
}
private static Animation createExpandAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta,
long startOffset, long duration, Interpolator interpolator) {
Animation animation = new RotateAndTranslateAnimation(0, toXDelta, 0, toYDelta, 0, 720);
animation.setStartOffset(startOffset);
animation.setDuration(duration);
animation.setInterpolator(interpolator);
animation.setFillAfter(true);
return animation;
}
private static Animation createShrinkAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta,
long startOffset, long duration, Interpolator interpolator) {
AnimationSet animationSet = new AnimationSet(false);
animationSet.setFillAfter(true);
final long preDuration = duration / 2;
Animation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setStartOffset(startOffset);
rotateAnimation.setDuration(preDuration);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setFillAfter(true);
animationSet.addAnimation(rotateAnimation);
Animation translateAnimation = new RotateAndTranslateAnimation(0, toXDelta, 0, toYDelta, 360, 720);
translateAnimation.setStartOffset(startOffset + preDuration);
translateAnimation.setDuration(duration - preDuration);
translateAnimation.setInterpolator(interpolator);
translateAnimation.setFillAfter(true);
animationSet.addAnimation(translateAnimation);
return animationSet;
}
private void bindChildAnimation(final View child, final int index, final long duration) {
final boolean expanded = mExpanded;
final int childCount = getChildCount();
Rect frame = computeChildFrame(!expanded, mLeftHolderWidth, index, mChildGap, mChildSize);
final int toXDelta = frame.left - child.getLeft();
final int toYDelta = frame.top - child.getTop();
Interpolator interpolator = mExpanded ? new AccelerateInterpolator() : new OvershootInterpolator(1.5f);
final long startOffset = computeStartOffset(childCount, mExpanded, index, 0.1f, duration, interpolator);
Animation animation = mExpanded ? createShrinkAnimation(0, toXDelta, 0, toYDelta, startOffset, duration,
interpolator) : createExpandAnimation(0, toXDelta, 0, toYDelta, startOffset, duration, interpolator);
final boolean isLast = getTransformedIndex(expanded, childCount, index) == childCount - 1;
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (isLast) {
postDelayed(new Runnable() {
@Override
public void run() {
onAllAnimationsEnd();
}
}, 0);
}
}
});
child.setAnimation(animation);
}
public boolean isExpanded() {
return mExpanded;
}
public void setChildSize(int size) {
if (mChildSize == size || size < 0) {
return;
}
mChildSize = size;
requestLayout();
}
/**
* switch between expansion and shrinkage
*
* @param showAnimation
*/
public void switchState(final boolean showAnimation) {
if (showAnimation) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
bindChildAnimation(getChildAt(i), i, 300);
}
}
mExpanded = !mExpanded;
if (!showAnimation) {
requestLayout();
}
invalidate();
}
private void onAllAnimationsEnd() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).clearAnimation();
}
requestLayout();
}
}