卫星菜单

来源:互联网 发布:手机知乎怎么回答问题 编辑:程序博客网 时间:2024/04/29 11:18

转载请注明出处:http://blog.csdn.net/forwardyzk/article/details/44306855

下面就介绍一种菜单,卫星菜单(旋转菜单).按照下面的步骤来介绍其实现的方法。

卫星菜单
1.自定义类继承ViewGroup
2.自定义属性(在res/value/attr.xml中添加属性位置和半径 )
3.在View中获取这些自定义的属性
4.onMeasure测量子孩子
5.设置菜单主按钮的位置
6.设置子孩子的位置
7.给Item设置平移动画和旋转动画
8.点击Item设置点击事件并且设置缩放动画
9.测试使用

步骤1

   -自定义类(MoonMenu)继承ViewGroup

public class MoonMenu extends ViewGroup {
添加两个构造方法,其中一个必须含有设置属性的构造方法

public MoonMenu(Context context) {        this(context, null);    }    public MoonMenu(Context context, AttributeSet attrs) {        super(context, attrs);


步骤2

    --自定义属性(在res/value/attr.xml中添加属性位置和半径 )

定义属性,显示位置,使用枚举,有四个位置供选择,左上,左下,右上,右下。

半径大小,是一个旋转的弧形,设置一个半径的大小

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="MoonMenu">        <attr name="position">            <enum name="LEFT_TOP" value="0"></enum>            <enum name="LEFT_BOTTOM" value="1"></enum>            <enum name="RIGHT_TOP" value="2"></enum>            <enum name="RIGHT_BOTTOM" value="3"></enum>        </attr>        <attr name="radius" format="dimension"></attr>    </declare-styleable></resources>

步骤3.

   --在View中获取这些自定义的属性

在调用出,要添加一个命名空间,xmlns:moon="http://schemas.android.com/apk/res-auto"

 <com.example.menu.view.MoonMenu        android:layout_width="wrap_content"        android:layout_height="wrap_content"        moon:position="LEFT_TOP"        moon:radius="130dp">    </com.example.menu.view.MoonMenu>

在构造方法中获取设置的自定义的位置和半径属性

定义一个枚举位置

enum Position {        LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM    }


public MoonMenu(Context context, AttributeSet attrs) {        super(context, attrs);        mContext = context;        //获取自定义的属性        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MoonMenu);        int p = typedArray.getInt(R.styleable.MoonMenu_position, 3);        if (p == 0) {            position = Position.LEFT_TOP;        } else if (p == 1) {            position = Position.LEFT_BOTTOM;        } else if (p == 2) {            position = Position.RIGHT_TOP;        } else if (p == 3) {            position = Position.RIGHT_BOTTOM;        }        radius = (int) typedArray.getDimension(R.styleable.MoonMenu_radius, 100);    }
半径默认为100dp,位置默认右下角


步骤4

  --测量子孩子的长和宽,供在onLayout方法中显示

  菜单的主按钮和菜单Item都作为其MoonMenu菜单子孩子显示

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int count = getChildCount();        for (int i = 0; i < count; i++) {            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);        }        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }

步骤5

  --设置菜单主按钮的位置

