备注说明:本文主要是笔者通过之前“卫星菜单实现”的学习,特意将实现的思路给理出来、记录下来,作为自己的学习总结。文中存在不对之处,请大家踊跃指出,转载请标明出处。
卫星菜单
实现思路:
1、自定义ViewGroup
(1)自定义属性:a、创建attr.xml文件 b、在布局中使用 c、在自定义控件中解析使用
(2)重写onMeasure与onLayout方法,设置菜单按钮的布局位置
2、定位Item(设置每一个菜单项的位置)
3、展开Item(动画效果实现,包括菜单按钮旋转动画、菜单项平移、旋转动画、菜单项缩放、透明度变换动画)
4、监听Item点击事件实现
效果展示:
一、自定义ViewGroup
(1)自定义属性:a、创建attr.xml文件 b、在布局中使用 c、在自定义控件中解析使用
a、创建attr.xml文件
<resources>
<attr name="position">
<enum name="left_top" value="0"/>
<enum name="left_bottom" value="1"/>
<enum name="center" value="2"/>
<enum name="right_top" value="3"/>
<enum name="right_bottom" value="4"/>
</attr>
<attr name="radius" format="dimension"></attr>
<declare-styleable name="ArcMenu">
<attr name="position"/>
<attr name="radius"/>
</declare-styleable>
</resources>
b、在布局中使用
注意:在ADT中引入自定义属性,创建命名空间是 xmlns:wusw=”http://schemas.android.com/apk/res/+应用程序包名;而在Androis Studio中则直接是: xmlns:wusw=”http://schemas.android.com/apk/res-auto” ,并不需要指明包名。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:wusw="http://schemas.android.com/apk/res/com.wusw.arcmenu"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.wusw.arcmenu.widget.ArcMenu
android:layout_width="wrap_content"
android:layout_height="wrap_content"
wusw:position="left_top"
wusw:radius="150dp">
</com.wusw.arcmenu.widget.ArcMenu>
</RelativeLayout>
c、在自定义控件中解析使用
TypedArray typedArray=context.getResources().obtainAttributes(attrs, R.styleable.ArcMenu);
mRadius=(int) typedArray.getDimension(R.styleable.ArcMenu_radius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics()));
int pos=typedArray.getInt(R.styleable.ArcMenu_position, 4);
switch (pos) {
case LEFT_TOP:
mPosition=Position.LEFT_TOP;
break;
case LEFT_BOTTOM:
mPosition=Position.LEFT_BOTTOM;
break;
case CENTER:
mPosition=Position.CENTER;
break;
case RIGHT_TOP:
mPosition=Position.RIGHT_TOP;
break;
case RIGHT_BOTTOM:
mPosition=Position.RIGHT_BOTTOM;
break;
}
typedArray.recycle();
(2)重写onMeasure与onLayout方法,设置菜单按钮的布局位置。
a、重写onMeasure()方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
for(int i=0;i<getChildCount();i++){
View child=getChildAt(i);
measureChild(child, widthMeasureSpec, widthMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
b、重写onLayout方法。
如图所示,放置不同位置时,left和top的位置情况。
@Override
protected void onLayout(boolean changed, int arg1, int arg2, int arg3, int arg4) {
// TODO Auto-generated method stub
if(changed){
layoutButton();//为菜单项布局
}
}
//为菜单按钮布局
private void layoutButton() {
// TODO Auto-generated method stub
if(getChildCount()>0){
mCButton=getChildAt(0);
int left=0;
int top=0;
int width=mCButton.getMeasuredWidth();
int height=mCButton.getMeasuredHeight();
switch (mPosition) {
case LEFT_TOP:
left=0;
top=0;
break;
case LEFT_BOTTOM:
left=0;
top=getMeasuredHeight()-height;
break;
case CENTER:
left=(getMeasuredWidth()-width)/2;
top=(getMeasuredHeight()-height)/2;
break;
case RIGHT_TOP:
left=getMeasuredWidth()-width;
top=0;
break;
case RIGHT_BOTTOM:
left=getMeasuredWidth()-width;
top=getMeasuredHeight()-height;
break;
}
mCButton.layout(left, top, left+width, top+height);
}
}
效果实现:
二、定位Item(设置每一个菜单项的位置)
如果所示,每个菜单项的位置与角度的关系。
//菜单项布局
private void layoutItem() {
// TODO Auto-generated method stub
int childCount=getChildCount()-1;
for(int i=0;i<childCount;i++){
View child=getChildAt(i+1);//第一个view为菜单按钮项
int left=0;
int top=0;
int width=child.getMeasuredWidth();
int height=child.getMeasuredHeight();
if(mPosition==Position.CENTER){
left=(int) (mRadius*Math.sin(Math.PI*2/(childCount)*i));
top=(int) (mRadius*Math.cos(Math.PI*2/(childCount)*i));
}else{
left=(int) (mRadius*Math.cos(Math.PI/2/(childCount-1)*i));
top=(int) (mRadius*Math.sin(Math.PI/2/(childCount-1)*i));
}
if(mPosition==Position.LEFT_BOTTOM||mPosition==Position.RIGHT_BOTTOM){
top=getMeasuredHeight()-top-height;
}
if(mPosition==Position.RIGHT_TOP||mPosition==Position.RIGHT_BOTTOM){
left=getMeasuredWidth()-left-width;
}
if(mPosition==Position.CENTER){
left=mButtonX+left;
top=mButtonY+top;
}
child.layout(left, top, left+width, top+height);
}
}
其中mButtonX,mButtonY是居中显示菜单按钮时,菜单按钮的中心的位置:
case CENTER:
left=(getMeasuredWidth()-width)/2;
top=(getMeasuredHeight()-height)/2;
mButtonX=left+width/10;
mButtonY=top+height/10;
break;
效果显示:
三、展开Item(动画效果实现,包括菜单按钮旋转动画、菜单项平移、旋转动画、菜单项缩放、透明度变换动画)
(1)菜单按钮旋转动画实现:
/**菜单按钮旋转*/
private void rotateButton(View v, float start, float end, int duration) {
RotateAnimation roteAnimation=new RotateAnimation(start,end, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);//自我为中心
roteAnimation.setDuration(duration);
roteAnimation.setFillAfter(true);
v.startAnimation(roteAnimation);
}
(2)菜单项平移:
final View child=getChildAt(i+1);
child.setVisibility(View.VISIBLE);//菜单项显示出来
int left=0;//平移时,起点的位置
int top=0;
if(mPosition==Position.CENTER){
left= (int) (mRadius*Math.cos(Math.PI*2 / (childCount) * i));
top= (int) (mRadius*Math.sin(Math.PI*2 / (childCount) * i));
}else{
left= (int) (mRadius*Math.sin(Math.PI/2/(childCount - 1) * i))-(mCButton.getMeasuredWidth()-child.getMeasuredWidth())/2;
top= (int) (mRadius*Math.cos(Math.PI / 2 / (childCount - 1) * i))-(mCButton.getMeasuredHeight()-child.getMeasuredHeight())/2;
}
int xFlag=1;//平移标识 1:正向 -1:负向
int yFlag=1;
if(mPosition==Position.LEFT_BOTTOM||mPosition==Position.LEFT_TOP){
xFlag=-1;
}
if(mPosition==Position.LEFT_TOP||mPosition==Position.RIGHT_TOP){
yFlag=-1;
}
int startX=left*xFlag;//平移开始位置
int startY=top*yFlag;
int endX=0;//平移结束位置
int endY=0;
//居中
if(mPosition==Position.CENTER){
startX=-left;
startY=-top;
endX=0;
endY=0;
}
//为item设置平移和旋转动画
AnimationSet animationSet=new AnimationSet(true);
TranslateAnimation transAnimation;//平移动画
if(mState==State.CLOSE){//需要打开时
transAnimation=new TranslateAnimation(startX,endX,startY,endY);
child.setClickable(true);
child.setFocusable(true);
}else{//需要关闭
transAnimation=new TranslateAnimation(endX,startX,endY,startY);
child.setClickable(false);
child.setFocusable(false);
(3)菜单项旋转动画+加平移动画:
//旋转动画
RotateAnimation roateAnimation=new RotateAnimation(0,720,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
roateAnimation.setDuration(duration);
roateAnimation.setFillAfter(true);
transAnimation.setDuration(duration);
transAnimation.setFillAfter(true);
animationSet.addAnimation(roateAnimation);//动画的点击顺序对实现的效果存在影响
animationSet.addAnimation(transAnimation);
animationSet.setStartOffset((i * 100) / childCount);//设置每个菜单项不同的时间
child.startAnimation(animationSet);
(4)菜单项透明度变换动画:
/**菜单项点击时,缩小隐藏,透明度变换动画*/
private AnimationSet childSmallAnimation(int duration,float scale) {
AnimationSet animationSet=new AnimationSet(true);
ScaleAnimation scaleAnimation=new ScaleAnimation(scale,0.0f,scale,0.0f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
AlphaAnimation alphaAnimation=new AlphaAnimation(1.0f,0.0f);
animationSet.setFillAfter(true);
animationSet.setDuration(duration);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(alphaAnimation);
return animationSet;
}
/**菜单项点击时,放大显示,透明度变换动画*/
private AnimationSet childBigAnimation(int duration,float scale) {
AnimationSet animationSet=new AnimationSet(true);
//缩放动画
ScaleAnimation scaleAnimation=new ScaleAnimation(1.0f,scale,1.0f,scale,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
AlphaAnimation alphaAnimation=new AlphaAnimation(1.0f,0.0f);
animationSet.setFillAfter(true);
animationSet.setDuration(duration);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(alphaAnimation);
return animationSet;
}
(5)菜单按钮自动放大缩小动画:
/**菜单按钮放大缩小动画*/
private void menuAnimation(final View v,int duration){
if(mMenuAnimation==null){
mMenuAnimation=childBigAnimation(duration, 2.0f);
mMenuAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
v.startAnimation(mMenuAnimation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
}
四、监听Item点击事件实现
(1)设置回调接口:
/**设置菜单回调接口*/
public void setOnMenuItemClickListner(onMenuItemClickListner mOnMenuItemClickListner) {
this.mOnMenuItemClickListner = mOnMenuItemClickListner;
}
/**菜单项回调接口*/
public interface onMenuItemClickListner{
public void onClick(View childView,int position);
}
(2)在菜单项点击时,调用回调接口方法。
final int poisition=i;//第几个菜单项
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(mOnMenuItemClickListner!=null){
mOnMenuItemClickListner.onClick(child,poisition);
}
childClickAnimation(poisition, duration);
changeMenuState();
startMenuAnimation(mCButton);
}
});