以圆心散开的半圆菜单

刚开始做Android的时候曾经遇到过一个这样的需求,半圆形菜单,从圆心处向外散开,收回。当时一看就蒙了,最后被牛人给实现了。昨天突然想起了这个需求,就想自己实现下,顺便也复习下Android知识。

效果如下:

以圆心散开的半圆菜单_第1张图片


这是一个自定义控件,首先,想要实现这个效果,需要了解View的绘制过程,即对onMeasure、onLayout、onDraw这个几个方法要有所了解。建议查看郭老大的帖子:

Android LayoutInflater原理分析,带你一步步深入了解View(一)

Android视图绘制流程完全解析,带你一步步深入了解View(二)

Android视图状态及重绘流程分析,带你一步步深入了解View(三)

Android自定义View的实现方法,带你一步步深入了解View(四)

草图:

就是根据下面的草图进行每个控件的位置计算的,以适配各类屏幕

以圆心散开的半圆菜单_第2张图片

主要代码:

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




    
	


源码下载地址


你可能感兴趣的:(自定义控件,Android,ANDROID,控件,自定义,半圆,菜单)