学自鸿洋(hyman)的imooc视频
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import com.stone.satellitemenu.R;
/**
* 卫星式菜单
* author : stone
* email : aa86799@163.com
* time : 15/5/7 14 17
*/
public class SateliteMenu extends ViewGroup implements View.OnClickListener {
public enum Position {
POS_LEFT_TOP, POS_RIGHT_TOP, POS_LEFT_BOTTOM, POS_RIGHT_BOTTOM
}
private final int LEFT_TOP = 1;
private final int RIGHT_TOP = 2;
private final int LEFT_BOTTOM = 4;
private final int RIGHT_BOTTOM = 8;
private final int STATUS_OPEN = 0; //菜单的状态 打开
private final int STATUS_CLOSE = 1; //菜单的状态 关闭
private Position mPosition;
private int mRadius;
private int mStatus;
private onMenuItemClickListener mMenuItemClickListener;
private View mMenuButton;
public interface onMenuItemClickListener {
/**
* @param view item-view
* @param position item-position
*/
void onItemClick(View view, int position);
}
public SateliteMenu(Context context) {
this(context, null);
}
public SateliteMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SateliteMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SateliteMenu);
int position = typedArray.getInt(R.styleable.SateliteMenu_position, LEFT_TOP);
//定义半径默认值
float defRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());
switch (position) {
case LEFT_TOP:
mPosition = Position.POS_LEFT_TOP;
break;
case RIGHT_TOP:
mPosition = Position.POS_RIGHT_TOP;
break;
case LEFT_BOTTOM:
mPosition = Position.POS_LEFT_BOTTOM;
break;
case RIGHT_BOTTOM:
mPosition = Position.POS_RIGHT_BOTTOM;
break;
}
mRadius = (int) typedArray.getDimension(R.styleable.SateliteMenu_radius, defRadius);
typedArray.recycle(); //回收
mStatus = STATUS_CLOSE; //默认关闭状态
}
public void setOnMenuItemClickListener(onMenuItemClickListener menuItemClickListener) {
this.mMenuItemClickListener = menuItemClickListener;
}
public void setPosition(Position position) {
if (mPosition == position) {
return;
}
this.mPosition = position;
View child;
int count = getChildCount();
for (int i = 0; i < count; i++) {
child = getChildAt(i);
child.clearAnimation();
}
// invalidate(); //会触发 测量、布局和绘制
requestLayout(); //这里只要请求布局
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量子view
for (int i = 0, count = getChildCount(); i < count; i++) {
//需要传入父view的spec
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
}
@Override //lt 左上点 rb 右下点 如果 r<l 或 b<t 则无法显示了
protected void onLayout(boolean changed, int l, int t, int r, int b) {//l=0, t=0 因为是相对于父view的位置
layoutMenuButton();
/*
分析:
menuButton距离每个item为radius。
到item作直线,其夹角,应为90度均分。90/(item-1)=每个夹角的度数。
有角度,就能求出正弦值sina。
根据正弦公式:sina=a/c,且已知c=radius,求出a边长,即x坐标。
有角度,就能求出正弦值cosa。
余弦公式:cosa=b/c,且已知radius(斜边),求出b边长,即y坐标
*/
int count = getChildCount();
double angle = 90.0f / (count - 2);//这里-2,是多减去了一个menuButton
View child;
int w,h;
for (int i = 1; i < count; i++) {
child = getChildAt(i);
child.setVisibility(View.GONE);
w = child.getMeasuredWidth();
h = child.getMeasuredHeight();
double sin = 0, cos = 0;
//Math.toRadians:math.pi/180 * angle = 弧度 angel/180*pi<==>angel*pi/180
sin = Math.sin(Math.toRadians(angle * (i - 1))); //第i个角度的 sin(0)=0 i-1即从0开始,会有与屏幕直角边平行的 math.sin需要传弧度值
cos = Math.cos(Math.toRadians(angle * (i - 1)));// 邻直角边/斜边 cos(0)=1
l = (int) (mRadius * sin); //对横边长
t = (int) (mRadius * cos); //邻纵边长
//左上,左下 left值 就是上面的l l递增 符合默认变化规则
//左上,右上 top值 就是上面的t t递减 符合默认变化规则
//右上、右下 left值一样: 从右向左 递减
if (mPosition == Position.POS_RIGHT_TOP || mPosition == Position.POS_RIGHT_BOTTOM) {
l = getMeasuredWidth() - w - l;
}
//左下、右下 top值一样: 从上向下 递增
if (mPosition == Position.POS_LEFT_BOTTOM || mPosition == Position.POS_RIGHT_BOTTOM) {
t = getMeasuredHeight() - h - t;
}
child.layout(l, t, l + w, t + h);
final int pos = i;
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mMenuItemClickListener != null) {
mMenuItemClickListener.onItemClick(v, pos);
itemAnim(pos);
}
mStatus = STATUS_CLOSE; //关闭状态
}
});
}
}
/**
* 菜单按钮设置layout
*/
private void layoutMenuButton() {
mMenuButton = getChildAt(0);
int l = 0, t = 0;
int w = mMenuButton.getMeasuredWidth();
int h = mMenuButton.getMeasuredHeight();
switch (mPosition) {
case POS_LEFT_TOP:
l = t = 0;
break;
case POS_RIGHT_TOP:
l = getMeasuredWidth() - w;
t = 0;
break;
case POS_LEFT_BOTTOM:
l = 0;
t = getMeasuredHeight() - h;
break;
case POS_RIGHT_BOTTOM:
l = getMeasuredWidth() - w;
t = getMeasuredHeight() - h;
break;
}
mMenuButton.layout(l, t, w + l, h + t);
mMenuButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
rotateMenuBotton(mMenuButton, 360, 500);
toggleMenu(500);
}
private void rotateMenuBotton(View view, int angle, int duration) {
RotateAnimation anim = new RotateAnimation(
0, angle, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);
anim.setDuration(duration);
anim.setFillAfter(true); //view保持在动画结束位置
view.startAnimation(anim);
}
/**
* 展开/隐藏子菜单
* 子菜单动画 平移
*/
private void toggleMenu(int duration) {
int count = getChildCount();
for (int i = 1; i < count; i++) {
final View child = getChildAt(i);
/*
平移动画 以layout中计算的长度 再乘以1或-1
close:
左上 r->l b->t
右上 l->r b->t
左下 r->l t->b
右下 l->r t->b
open:
左上
右上
左下
右下
*/
int xflag = 1, yflag = 1;
//
if (mPosition == Position.POS_LEFT_TOP || mPosition == Position.POS_LEFT_BOTTOM) {
xflag = -1;
}
//
if (mPosition == Position.POS_LEFT_TOP || mPosition == Position.POS_RIGHT_TOP) {
yflag = -1;
}
double angle = 90 / (count - 2);
/*
一个圆的弧度是2π,角度是360° π/2即90度的弧度
*/
int oppositeLen = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * (i - 1))); //对边 横向
int adjacentLen = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * (i - 1))); //邻边 纵向
/*
一个圆的弧度是2π,角度是360° π/180,每角度对应的弧度 然后乘以角度数=其所对应的弧度
*/
// int oppositeLen = (int) (mRadius * Math.sin(angle * Math.PI / 180 * (i-1))); //对边 横向
// int adjacentLen = (int) (mRadius * Math.cos(angle * Math.PI / 180 * (i-1))); //邻边 纵向
int stopx = xflag * oppositeLen;
int stopy = yflag * adjacentLen;
AnimationSet set = new AnimationSet(true);
if (mStatus == STATUS_OPEN) {//如是打开,则要关闭
//4个值是起始点和结束点,相对于自身x、y的距离
TranslateAnimation tranAnim = new TranslateAnimation(0, stopx, 0, stopy);
tranAnim.setStartOffset(mRadius/6);//偏移
set.addAnimation(tranAnim);
AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);
set.addAnimation(alphaAnim);
set.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
setItemClickable(child, false);
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
} else { //要打开
TranslateAnimation tranAnim = new TranslateAnimation(stopx, 0, stopy, 0);
set.addAnimation(tranAnim);
AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
set.addAnimation(alphaAnim);
set.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
setItemClickable(child, false);
}
@Override
public void onAnimationEnd(Animation animation) {
setItemClickable(child, true);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
set.setDuration(duration);
set.setFillAfter(true);
child.startAnimation(set);
}
if (mStatus == STATUS_OPEN) {
mStatus = STATUS_CLOSE;
} else {
mStatus = STATUS_OPEN;
}
}
/**
* item点击动画
* @param position
*/
private void itemAnim(int position) {
View child;
int count = getChildCount();
for (int i = 1; i < count; i++) {
child = getChildAt(i);
if (position == i) {
scaleBigAnim(child);
} else {
scaleSmallAnim(child);
}
setItemClickable(child, false);
}
}
private void scaleBigAnim(View view) {
ScaleAnimation scaleAnim = new ScaleAnimation(
1.0f, 3f, 1.0f, 3f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);
AnimationSet set = new AnimationSet(true);
set.addAnimation(alphaAnim);
set.addAnimation(scaleAnim);
set.setDuration(800);
set.setFillAfter(true);
view.startAnimation(set);
}
private void scaleSmallAnim(View view) {
ScaleAnimation scaleAnim = new ScaleAnimation(
1.0f, 0, 1.0f, 0,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);
AnimationSet set = new AnimationSet(true);
set.addAnimation(alphaAnim);
set.addAnimation(scaleAnim);
set.setFillAfter(true);
set.setDuration(500);
view.startAnimation(set);
}
private void setItemClickable(View view, boolean flag) {
view.setClickable(flag);
view.setFocusable(flag);
}
}
我的自定义View项目地址: https://github.com/aa86799/MyCustomView (欢迎start&fork)
本文地址:https://github.com/aa86799/MyCustomView/tree/master/satellitemenu