 现在控件中添加菜单主按钮到MoonMenu,作为第一个子孩子

<com.example.menu.view.MoonMenu        android:layout_width="match_parent"        android:layout_height="match_parent"        moon:position="LEFT_TOP"        moon:radius="130dp">        <!--菜单主按钮-->        <RelativeLayout            android:layout_width="wrap_content"            android:layout_height="wrap_content">            <ImageView                android:layout_centerInParent="true"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:src="@drawable/main" />        </RelativeLayout>    </com.example.menu.view.MoonMenu>


/**     * 设置菜单的主按钮     */    private void layoutMainView() {        mainView = getChildAt(0);        //设置菜单主按钮的点击事件        mainView.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                switchStaOffOn(v);            }        });        int sWidth = getMeasuredWidth();        int sHeight = getMeasuredHeight();        int l = 0;        int t = 0;        int width = mainView.getMeasuredWidth();        int height = mainView.getMeasuredHeight();        switch (position) {            case LEFT_TOP:                mainView.layout(l, t, width, height);                break;            case LEFT_BOTTOM:                mainView.layout(l, sHeight - height, width, sHeight);                break;            case RIGHT_TOP:                mainView.layout(sWidth - width, t, sWidth, height);                break;            case RIGHT_BOTTOM:                mainView.layout(sWidth - width, sHeight - height, sWidth, sHeight);                break;        }    }

通过获取菜单的长和宽,子孩子的长和宽,确定View显示的坐标,使用layout显示的界面上。


以左上角的位置作为例子。

l=0:childView左边相对于父View的坐标

t=0:childView顶部边相对于父View的坐标

r=childView的宽度:childView右边相对于父View的坐标

b=childView的高度:childView底部边相对于父View的坐标

 mainView.layout(l, t, r, b);


步骤6

     ---设置子孩子MenuItem的位置

先一下一张图


MenuItem可以有半径(R),角度(θ)确定两个距离,这样在获取childView的宽和高,Menu的长和宽,就可以确定MenItem相对父View的坐标

R:是设置Menu时,传进来的。

θ:由MenuItem 的个数确定,例如有三个MenuItem,单位的角度就是:α=Math.PI / 2 / (3 - 2),为什么-2呢,第一去掉主按钮,第二去掉MenuItem-1.那三个MenuItem的计算的角度分别为:0*α,1*α,2*α


那么设置显示位置也是用layout(l,t,r,b)

/**     * 设置子菜单Item位置     */    private void layoutChildsItem() {        int childCount = getChildCount();        double angleUnit = Math.PI / 2 / (childCount - 2);        for (int i = 0; i < childCount - 1; i++) {            View childView = getChildAt(i + 1);            final int clickPosition = i + 1;            //默认的是显示            int xl = (int) (radius * Math.sin(angleUnit * i));            int yl = (int) (radius * Math.cos(angleUnit * i));            int childHeight = childView.getMeasuredHeight();            int childWidth = childView.getMeasuredWidth();            int screenWidth = getMeasuredWidth();            int screenHeight = getMeasuredHeight();            switch (position) {                case LEFT_TOP:                    childView.layout(xl, yl, xl + childWidth, yl + childHeight);                    break;                case LEFT_BOTTOM:                    childView.layout(xl, screenHeight - yl - childHeight, xl + childWidth, screenHeight - yl);                    break;                case RIGHT_TOP:                    childView.layout(screenWidth - xl - childWidth, yl, screenWidth - xl, yl + childHeight);                    break;                case RIGHT_BOTTOM:                    childView.layout(screenWidth - xl - childWidth, screenHeight - yl - childHeight, screenWidth - xl, screenHeight - yl);                    break;            }            childView.setOnClickListener(new OnClickListener() {                @Override                public void onClick(View v) {                    if (onMenuItemClickListener != null) {                        onMenuItemClickListener.onClickMenuItem(v, clickPosition);                    }                    onMenuItemClickAnimation(v);                }            });        }    }

步骤7

  ---给MainMenuItem设置点击事件和给Item设置平移动画和旋转动画

     主菜单按钮旋转动画,其他的ChildItem平移动画和旋转动画

     切换菜单的开关状态


在layoutMainView()方法中,给第一个ChildItem(菜单主按钮)设置点击事件

mainView.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                switchStaOffOn(v);            }        });

 /**     * 主菜单点击事件执行的操作     *     * @param mainView     */    private void switchStaOffOn(View mainView) {        mainAnim(mainView, DURATION);        childItemAnim(DURATION);        changeStatus();    }

/**     * 菜单主按钮设置旋转动画     *     * @param mainView     */    private void mainAnim(View mainView, int duration) {        RotateAnimation roateAn = new RotateAnimation(0, 360, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);        roateAn.setFillAfter(true);        roateAn.setDuration(duration);        mainView.setAnimation(roateAn);        mainView.startAnimation(roateAn);    }
当点击了主按钮时,主菜单按钮旋转360度。


给其他的ChildItem要设置平移动画和旋转动画

其坐标都是相对与父View作为参考物

当菜单展开时:

对于LEFT_BOTTOM,LEFT_TOP位置的菜单,当平移的时候,向左移动,X坐标变小(与移动前X坐标比较)

对于RIGHT_TOP,LEFT_TOP位置的菜单,  当平移的时候,向上移动,Y坐标变小(与移动前Y坐标比较)

为X坐标和Y坐标大小变化增加标记,xFlag和yFlag,默认为1,表示变大,-1表示变小

当菜单打开的时候,X和Y坐标都是(0,0)--->>(xFlag * xl,yFlag * yl)

当菜单关闭的时候,X和Y的坐标是xFlag * xl,yFlag * yl)-->>0,0)--->>

给Item增加旋转动画

相对于自己中心位置旋转,0-->720,旋转两圈


ChildMenuItem设置了动画的开始执行的间隔时间,这样的效果更好,感觉就是按顺序展开是关闭。setStartOffset(开始延迟时间执行)

