效果图
啥都不说先看图:
![](https://img.haomeiwen.com/i6555017/462e91980a7a5221.gif)
如果还能入的了您的法眼,那就继续往下看,听我给你慢慢道来。
具体实现
1.布局
图中有五个圆形控件,当然可以是任意奇数个,每个圆形控件这里使用的是TextView添加的圆形背景。布局代码如下:
<com.example.arcmenu.view.ArcMenu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/id_arcmenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:radius="360dp"
app:angle="45" >
<TextView
android:id="@+id/id_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/yuan"
android:gravity="center"
android:text="A"
android:textSize="16dp"
android:textColor="#FFFFFF" />
<TextView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/yuan"
android:gravity="center"
android:text="B"
android:textSize="16dp"
android:textColor="#FFFFFF" />
<TextView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/yuan"
android:gravity="center"
android:text="+"
android:textSize="16dp"
android:textColor="#FFFFFF" />
<TextView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/yuan"
android:gravity="center"
android:text="C"
android:textSize="16dp"
android:textColor="#FFFFFF" />
<TextView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/yuan"
android:gravity="center"
android:text="D"
android:textSize="16dp"
android:textColor="#FFFFFF" />
</com.example.arcmenu.view.ArcMenu>
2.自定义属性
自定义了一个叫ArcMenu 的控件,并且添加了两个自定义属性radius(圆弧半径)和angle(圆弧的角度)。在attrs.xml添加如下代码:
<declare-styleable name="ArcMenu">
<attr name="radius" format="dimension" />
<attr name="angle" format="float" />
</declare-styleable>
这样就可以在ArcMenu中通过代码获取radius和angle的值。radius默认360dp,angle默认45° 。
// 获取自定义属性的值
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.ArcMenu, defStyleAttr, 0);
radius = (int) a.getDimension(R.styleable.ArcMenu_radius, TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 360,
getResources().getDisplayMetrics()));
angle = (float) (a.getFloat(R.styleable.ArcMenu_angle, 45) * Math.PI/180);
3.坐标计算
radius即为图中的r,angle即为图中的角1,我们用width表示View的宽度,count表示子View的个数,角2即为angle / (count-1)。
圆c的圆心坐标:
x轴坐标:width / 2 + radius * Math.sin( angle / (count - 1) )
y轴坐标:radius * Math.cos( angle / (count - 1) )
每个圆的圆心坐标:
x轴坐标:width / 2 + radius * Math.sin( angle / (count - 1) * (i-count/2) )
y轴坐标:radius * Math.cos( angle / (count - 1) * (i-count/2) )
其中,i为第几个圆,从0开始。
因此,在onLayout方法中设置子View的位置。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed){
int count = getChildCount();
//设置中间的子View为主按钮
mMainButton = getChildAt(count/2);
mMainButton.setOnClickListener(this);
for (int i = 0; i < count; i++){
View child = getChildAt(i);
//开始时隐藏非主按钮
if(!child.equals(mMainButton)){
child.setVisibility(View.GONE);
}
//子View的左上角坐标(cl,ct)
int cl = (int) (mRadius * Math.sin(mAngle / (count - 1) * (i-count/2)))
+ getMeasuredWidth()/2 - child.getMeasuredWidth()/2;
int ct = (int) (mRadius * Math.cos(mAngle / (count - 1) * (i-count/2))) ;
//测量的子View的宽,高
int cWidth = child.getMeasuredWidth();
int cHeight = child.getMeasuredHeight();
//设置子view的位置
child.layout(cl, ct, cl + cWidth, ct + cHeight);
}
}
4.添加动画
初始时只显示最中间的子View(称为主View),隐藏其他子View;点击主View展开其他子View,此时,每个子View移动的角度endAngle为:
endAngle = angle / (count-1) * (i-count/2)
这里使用ValueAnimator做动画,首先定义一个估值器AngleEvaluator:
public class AngleEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
float start = (Float) startValue;
float end = (Float) endValue;
float angle = start + fraction * (end-start);
return angle;
}
}
设置动画
anim = ValueAnimator.ofObject(new AngleEvaluator(), 0, endAngle);
再次点击主View收缩其他子View时
anim = ValueAnimator.ofObject(new AngleEvaluator(), endAngle, 0);
代码如下:
/**
* 按钮开关,控制按钮的显示与隐藏
* @param duration 执行动画时间
*/
private void toggleMenu(int duration) {
// 为menuItem添加旋转动画
int count = getChildCount();
for (int i = 0; i < count; i++){
if(i!=count/2){
final View childView = getChildAt(i);
childView.setVisibility(View.VISIBLE);
float startAngle = 0;
float endAngle = (float) (mAngle / (count - 1) * (i-count/2));
ValueAnimator anim = null;
// 若当前状态为关闭,则打开按钮 to open
if (mCurrentStatus == CLOSE){
anim = ValueAnimator.ofObject(new AngleEvaluator(), startAngle, endAngle);
} else {// 若当前状态为打开,则关闭按钮 to close
anim = ValueAnimator.ofObject(new AngleEvaluator(), endAngle, startAngle);
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
childView.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
}
//监听动画传回的结果,重新设置子View位置
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentAngle = (Float) animation.getAnimatedValue();
int cl = (int) (mRadius * Math.sin(currentAngle)) + getMeasuredWidth()/2 - childView.getMeasuredWidth()/2;
int ct = (int) (mRadius * Math.cos(currentAngle)) ;
int cWidth = childView.getMeasuredWidth();
int cHeight = childView.getMeasuredHeight();
childView.layout(cl, ct, cl + cWidth, ct + cHeight);
}
});
anim.setInterpolator(new AnticipateOvershootInterpolator());
anim.setDuration(duration);
anim.start();
}
}
// 切换菜单状态
changeStatus();
}
private void changeStatus() {
mCurrentStatus = (mCurrentStatus == CLOSE ? OPEN : CLOSE);
}
到此已经全部结束了,有哪些做的不对的地方,希望大家多多指点。
源码
![](https://img.haomeiwen.com/i6555017/0cd9ef73212dc8d3.jpg)
网友评论