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,就完了,是不是很简单呀,粘贴复制就能出效果,如果跑不起来可以来打我。。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android自定义ViewGroup是指在Android开发中,通过继承ViewGroup类来创建自定义的布局容器。自定义ViewGroup可以用于实现一些特殊的布局效果,比如侧滑菜单、滑动卡片等等。通过自定义ViewGroup,我们可以更灵活地控制子视图的布局和交互行为,以满足特定的需求。自定义ViewGroup的实现主要包括重写onMeasure()方法和onLayout()方法,来测量和布局子视图。同时,我们还可以通过重写onInterceptTouchEvent()方法和onTouchEvent()方法来处理触摸事件,实现自定义的交互效果。如果你对自定义ViewGroup还不是很了解,或者正想学习如何自定义,可以参考相关的教程和文档,如引用\[1\]和引用\[2\]所提到的博客和官方文档。 #### 引用[.reference_title] - *1* [Android 手把手教您自定义ViewGroup(一)](https://blog.csdn.net/iteye_563/article/details/82601716)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [使用LayoutParams自定义安卓ViewGroup](https://blog.csdn.net/lfq88/article/details/127268493)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android自定义ViewGroup](https://blog.csdn.net/farsight2009/article/details/62046643)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值