刚开始做Android的时候曾经遇到过一个这样的需求,半圆形菜单,从圆心处向外散开,收回。当时一看就蒙了,最后被牛人给实现了。昨天突然想起了这个需求,就想自己实现下,顺便也复习下Android知识。
效果如下:
这是一个自定义控件,首先,想要实现这个效果,需要了解View的绘制过程,即对onMeasure、onLayout、onDraw这个几个方法要有所了解。建议查看郭老大的帖子:
Android LayoutInflater原理分析,带你一步步深入了解View(一)
Android视图绘制流程完全解析,带你一步步深入了解View(二)
Android视图状态及重绘流程分析,带你一步步深入了解View(三)
Android自定义View的实现方法,带你一步步深入了解View(四)
草图:
就是根据下面的草图进行每个控件的位置计算的,以适配各类屏幕
主要代码:
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.OvershootInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.TextView;
public class MenuView extends ViewGroup {
/**paddingLeft paddingRight*/
private static final int PADDING_L_R = 10;
/**paddingTop paddingBottom*/
private static final int PADDING_T_B = 10;
/**动画时长*/
private static int DURATION = 500;
/**小圆半径*/
private final int SMALL_RADIUS;
/**是否正在进行动画*/
private boolean isAnimating;
/**当前正在进行动画的小圆序号*/
private int currentIndex;
/**动画开始位置*/
private int startX,startY;
/**菜单是否已经显示出来*/
private boolean isShown;
private TextView childOne;
private TextView childTwo;
private TextView childThree;
private TextView childFour;
private TextView childFive;
private TextView childSix;
private TextView childSeven;
private ArrayList views = new ArrayList();
public MenuView(Context context) {
super(context);
SMALL_RADIUS = (int) context.getResources().getDimension(R.dimen.radius);
init(context);
}
public MenuView(Context context, AttributeSet attrs) {
super(context, attrs);
SMALL_RADIUS = (int) context.getResources().getDimension(R.dimen.radius);
init(context);
}
public MenuView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
SMALL_RADIUS = (int) context.getResources().getDimension(R.dimen.radius);
init(context);
}
private void init(Context context) {
childOne = new TextView(context);
childTwo = new TextView(context);
childThree = new TextView(context);
childFour = new TextView(context);
childFive = new TextView(context);
childSix = new TextView(context);
childSeven = new TextView(context);
childOne.setBackgroundResource(R.drawable.circle_bg);
childTwo.setBackgroundResource(R.drawable.circle_bg);
childThree.setBackgroundResource(R.drawable.circle_bg);
childFour.setBackgroundResource(R.drawable.circle_bg);
childFive.setBackgroundResource(R.drawable.circle_bg);
childSix.setBackgroundResource(R.drawable.circle_bg);
childSeven.setBackgroundResource(R.drawable.circle_bg);
childOne.setText("1");
childTwo.setText("2");
childThree.setText("3");
childFour.setText("4");
childFive.setText("5");
childSix.setText("6");
childSeven.setText("7");
childOne.setGravity(Gravity.CENTER);
childTwo.setGravity(Gravity.CENTER);
childThree.setGravity(Gravity.CENTER);
childFour.setGravity(Gravity.CENTER);
childFive.setGravity(Gravity.CENTER);
childSix.setGravity(Gravity.CENTER);
childSeven.setGravity(Gravity.CENTER);
addView(childOne);
addView(childTwo);
addView(childThree);
addView(childFour);
addView(childFive);
addView(childSix);
addView(childSeven);
views.add(childOne);
views.add(childTwo);
views.add(childThree);
views.add(childFour);
views.add(childFive);
views.add(childSix);
views.add(childSeven);
childOne.setVisibility(View.INVISIBLE);
childTwo.setVisibility(View.INVISIBLE);
childThree.setVisibility(View.INVISIBLE);
childFour.setVisibility(View.INVISIBLE);
childFive.setVisibility(View.INVISIBLE);
childSix.setVisibility(View.INVISIBLE);
childSeven.setVisibility(View.INVISIBLE);
}
/**
* 菜单散开
*/
public void out(){
if(isAnimating)
return;
isAnimating = true;
int length = views.size();
for (int i = 0; i < length; i++) {
final View v = views.get(i);
TranslateAnimation animation = new TranslateAnimation(startX - v.getLeft(), 0, startY - v.getTop(), 0);
animation.setInterpolator(new OvershootInterpolator(1.5f));
animation.setDuration(DURATION);
v.startAnimation(animation);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
currentIndex++;
v.setVisibility(View.VISIBLE);
if(currentIndex == views.size()){
isAnimating = false;
currentIndex = 0;
isShown = true;
}
}
});
}
}
/**
* 菜单收起
*/
public void in(){
if(isAnimating){
return;
}
isAnimating = true;
int length = views.size();
for (int i = 0; i < length; i++) {
final View v = views.get(i);
TranslateAnimation animation = new TranslateAnimation( 0,startX - v.getLeft() - v.getWidth()/2, 0,startY - v.getTop() + v.getHeight()/2);
animation.setInterpolator(new OvershootInterpolator(1.2f));
if (i >= length/2 ) {
animation.setDuration(DURATION-100);
} else {
animation.setDuration(DURATION);
}
v.startAnimation(animation);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
currentIndex++;
v.setVisibility(View.INVISIBLE);
if(currentIndex == views.size()){
isAnimating = false;
currentIndex = 0;
isShown = false;
}
}
});
}
}
public boolean isAnimating(){
return isAnimating;
}
public boolean isShown(){
return isShown;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
if (childCount == 0)
return;
//逐个测量小圆的大小
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child != null) {
//设置小圆的大小宽高都为 SMALL_RADIUS
child.measure(
MeasureSpec.makeMeasureSpec(SMALL_RADIUS,
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec( SMALL_RADIUS,
MeasureSpec.EXACTLY));
}
}
int screenWidth = getResources().getDisplayMetrics().widthPixels;
//计算控件的高度h,由草图可以计算出为R+2r
int r = getChildAt(0).getMeasuredWidth();
int R = screenWidth/2-r;
int h = R + r*2 + PADDING_T_B*2;
setMeasuredDimension(screenWidth, h);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if (childCount == 0||getChildAt(0)==null)
return;
int width = getMeasuredWidth() - PADDING_L_R*2;
int height = getMeasuredHeight() - PADDING_T_B*2;
View childView = getChildAt(0);
//大半圆半径
int radiusBig = (width-childView.getMeasuredWidth())/2;
//小圆半径
int radiusSmall = childView.getMeasuredWidth()/2;
//计算每个小圆的圆心,并布局位置
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child == null)
break;
if(i==0){
int child_x = radiusSmall + PADDING_L_R;
int child_y = height - radiusSmall;
layout(child, child_x, child_y, radiusSmall);
}else if(i==1){
int child_x = (int) (width/2 - radiusBig*Math.cos(Math.PI/6)) + PADDING_L_R;
int child_y = (int) (height-radiusSmall-radiusBig*Math.sin(Math.PI/6));
layout(child, child_x, child_y, radiusSmall);
}else if(i==2){
int child_x = (int) (width/2 - radiusBig*Math.cos(Math.PI/3)) + PADDING_L_R;
int child_y = (int) (height-radiusSmall-radiusBig*Math.sin(Math.PI/3));
layout(child, child_x, child_y, radiusSmall);
}else if(i==3){
int child_x = width/2 + PADDING_L_R;
int child_y = height - radiusSmall - radiusBig;
layout(child, child_x, child_y, radiusSmall);
}else if(i==4){
int child_x = (int) (width/2+radiusBig*Math.cos(Math.PI/3)) + PADDING_L_R;
int child_y = (int) (height - radiusSmall - radiusBig*Math.sin(Math.PI/3));
layout(child, child_x, child_y, radiusSmall);
}else if(i==5){
int child_x = (int) (width/2+radiusBig*Math.cos(Math.PI/6)) + PADDING_L_R;
int child_y = (int) (height - radiusSmall - radiusBig*Math.sin(Math.PI/6));
layout(child, child_x, child_y, radiusSmall);
}else if(i==6){
int child_x = width - radiusSmall + PADDING_L_R;
int child_y = height - radiusSmall;
layout(child, child_x, child_y, radiusSmall);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
startX = getWidth()/2;
startY = getHeight() - childOne.getWidth()/2 - PADDING_T_B;
}
/**
* 确定小圆的位置
* @param view 小圆
* @param child_x 圆心 x坐标
* @param child_y 圆心y坐标
* @param radiusSmall 小圆半径
*/
private void layout(View view, int child_x, int child_y,int radiusSmall) {
int child_l = getChildOffset_l(child_x, child_y, radiusSmall);
int child_t = getChildOffset_t(child_x, child_y, radiusSmall);
int child_r = getChildOffset_r(child_x, child_y, radiusSmall);
int child_b = getChildOffset_b(child_x, child_y, radiusSmall);
view.layout(child_l, child_t, child_r, child_b);
}
/**
* 根据圆心坐标,半径获取小圆View的left
* @param child_x
* @param child_y
* @param radius
* @return 小圆相对于父控件的left
*/
private int getChildOffset_l(int child_x,int child_y,int radius){
return child_x - radius;
}
/**
* 根据圆心坐标,半径获取小圆View的top
* @param child_x
* @param child_y
* @param radius
* @return 小圆相对于父控件的top
*/
private int getChildOffset_t(int child_x,int child_y,int radius){
return child_y - radius;
}
/**
* 根据圆心坐标,半径获取小圆View的right
* @param child_x
* @param child_y
* @param radius
* @return 小圆相对于父控件的right
*/
private int getChildOffset_r(int child_x,int child_y,int radius){
return child_x + radius;
}
/**
* 根据圆心坐标,半径获取小圆View的bottom
* @param child_x
* @param child_y
* @param radius
* @return 小圆相对于父控件的bottom
*/
private int getChildOffset_b(int child_x,int child_y,int radius){
return child_y + radius;
}
}
circle_bg.xml
源码下载地址