package com.forfan.bigbang.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
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 com.forfan.bigbang.R;
import com.forfan.bigbang.util.LogUtil;
import com.forfan.bigbang.util.ViewUtil;
/**
* 子菜单项布局
*
* @author 何凌波
*/
public class ArcLayout extends ViewGroup {
private int mChildSize; // 子菜单项大小相同
private int mChildPadding = 5;
public static final float DEFAULT_FROM_DEGREES = 270.0f;
public static final float DEFAULT_TO_DEGREES = 360.0f;
private float mFromDegrees = DEFAULT_FROM_DEGREES;
private float mToDegrees = DEFAULT_TO_DEGREES;
private static final int DEFAULT_MIN_RADIUS = ViewUtil.dp2px(50);
private static int MIN_RADIUS = DEFAULT_MIN_RADIUS;
private int mRadius;// 中心菜单圆点到子菜单中心的距离
private boolean mExpanded = false;
private int position = PathMenu.LEFT_TOP;
private int centerX = 0;
private int centerY = 0;
public void computeCenterXY(int position) {
switch (position) {
case PathMenu.LEFT_TOP://左上
centerX = getWidth() / 2 - getRadiusAndPadding();
centerY = getHeight() / 2 - getRadiusAndPadding();
break;
case PathMenu.LEFT_CENTER://左中
centerX = getWidth() / 2 - getRadiusAndPadding();
centerY = getHeight() / 2;
break;
case PathMenu.LEFT_BOTTOM://左下
centerX = getWidth() / 2 - getRadiusAndPadding();
centerY = getHeight() / 2 + getRadiusAndPadding();
break;
case PathMenu.CENTER_TOP://上中
centerX = getWidth() / 2;
centerY = getHeight() / 2 - getRadiusAndPadding();
break;
case PathMenu.CENTER_BOTTOM://下中
centerX = getWidth() / 2;
centerY = getHeight() / 2 + getRadiusAndPadding();
break;
case PathMenu.RIGHT_TOP://右上
centerX = getWidth() / 2 + getRadiusAndPadding();
centerY = getHeight() / 2 - getRadiusAndPadding();
break;
case PathMenu.RIGHT_CENTER://右中
centerX = getWidth() / 2 + getRadiusAndPadding();
centerY = getHeight() / 2;
break;
case PathMenu.RIGHT_BOTTOM://右下
centerX = getWidth() / 2 + getRadiusAndPadding();
centerY = getHeight() / 2 + getRadiusAndPadding();
break;
case PathMenu.CENTER:
centerX = getWidth() / 2;
centerY = getHeight() / 2;
break;
}
}
private int getRadiusAndPadding() {
return mRadius + (mChildPadding * 2);
}
public ArcLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取自定义属性,设定默认值
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ArcLayout, 0, 0);
mFromDegrees = a.getFloat(R.styleable.ArcLayout_fromDegrees,
DEFAULT_FROM_DEGREES);
mToDegrees = a.getFloat(R.styleable.ArcLayout_toDegrees,
DEFAULT_TO_DEGREES);
mChildSize = Math
.max(a.getDimensionPixelSize(
R.styleable.ArcLayout_childSize, 0), 0);
a.recycle();
}
}
/**
* 计算半径
*/
private static int computeRadius(final float arcDegrees,
final int childCount, final int childSize, final int childPadding,
final int minRadius) {
if (childCount < 2) {
return minRadius;
}
// final float perDegrees = arcDegrees / (childCount - 1);
final float perDegrees = arcDegrees == 360 ? (arcDegrees) / (childCount) : (arcDegrees) / (childCount - 1);
final float perHalfDegrees = perDegrees / 2;
final int perSize = childSize + childPadding;
final int radius = (int) ((perSize / 2) / Math.sin(Math
.toRadians(perHalfDegrees)));
return Math.max(radius, minRadius);
}
/**
* 计算子菜单项的范围
*/
private static Rect computeChildFrame(final int centerX, final int centerY,
final int radius, final float degrees, final int size) {
//子菜单项中心点
final double childCenterX = centerX + radius
* Math.cos(Math.toRadians(degrees));
final double childCenterY = centerY + radius
* Math.sin(Math.toRadians(degrees));
//子菜单项的左上角,右上角,左下角,右下角
return new Rect((int) (childCenterX - size / 2),
(int) (childCenterY - size / 2),
(int) (childCenterX + size / 2),
(int) (childCenterY + size / 2));
}
public int getRadius() {
return mRadius;
}
public void setMinRadius(float persent){
MIN_RADIUS= (int) (DEFAULT_MIN_RADIUS*persent);
}
/**
* 子菜单项大小
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
int radius = mRadius = computeRadius(
Math.abs(mToDegrees - mFromDegrees), getChildCount(),
mChildSize, mChildPadding, MIN_RADIUS);
Log.i("layout", "radius:" + radius);
int layoutPadding = 10;
int size = radius * 2 + mChildSize + mChildPadding
+ layoutPadding * 2;
Log.i("layout", "size:" + size);
setMeasuredDimension(size, size);
final int count = getChildCount();
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 centerX = getWidth() / 2 - mRadius;
// final int centerY = getHeight() / 2;
computeCenterXY(position);
//当子菜单要收缩时radius=0,在ViewGroup坐标中心
final int radius = mExpanded ? mRadius : 0;
final int childCount = getChildCount();
// final float perDegrees =Math.abs (mToDegrees - mFromDegrees) / (childCount - 1);
final float perDegrees = Math.abs(mToDegrees - mFromDegrees) == 360 ? (Math.abs(mToDegrees - mFromDegrees)) / (childCount) : (Math.abs(mToDegrees - mFromDegrees)) / (childCount - 1);
float degrees = mFromDegrees;
for (int i = 0; i < childCount; i++) {
Rect frame = computeChildFrame(centerX, centerY, radius, degrees,
mChildSize);
degrees += perDegrees;
getChildAt(i).layout(frame.left, frame.top, frame.right,
frame.bottom);
}
}
/**
* 计算动画开始时的偏移量
*/
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) {
if (expanded) {
return count - 1 - index;
}
return 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);
//收缩过程中,child 逆时针自旋转360度
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);
//收缩过程中位移,并逆时针旋转360度
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 centerX = getWidth() / 2 - mRadius; //ViewGroup的中心X坐标
// final int centerY = getHeight() / 2;
computeCenterXY(position);
final int radius = expanded ? 0 : mRadius;
final int childCount = getChildCount();
final float perDegrees = Math.abs(mToDegrees - mFromDegrees) == 360 ? (mToDegrees - mFromDegrees) / (childCount) : (mToDegrees - mFromDegrees) / (childCount - 1);
Rect frame = computeChildFrame(centerX, centerY, radius, mFromDegrees
+ index * perDegrees, mChildSize);
final int toXDelta = frame.left - child.getLeft();//展开或收缩动画,child沿X轴位移距离
final int toYDelta = frame.top - child.getTop();//展开或收缩动画,child沿Y轴位移距离
LogUtil.d("arcLayout","toX:"+ toXDelta+" toY:"+toYDelta);
Interpolator interpolator = mExpanded ? new AccelerateInterpolator()
: new OvershootInterpolator(1.5f);
final long startOffset = computeStartOffset(childCount, mExpanded,
index, 0.1f, duration, interpolator);
//TODO toXDelta toYDelta 设置为0 为什么可以
//mExpanded为true,已经展开,收缩动画;为false,展开动画
Animation animation = mExpanded ? createShrinkAnimation(0, toXDelta, 0,
toYDelta, startOffset, duration, interpolator)
: createExpandAnimation(0, 0, 0, 0, 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 setArc(float fromDegrees, float toDegrees, int position) {
this.position = position;
if (mFromDegrees == fromDegrees && mToDegrees == toDegrees) {
return;
}
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
computeCenterXY(position);
requestLayout();
}
/**
* 设定弧度
*/
public void setArc(float fromDegrees, float toDegrees) {
if (mFromDegrees == fromDegrees && mToDegrees == toDegrees) {
return;
}
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
computeCenterXY(position);
requestLayout();
}
/**
* 设定子菜单项大小
*/
public void setChildSize(int size) {
if (mChildSize == size || size < 0) {
return;
}
mChildSize = size;
requestLayout();
}
public int getChildSize() {
return mChildSize;
}
/**
* 切换中心按钮的展开缩小
*/
public void switchState(final boolean showAnimation, int position) {
this.position = position;
if (showAnimation) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
bindChildAnimation(getChildAt(i), i, 300);
}
}
mExpanded = !mExpanded;
if (!showAnimation) {
requestLayout();
}
invalidate();
}
public void setExpand(boolean expand){
mExpanded = expand;
}
/**
* 切换中心按钮的展开缩小
*/
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();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("shang-path-menu-layout",""+event);
return super.onTouchEvent(event);
}
}