26
2017
09

android自定义ViewGroup卫星导航菜单

卫星导航菜单看起来还是很酷的,实现起来也不是很难,网上也已经有很多这样的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,就完了,是不是很简单呀,粘贴复制就能出效果,如果跑不起来可以来打我。。。。

上一篇:多线程使用信号量sem_init,sem_wait,sem_post 下一篇:[RK3288][Android6.0] 调试笔记 --- Camera动态热插拔支持