卫星导航菜单看起来还是很酷的,实现起来也不是很难,网上也已经有很多这样的demo,而且封装的也特别好。作为一个技术gou,不能只是一味的用现成的轮子,向大神学习,自己造轮子。今天菜鸟也来尝试一下,从头到尾来实现一吧这高端的ui效果。。
老规矩,上图上代码:
效果还可以吧,也许有的人会认为有点low,但是我想说的是晚上关上灯效果是一样的,重点是内涵。。。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="radius" format="dimension" />
<attr name="CustomLayout">
<enum name="left_top" value="0" />
<enum name="right_top" value="1" />
<enum name="right_bottom" value="2" />
<enum name="left_bottom" value="3" />
<enum name="bottom_center" value="4" />
</attr>
<attr name="MenuStats">
<enum name="open" value="0" />
<enum name="close" value="1" />
</attr>
<declare-styleable name="PlanetViewGroup">
<attr name="radius" />
<attr name="CustomLayout" />
<attr name="MenuStats" />
</declare-styleable>
</resources>
用到了enum,主要就是指定用户选择的范围,不让用户乱搞。。。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.apple.Custom.PlanetViewGroup
android:id="@+id/planet_top"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:CustomLayout="bottom_center"
app:MenuStats="open"
app:radius="130dp">
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_red_light"
android:src="@mipmap/ic_launcher"
android:text="1" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_blue_dark"
android:src="@mipmap/ic_launcher"
android:text="2" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_green_light"
android:src="@mipmap/ic_launcher"
android:text="3" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_orange_dark"
android:src="@mipmap/ic_launcher"
android:text="4" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_blue_light"
android:src="@mipmap/ic_launcher"
android:text="5" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_orange_dark"
android:src="@mipmap/ic_launcher"
android:text="6" />
<TextView
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@android:color/holo_red_dark"
android:src="@mipmap/ic_launcher"
android:text="C" />
</com.example.apple.Custom.PlanetViewGroup>
<com.example.apple.Custom.PlanetViewGroup
android:id="@+id/planet_bottem"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:CustomLayout="left_top"
app:MenuStats="open"
app:radius="130dp">
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_red_light"
android:src="@mipmap/ic_launcher"
android:text="1" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_blue_dark"
android:src="@mipmap/ic_launcher"
android:text="2" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_green_light"
android:src="@mipmap/ic_launcher"
android:text="3" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_orange_dark"
android:src="@mipmap/ic_launcher"
android:text="4" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_blue_light"
android:src="@mipmap/ic_launcher"
android:text="5" />
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@android:color/holo_orange_dark"
android:src="@mipmap/ic_launcher"
android:text="6" />
<TextView
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@android:color/holo_red_dark"
android:src="@mipmap/ic_launcher"
android:text="C" />
</com.example.apple.Custom.PlanetViewGroup>
</RelativeLayout>
使用起来也是比较简单的,可以在布局里面指定菜单的位置,一般就四个角,和底部的中间位置,这里都是支持的 。还可以指定,子菜单是关闭还是显示,open close.
package com.example.apple.Custom;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.OvershootInterpolator;
import com.example.apple.pullzoom.R;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
/**
* Created by apple on 17/9/21.
*/
public class PlanetViewGroup extends ViewGroup implements View.OnClickListener {
final String TAG = this.getClass().getSimpleName();
CustomLayout mlayout = CustomLayout.left_top;
MenuStats menuStats = MenuStats.open;
private int count = 0;
View centerView;
MenuOnClickListener lis;
int cl, ct, cr, cb, childWidth, childHeight;
/**
* 半径
*/
private int radius = 100;
private double angle;
public enum CustomLayout {
left_top, right_top, right_bottom, left_bottom, bottom_center
}
public enum MenuStats {
open, close;
}
int measuredHeight, measuredWidth;
public PlanetViewGroup(Context context) {
this(context, null);
}
public PlanetViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PlanetViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PlanetViewGroup);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int index = typedArray.getIndex(i);
switch (index) {
case R.styleable.PlanetViewGroup_radius:
radius = typedArray.getDimensionPixelSize(index, 50);
break;
case R.styleable.PlanetViewGroup_MenuStats:
int anInt = typedArray.getInt(index, 0);
switch (anInt) {
case 0:
menuStats = MenuStats.open;
break;
case 1:
menuStats = MenuStats.close;
break;
}
break;
case R.styleable.PlanetViewGroup_CustomLayout:
anInt = typedArray.getInt(index, 0);
switch (anInt) {
case 0:
mlayout = CustomLayout.left_top;
break;
case 1:
mlayout = CustomLayout.right_top;
break;
case 2:
mlayout = CustomLayout.right_bottom;
break;
case 3:
mlayout = CustomLayout.left_bottom;
break;
case 4:
mlayout = CustomLayout.bottom_center;
break;
}
break;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
measuredHeight = getMeasuredHeight();
measuredWidth = getMeasuredWidth();
angle = Math.PI / (2 * (count - 2));
radius = Math.min(radius, measuredWidth / 2);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed) return;
if (count < 3) return;//里面控件太少,菜单显示没有意思
//最后一个view作为主控件view
centerView = getChildAt(count - 1);
controllerView();
subViewLayout(false);
}
/**
* 计算子view坐标
*
* @param showAnim
*/
private void subViewLayout(boolean showAnim) {
View child;
for (int i = 0; i < count - 1; i++) {
child = getChildAt(i);
if (!showAnim) {
if (menuStats == MenuStats.close) {
child.setVisibility(GONE);
} else {
child.setVisibility(VISIBLE);
}
}
final int index = i + 1;
if (lis != null) {
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (lis != null) {
lis.menuItem(index);
}
menuStats = MenuStats.close;
viewVisibleStats(GONE);
alphaAndScale(getChildAt(index - 1));
}
});
}
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
if (mlayout == CustomLayout.left_top) {
cl = (int) (radius * Math.sin((i) * angle));
ct = (int) (radius * Math.cos((i) * angle));
} else if (mlayout == CustomLayout.right_top) {
cl = measuredWidth - childWidth - (int) (radius * Math.sin((i) * angle));
ct = (int) (radius * Math.cos((i) * angle));
} else if (mlayout == CustomLayout.right_bottom) {
cl = measuredWidth - childWidth - (int) (radius * Math.sin((i) * angle));
ct = measuredHeight - childHeight - (int) (radius * Math.cos((i) * angle));
} else if (mlayout == CustomLayout.left_bottom) {
cl = (int) (radius * Math.sin((i) * angle));
ct = measuredHeight - childHeight - (int) (radius * Math.cos((i) * angle));
} else if (mlayout == CustomLayout.bottom_center) {
cl = (measuredWidth - childWidth) / 2 + (int) (radius * Math.cos((i) * (Math.PI / (count - 2))));
ct = measuredHeight - childHeight - (int) (radius * Math.sin((i) * (Math.PI / (count - 2))));
}
if (showAnim) {
if (menuStats == MenuStats.close) {
hidePlanetMenu(child, index);
} else {
showPlanetMenu(child, index);
}
}
cr = cl + childWidth;
cb = ct + childHeight;
child.layout(cl, ct, cr, cb);
}
}
/**
* 对view缩放和透明度变化
*
* @param child
*/
private void alphaAndScale(final View child) {
child.setVisibility(VISIBLE);
ObjectAnimator alpha = ObjectAnimator.ofFloat(child, "alpha", 1, 0);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(child, "scaleX", 1, 2);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(child, "scaleY", 1, 2);
AnimatorSet set = new AnimatorSet();
set.setDuration(500);
set.play(alpha).with(scaleX).with(scaleY);
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
child.setScaleX(1);
child.setScaleY(1);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
set.start();
}
/***
* 子菜单动画
*/
private void showPlanetMenu(View child, int index) {
ObjectAnimator translationY = ObjectAnimator.ofFloat(child, "y", centerView.getTop(), ct);
ObjectAnimator translationX = ObjectAnimator.ofFloat(child, "x", centerView.getLeft(), cl);
ObjectAnimator rotation = ObjectAnimator.ofFloat(child, "rotation", 0, 360);
translationY.setInterpolator(new OvershootInterpolator());
translationX.setInterpolator(new OvershootInterpolator());
AnimatorSet set = new AnimatorSet();
set.playTogether(translationY, translationX, rotation);
set.setDuration(300 + index * 100);
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
viewVisibleStats(VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
set.start();
}
/***
* 子菜单动画
*/
private void hidePlanetMenu(View child, int index) {
ObjectAnimator translationY = ObjectAnimator.ofFloat(child, "y", ct, centerView.getTop());
ObjectAnimator translationX = ObjectAnimator.ofFloat(child, "x", cl, centerView.getLeft());
ObjectAnimator rotation = ObjectAnimator.ofFloat(child, "rotation", 0, 360);
AnimatorSet set = new AnimatorSet();
set.setDuration(300 + index * 100);
set.setInterpolator(new OvershootInterpolator());
set.playTogether(translationY, translationX, rotation);
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
viewVisibleStats(GONE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
set.start();
}
/**
* 主菜单控制按钮
*/
private void controllerView() {
MarginLayoutParams marginLayoutParams = null;
LayoutParams layoutParams = centerView.getLayoutParams();
if(layoutParams instanceof MarginLayoutParams){
marginLayoutParams = (MarginLayoutParams)layoutParams;
}
childWidth = centerView.getMeasuredWidth();
childHeight = centerView.getMeasuredHeight();
if (mlayout == CustomLayout.left_top) {
cl = 0;
ct = 0;
cr = childWidth;
cb = childHeight;
} else if (mlayout == CustomLayout.right_top) {
cl = measuredWidth - childWidth;
ct = 0;
cr = measuredWidth;
cb = childHeight;
} else if (mlayout == CustomLayout.right_bottom) {
cl = measuredWidth - childWidth;
ct = measuredHeight - childHeight;
cr = measuredWidth;
cb = measuredHeight;
} else if (mlayout == CustomLayout.left_bottom) {
cl = 0;
ct = measuredHeight - childHeight;
cr = childWidth;
cb = measuredHeight;
} else if (mlayout == CustomLayout.bottom_center) {
cl = (measuredWidth - childWidth) / 2;
ct = measuredHeight - childHeight;
cr = cl + childWidth;
cb = measuredHeight;
}
if(marginLayoutParams == null){
centerView.layout(cl, ct , cr , cb );
}else{
centerView.layout(cl + marginLayoutParams.leftMargin, ct + marginLayoutParams.topMargin, cr + marginLayoutParams.rightMargin, cb + marginLayoutParams.bottomMargin);
}
centerView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
ObjectAnimator rotation = ObjectAnimator.ofFloat(v, "rotation", 0, 360).setDuration(500);
rotation.setDuration(500);
rotation.start();
if (menuStats == MenuStats.open) {
menuStats = MenuStats.close;
subViewLayout(true);
} else {
menuStats = MenuStats.open;
subViewLayout(true);
}
}
/**
* 子view是否可见
*
* @param visible
*/
private void viewVisibleStats(int visible) {
for (int i = 0; i < count - 1; i++) {
getChildAt(i).setVisibility(visible);
getChildAt(i).setAlpha(1);
}
}
public void setMenuOnClickListener(MenuOnClickListener lis) {
this.lis = lis;
}
public interface MenuOnClickListener {
void menuItem(int pos);
}
}
全部实现代码就一个类,而且也不多,说明还是简单呀。坐标计算采用的是三角函数,计算子view的x,y。动画用的属性动画,大伙儿都知道属性动画最好用,因为它可以直接修改view的属性,影响最深远的就是他的点击事件,会随着view的位置而改变。这里MarginLayoutParams进行了处理,之view要使用MarginLayoutParams,就必须在父控件里面重写generateLayoutParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
不然会报错。。
((PlanetViewGroup) findViewById(R.id.planet_bottem)).setMenuOnClickListener(new PlanetViewGroup.MenuOnClickListener() {
@Override
public void menuItem(int pos) {
Toast.makeText(getApplication(), "click" + pos, Toast.LENGTH_SHORT).show();
}
});
actvivity粘贴上面这段,ok,就完了,是不是很简单呀,粘贴复制就能出效果,如果跑不起来可以来打我。。。。