/**     * 给Item设置展开与收回的动画     *     * @param duration     */    private void childItemAnim(long duration) {        int xFlag = 1;        int yFlag = 1;        if (position == Position.LEFT_BOTTOM || position == Position.LEFT_TOP) {            xFlag = -1;        }        if (position == Position.RIGHT_TOP || position == Position.LEFT_TOP) {            yFlag = -1;        }        int childCount = getChildCount();        double angleUnit = Math.PI / 2 / (childCount - 2);        for (int i = 0; i < childCount - 1; i++) {            final View childView = getChildAt(i + 1);            childView.setVisibility(View.VISIBLE);            int xl = (int) (radius * Math.sin(angleUnit * i));            int yl = (int) (radius * Math.cos(angleUnit * i));            AnimationSet anSet = new AnimationSet(true);            //平移动画            TranslateAnimation translateAnim = null;            //旋转动画            RotateAnimation rotateAni = null;            if (switchStatus == Status.OPEN) {                translateAnim = new TranslateAnimation(0, xFlag * xl, 0, yFlag * yl);                childView.setClickable(false);                childView.setFocusable(false);            } else if (switchStatus == Status.CLOSE) {                translateAnim = new TranslateAnimation(xFlag * xl, 0, yFlag * yl, 0);                childView.setClickable(true);                childView.setFocusable(true);            }            translateAnim.setFillAfter(true);            translateAnim.setDuration(duration);            rotateAni = new RotateAnimation(0, 720, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);            rotateAni.setFillAfter(true);            rotateAni.setDuration(duration);            //添加动画到集合            translateAnim.setAnimationListener(new Animation.AnimationListener() {                @Override                public void onAnimationStart(Animation animation) {                }                @Override                public void onAnimationEnd(Animation animation) {                    if (switchStatus == Status.CLOSE) {                        childView.setVisibility(View.GONE);                    }                }                @Override                public void onAnimationRepeat(Animation animation) {                }            });            anSet.addAnimation(rotateAni);            anSet.addAnimation(translateAnim);            anSet.setStartOffset(i * 100 / childCount);            childView.startAnimation(anSet);        }    }


默认所有的ChildMenuIem都显示出来 childView.setVisibility(View.VISIBLE);
当动画结束的时候,如果此时菜单的状态为关闭状态,那么ChildMenuItem,设置为不可显示,

最后改变菜单开和关状态

/**     * 改变开关的状态     */    private void changeStatus() {        switchStatus = (switchStatus == Status.OPEN ? Status.CLOSE : Status.OPEN);    }



/**     * 判断当前的Menu是否是开的状态     *     * @return     */    public boolean isOpen() {        return switchStatus == Status.OPEN;    }    /**     * Menu从开---关的ChildMenuItem动画效果,并且要改变Menu的状态     */    public void toClose() {        childItemAnim(DURATION);        changeStatus();    }


步骤8

  ---点击Item设置点击事件并且设置缩放动画

   当点击Item的时候,点击的ChildMenuItem扩大,其他的ChildMenuItem缩小,同时透明度变小

在layoutChildsItem()中给ChildMenuItem设置点击事件

childView.setOnClickListener(new OnClickListener() {                @Override                public void onClick(View v) {                    if (onMenuItemClickListener != null) {                        onMenuItemClickListener.onClickMenuItem(v, clickPosition);                    }                    onMenuItemClickAnimation(v);                }            });

/**     * 点击菜单Item的动画,点击的Item放大,其他的缩小,并且透明度变低     *     * @param v     */    private void onMenuItemClickAnimation(View v) {        int childCount = getChildCount();        for (int i = 0; i < childCount - 1; i++) {            View childView = getChildAt(i + 1);            if (childView == v) {                childView.startAnimation(menuItemBigAnimation());            } else {                childView.startAnimation(menuItemSmallAnimation());            }            childView.setClickable(false);            childView.setFocusable(false);            if (isOpen()) {                changeStatus();            }        }    }


 /**     * 点击Item后,Item变大和透明度变大的动画     *     * @return     */    private Animation menuItemBigAnimation() {        AlphaAnimation alphaAnima = new AlphaAnimation(1.0f, 0.0f);        //点击的item动画        ScaleAnimation scaleAnimaClick = new ScaleAnimation(1.0f, 2.0f, 1.0f, 2.0f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);        AnimationSet clickSet = new AnimationSet(true);        clickSet.addAnimation(scaleAnimaClick);        clickSet.addAnimation(alphaAnima);        clickSet.setFillAfter(true);        clickSet.setDuration(300);        return clickSet;    }    /**     * 点击Item后,Item变小和透明度变小的动画     *     * @return     */    private Animation menuItemSmallAnimation() {        AlphaAnimation alphaAnima = new AlphaAnimation(1.0f, 0.0f);        alphaAnima.setDuration(300);        ScaleAnimation scaleAnima = new ScaleAnimation(1.0f, 0.0f, 0.0f, 0.0f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);        scaleAnima.setDuration(300);        AnimationSet set = new AnimationSet(true);        set.addAnimation(scaleAnima);        set.addAnimation(alphaAnima);        set.setFillAfter(true);        set.setDuration(300);        return set;    }

定义点击事件的回调接口,

 /**     * 设置点击Item的事件监听     *     * @param onMenuItemClickListener     */    public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {        this.onMenuItemClickListener = onMenuItemClickListener;    }    /**     * 点击Item的监听事件     */    public interface OnMenuItemClickListener {        void onClickMenuItem(View itemView, int position);    }    private OnMenuItemClickListener onMenuItemClickListener;

当然对于动画效果,是写在了当前的空间中,不需要在回调接口中使用,当点击了ChildMenuItem后,执行的操作在回调接口中操作。

参数:点击的当前的ChildMenuView和当前ChildMenuView位置。


步骤9

   ---测试使用


在一个Activity中,有一个菜单和ListView展示,默认的都展示出来

test_activity.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:moon="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity">    <ListView        android:id="@+id/listview"        android:layout_width="match_parent"        android:layout_height="wrap_content"></ListView>    <!--右下角-->    <com.example.menu.view.MoonMenu        android:id="@+id/moonmenu"        android:layout_width="match_parent"        android:layout_height="match_parent"        moon:position="RIGHT_BOTTOM"        moon:radius="150dp">        <RelativeLayout            android:id="@+id/main_menu"            android:layout_width="wrap_content"            android:layout_height="wrap_content">            <ImageView                android:layout_centerInParent="true"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:src="@drawable/main" />        </RelativeLayout>        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/ic_1"            android:tag="1" />        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/ic_2"            android:tag="2" />        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/ic_3"            android:tag="3" />        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/ic_4"            android:tag="4" />        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/ic_5"            android:tag="5" />        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/ic_6"            android:tag="6" />    </com.example.menu.view.MoonMenu></RelativeLayout>

TestAcitivity.java

public class TestActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.test_activity);        initView();    }    private void initView() {        final MoonMenu moonMenu = (MoonMenu) findViewById(R.id.moonmenu);        moonMenu.setOnMenuItemClickListener(new MoonMenu.OnMenuItemClickListener() {            @Override            public void onClickMenuItem(View itemView, int position) {                //可以使用标记来判断点击的Viwew,当然必须给ChildViewItem设置了Tag                if ("1".equals(itemView.getTag())) {                    Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                } else if ("2".equals(itemView.getTag())) {                    Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                } else if ("3".equals(itemView.getTag())) {                    Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                } else if ("4".equals(itemView.getTag())) {                    Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                } else if ("5".equals(itemView.getTag())) {                    Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                } else if ("6".equals(itemView.getTag())) {                    Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                }                //可以使用position来判断                switch (position) {                    case 1:                        Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                        break;                    case 2:                        Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                        break;                    case 3:                        Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                        break;                    case 4:                        Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                        break;                    case 5:                        Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                        break;                    case 6:                        Toast.makeText(getApplicationContext(), "点击了" + position, Toast.LENGTH_SHORT).show();                        break;                }            }        });        //展示ListView,并且设置适配器和Item点击事件,滚动监听        ListView listview = (ListView) findViewById(R.id.listview);        String[] items = {"item0", "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10", "item11", "item12", "item13", "item14", "item15", "item16", "item17", "item18", "item19", "item20"};        ArrayAdapter<String> adapter = new ArrayAdapter<String>(TestActivity.this, android.R.layout.simple_list_item_activated_1, items);        listview.setAdapter(adapter);        listview.setOnScrollListener(new AbsListView.OnScrollListener() {            @Override            public void onScrollStateChanged(AbsListView view, int scrollState) {                if (moonMenu.isOpen()) {                    moonMenu.toClose();                }            }            @Override            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {            }        });        listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {            @Override            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {                if (moonMenu.isOpen()) {                    moonMenu.toClose();                }            }        });    }}


当ListView滚动的时候,如果当前的Menu是开的状态,那么就进行关闭的动画。

判断点击的ChildMenuItem,可以使用Tag(必须在xml文件中设置了Tag),或者使用position来判断。


效果图:



源码下载:   http://download.csdn.net/detail/forwardyzk/8506243


2 0
原创粉丝点击