ArcMenu

ArcMenu:

自定义属性
//attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- 定义自定义属性的类型 -->
    <!-- 定义位置相关的枚举类型 -->
    <attr name="position">
        <enum name="left_top" value="0" />
        <enum name="left_bottom" value="1" />
        <enum name="right_top" value="2" />
        <enum name="right_bottom" value="3" />
    </attr>
    <!-- 定义卫星菜单的半径类型 -->
    <attr name="radius" format="dimension" />

    <!-- 声明自定义属性可用的类 和 可用属性 -->
    <declare-styleable name="ArcMenu">
        <attr name="position" />
        <attr name="radius" />
    </declare-styleable>

</resources>

测试读取属性: 自定义控件, 使用自定义属性

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:myapp="http://schemas.android.com/apk/res/com.example.hendry_arcmenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.hendry_arcmenu.view.ArcMenu
        android:id="@+id/id_arcmenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        myapp:position="right_top"
        myapp:radius="200sp" >
    </com.example.hendry_arcmenu.view.ArcMenu>

</RelativeLayout>

构造函数中读取属性
//ArcMenu.xml

    public ArcMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.ArcMenu, defStyleAttr, 0);
        int default_radius = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, 100, context.getResources()
                        .getDisplayMetrics());
//      mRadius = a.getDimensionPixelSize(R.styleable.ArcMenu_radius,
//              default_radius);
        mRadius = (int) a.getDimension(R.styleable.ArcMenu_radius, default_radius);
        int pos = a.getInt(R.styleable.ArcMenu_position, RIGHT_BOTTOM);
        switch (pos) {
        case LEFT_TOP:
            mPosition = position.LEFT_TOP;
            break;
        case LEFT_BOTTOM:
            mPosition = position.LEFT_BOTTOM;
            break;
        case RIGHT_TOP:
            mPosition = position.RIGHT_TOP;
            break;
        case RIGHT_BOTTOM:
            mPosition = position.RIGHT_BOTTOM;
            break;
        }
        a.recycle();
        Log.i("hendry_arcmenu__", "position:" + mPosition + " radius:"
                + mRadius);
    }

OnMeasure 测量子View的宽和高 ,设置控件自身的宽和高

一般自定义view的话 只需要 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
调用这句即可, onMeasure会根据该自定义控件在布局文件中的设置, 来确定其宽高….

自定义ViewGroup的话还需要对每个子view进行测量

在测量之前, 先要把该控件的布局文件写好, 该控件内部有哪些子view都确定好了, 才能开始测量 , 子view用到图片资源等都需要copy到对应目录

这个ArcMenu控件是覆盖在其他控件之上的, 可以放在屏幕的4个角, 因此它是个全屏控件, 布局文件中宽高设置为match_parent

第一个主按钮是外边框 + 内部一张图片组合而成, 这样是为了方便图片旋转动画, 除主按钮外, 其他按钮都是图片框ImageView,

复写onMeasure方法, 最终会调用父类setMeasuredDimension方法, 或者我们复写的时候传递宽高,调用setMeasuredDimension方法, 这个方法主要作用就是把传递的参数赋值给mMeasuredWidth , mMeasuredHeight , 也就是确定了我们自定义控件的宽和高, 所以onMeasure方法执行完后, 我们可以用getMeasuredWidth , getMeasuredHeight 方法来获取自定义控件测量后得到的宽高

对于viewgroup而言, 需要遍历对每一个子view执行measureChild方法, 参数传递的都是相同的, 全都是widthMeasureSpec和heightMeasureSpec , 这时因为measureChild 方法中, 会根据传递进来的MeasureSpec参数, 生成子view对应的 childWidthMeasureSpec和childHeightMeasureSpec , 然后再调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 这个方法里面看起来挺复杂, 都是对MeasureSpec的计算, 还有cache , 但最终还是调用setMeasuredDimension 方法, 来设置该子view的宽和高

可见 MeasureSpec 就像是一个中间变量, 它主要作用是传递测量需要用到的数据, 它是一个32位的int值, 高2位代表SpecMode 测量模式, 低30位代表SpecSize , 指在某种测量模式下的规格大小

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            //这样写行不行??? 应该不行, 因为measureChild会生成新的MeasureSpec
//          getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

onLayout

onLayout不得不先说layout , layout方法是确定控件自身的位置 并且layout方法里会调用onLayout方法来确定子元素的位置…

所以自定义ViewGroup必须重写onLayout来确定子元素位置, 系统会自动调用layout方法, 从而导致我们重写的onLayout被调用…

onLayout的功能是父容器确定子元素的位置,View和ViewGroup均没有真正实现onLayout方法, 因为自定义控件子view位置是不确定的, 需要我们自定义来确定位置, 因此如果是继承的ViewGroup , 复写onLayout的时候, 不存在调用super.onLayout方法…必须自己实现onLayout, 在onLayout方法中调用layout方法来给子view定位.

注意, 不是所有的View控件都没有实现onLayout, 比如自定义控件继承自HorizontalScrollView的话 , 就需要调用其父类的onLayout
super.onLayout(changed, l, t, r, b);

刚才说了onLayout方法最终是需要通过调用View.layout 方法来确定子view位置,
而onLayout方法之所以被调用是因为 View.layout方法中会判断changed, 再次调用其onLayout(changed, l, t, r, b);

说到底, View.layout 方法给控件定位, 而如果该控件里面有子控件的话, 那么调用该控件的onLayout方法, onLayout方法中子控件又调用其自身的layout方法, 如果子控件下面又有子控件, 则layout方法被调用的时候会检查changed变量, 去执行其对应的onLayout 再定位其子view , 于是这样一层层传递下去

可见View.layout 方法中首先是对控件自身定位, 然后调用onLayout来对子控件定位, onLayout 执行过程中调用子控件对应的layout方法, layout执行中若发现子控件下还有子控件, 那么就会触发其调用onLayout来确定子控件

说回来,在调用我们重写的ViewGroup的onLayout的时候, 我们需要做的就是遍历子元素,调用layout方法, 如果该子元素下面还有子元素, 于是就再次调用其onLayout方法 , 这样子view下面的子view就得到了定位
另外就是ViewGroup原本是没有提供onLayout的, 我们复写的onLayout 是因为ViewGroup的View.layout被执行, 从而我们复写的onLayout得到调用, 因此我们不需要操心没有super.onLayout() 而viewGroup的定位没有被执行.

android.view.View.layout(int l, int t, int r, int b)

4个参数对应的是left, top, right, bottom 一般就是如mCButton.layout(l, t, l + width, t + width);

//attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- 定义自定义属性的类型 -->
    <!-- 定义位置相关的枚举类型 -->
    <attr name="position">
        <enum name="left_top" value="0" />
        <enum name="left_bottom" value="1" />
        <enum name="right_top" value="2" />
        <enum name="right_bottom" value="3" />
    </attr>
    <!-- 定义卫星菜单的半径类型 -->
    <attr name="radius" format="dimension" />

    <!-- 声明自定义属性可用的类 和 可用属性 -->
    <declare-styleable name="ArcMenu">
        <attr name="position" />
        <attr name="radius" />
    </declare-styleable>

</resources>

//activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:myapp="http://schemas.android.com/apk/res/com.example.hendry_arcmenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView
        android:id="@+id/id_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>

    <com.example.hendry_arcmenu.view.ArcMenu
        android:id="@+id/id_arcmenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        myapp:position="left_bottom"
        myapp:radius="120dp" >

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/composer_button" >

            <ImageView
                android:id="@+id/id_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:src="@drawable/composer_icn_plus" />
        </RelativeLayout>

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/composer_music"
            android:tag="Music" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/composer_place"
            android:tag="Place" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/composer_sleep"
            android:tag="Sleep" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/composer_thought"
            android:tag="Sun" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/composer_with"
            android:tag="People" />
    </com.example.hendry_arcmenu.view.ArcMenu>

</RelativeLayout>

//MainActivity.java

package com.example.hendry_arcmenu;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import com.example.hendry_arcmenu.view.ArcMenu;
import com.example.hendry_arcmenu.view.ArcMenu.OnMenuItemClickListener;

public class MainActivity extends Activity {

    private ListView mListView;
    private ArcMenu mArcMenu;
    private List<String> mData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();
        initViews();
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, mData);
        mListView.setAdapter(adapter);
        initEvents();
    }

    private void initEvents() {
        // ListView 滚动时关闭arcMenu
        mListView.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                if (mArcMenu.isOpen()) {
                    mArcMenu.toggleMenu(600);
                }

            }
        });

        // arcMenu 中子菜单的点击事件
        mArcMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {

            @Override
            public void onClick(View view, int pos) {
                Toast.makeText(
                        MainActivity.this,
                        "you clicked item getTag: " + view.getTag() + " , pos:"
                                + pos, Toast.LENGTH_SHORT).show();
            }
        });

    }

    private void initViews() {
        mListView = (ListView) findViewById(R.id.id_listview);
        mArcMenu = (ArcMenu) findViewById(R.id.id_arcmenu);
    }

    private void initData() {
        mData = new ArrayList<String>();
        // 注意这里必须用单引号表示char 而不能是字符串
        for (int i = 'A'; i < 'Z'; i++) {
            mData.add((char) i + " 测试数据"); // 强转为char 再连接字符串
        }
    }

}

ArcMenu 里面还是有个地方不清楚, 就是除了再onLayout中设置了 child.setVisibility(View.GONE); 这里可以生效 子菜单项隐藏了
这个地方以外, 遍历设置动画的时候 , 关闭菜单动画完成后 设置 child.setVisibility为GONE, 起不了作用 ,
toggleMenu里面遍历刚开始就设置childView.setVisibility(View.VISIBLE); 这里即使注释掉也没有任何影响 ….

onLayout设置为隐藏后, 只能靠设置动画的setFillAfter 来控制子菜单项是否显示, 而setVisibility 无法生效. 但看原来的代码却不是用的这个方法, 而是setVisibility 来控制是否显示子菜单项的….

//ArcMenu.java

package com.example.hendry_arcmenu.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;

import com.example.hendry_arcmenu.R;

public class ArcMenu extends ViewGroup implements OnClickListener {
    /**
     * 自定义属性变量的读取
     */

    private enum position {
        LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM
    };

    private static final int LEFT_TOP_VAL = 0;
    private static final int LEFT_BOTTOM_VAL = 1;
    private static final int RIGHT_TOP_VAL = 2;
    private static final int RIGHT_BOTTOM_VAL = 3;

    private int mRadius;
    private position mPosition = position.RIGHT_BOTTOM;

    private enum status {
        OPEN, CLOSED
    };

    private status mStatus = status.CLOSED;

    private OnMenuItemClickListener mMenuItemClickListener;

    /**
     * 子菜单点击的回调接口
     */

    public interface OnMenuItemClickListener {
        void onClick(View view, int pos);
    }

    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
        mMenuItemClickListener = listener;
    }

    public ArcMenu(Context context) {
        this(context, null);
    }

    public ArcMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ArcMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.ArcMenu, defStyleAttr, 0);
        int default_radius = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP, 100, context.getResources()
                        .getDisplayMetrics());
        // mRadius = a.getDimensionPixelSize(R.styleable.ArcMenu_radius,
        // default_radius);
        mRadius = (int) a.getDimension(R.styleable.ArcMenu_radius,
                default_radius);
        int pos = a.getInt(R.styleable.ArcMenu_position, RIGHT_BOTTOM_VAL);
        switch (pos) {
        case LEFT_TOP_VAL:
            mPosition = position.LEFT_TOP;
            break;
        case LEFT_BOTTOM_VAL:
            mPosition = position.LEFT_BOTTOM;
            break;
        case RIGHT_TOP_VAL:
            mPosition = position.RIGHT_TOP;
            break;
        case RIGHT_BOTTOM_VAL:
            mPosition = position.RIGHT_BOTTOM;
            break;
        }
        a.recycle();
        Log.i("hendry_arcmenu__", "position:" + mPosition + " radius:"
                + mRadius);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            // 这样写行不行??? 应该不行, 因为measureChild会生成新的MeasureSpec
            // getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            layoutCButton();
            // 设置其他按钮卫星扇形排列
            int count = getChildCount();

            for (int i = 0; i < count - 1; i++) {
                // 从 i + 1 开始 , 排除了第一个最面上的按钮
                View child = getChildAt(i + 1);
                // 设置子按钮为隐藏
                child.setVisibility(View.GONE);
                // 根据角度和半径计算left, top
                int cl = (int) (mRadius * Math.sin(i
                        * (Math.PI / 2 / (count - 2))));
                int ct = (int) (mRadius * Math.cos(i
                        * (Math.PI / 2 / (count - 2))));
                int cWidth = child.getMeasuredWidth();
                int cHeight = child.getMeasuredHeight();
                switch (mPosition) {
                case LEFT_TOP:

                    break;
                case LEFT_BOTTOM:

                    ct = getMeasuredHeight() - ct - cHeight;
                    break;
                case RIGHT_TOP:
                    cl = getMeasuredWidth() - cl - cWidth;
                    break;
                case RIGHT_BOTTOM:
                    cl = getMeasuredWidth() - cl - cWidth;
                    ct = getMeasuredHeight() - ct - cHeight;
                    break;
                }
                child.layout(cl, ct, cl + cWidth, ct + cHeight);
            }
        }
    }

    private void layoutCButton() {
        // 得到第一个按钮控件 作为 CButton

        View CButton = getChildAt(0);

        // 设置CButton的点击事件
        CButton.setOnClickListener(this);

        // 得到经过测量后的控件宽高
        int width = CButton.getMeasuredWidth();
        int height = CButton.getMeasuredHeight();

        // 针对自定义不同的位置计算参数
        int l = 0;
        int t = 0;
        switch (mPosition) {
        case LEFT_TOP:
            l = 0;
            t = 0;
            break;
        case LEFT_BOTTOM:
            l = 0;
            // getMeasuredHeight 返回的就是整个屏幕的高 getMeasuredWidth 即屏幕的宽
            t = getMeasuredHeight() - height;
            break;
        case RIGHT_TOP:
            l = getMeasuredWidth() - width;
            t = 0;
            break;
        case RIGHT_BOTTOM:
            l = getMeasuredWidth() - width;
            t = getMeasuredHeight() - height;
            break;
        }

        // 调用layout方法设置该控件位置
        CButton.layout(l, t, l + width, t + height);

    }

    @Override
    public void onClick(View v) {
        // CButton 中setOnClickListener , 因为这里只处理CButton事件所以不需要对view进行判断
        rotateCButton(v, 0f, 360f, 300);

        toggleMenu(300);
    }

    /**
     * 针对菜单中的每个子view设置平移动画和旋转动画
     * 
     * @param duration
     */
    public void toggleMenu(int duration) {

        int count = getChildCount();
        //让子菜单项和CButton圆心重合,  计算他们边长的差 , 后面平移的时候, 计算偏移量时去掉这个距离, 那么他们圆心就重合了
        int margin = ( getChildAt(0).getMeasuredWidth() - getChildAt(1).getMeasuredWidth()) /2 ;

        // 同样的去掉CButton
        // 整个for循环是针对每个菜单项做动画 , 每个菜单项动画完成后, 最后添加了该菜单项的事件处理
        for (int i = 0; i < count - 1; i++) {
            // 从第一个子view开始
            final View childView = getChildAt(i + 1);
            childView.setVisibility(View.VISIBLE);
            // 平移动画, 旋转动画这些用的都是相对坐标, 0, 0 代表的是原位置, 需要计算的是平移的偏移量

            // 以mRadius计算出来的偏移量, 最后发现图片的中心和CButton的中心不是重合的 , 因此需要把图片大小的误差减去 , 而这个误差应该是固定的
             int deltaX = (int) (mRadius * Math.sin(i
             * (Math.PI / 2 / (count - 2))) - margin);
             int deltaY = (int) (mRadius * Math.cos(i
             * (Math.PI / 2 / (count - 2))) - margin);


            // 假设 to open打开, 而我们前面在layout的时候, 菜单原位置是展开的且隐藏的
            // 初始状态是close的, 这时需要打开菜单, 就是把菜单放到圆心再平移到原位置
            // 平移动画 如果是左侧的话, X轴方向 起始位置应该是从菜单原位置向左移动到圆心 ,
            // 因此X轴起始位置为负值, 结束位置为自身原位置 也就是0
            // X轴方向, 向左为负, 向右为正
            // Y 轴方向 向上为负, 向下为正.. 前提条件是动画以自身为参考 , 平移动画默认就是以自身为参考
            // 左上角展开状态, 确定起始位置, 回到圆心, 需要向上(-1), 向左(-1),偏移量即sin, cos
            // 同理左下角 回圆心, X向左(-1), Y向下
            // 右上角 X向右,Y向上(-1)
            // 右下角 X向右 ,Y向下
            // 根据 4 个角的位置 确定各菜单到圆心方向
            int xflag = 1;
            int yflag = 1;
            switch (mPosition) {
            case LEFT_TOP:
                xflag = -1;
                yflag = -1;
                break;
            case LEFT_BOTTOM:
                xflag = -1;
                yflag = 1;
                break;
            case RIGHT_TOP:
                xflag = 1;
                yflag = -1;
                break;
            case RIGHT_BOTTOM:
                xflag = 1;
                yflag = 1;
                break;
            }

            // TranslateAnimation transAn = null;
            Animation transAn = null;
            if (mStatus == status.CLOSED) { // 展开菜单
                // 打开菜单的平移动画 , 从圆心 平移 原位置
                transAn = new TranslateAnimation(deltaX * xflag, 0, deltaY
                        * yflag, 0);

                // 设置可点击
                childView.setFocusable(true);
                childView.setClickable(true);

            } else { // 关闭菜单
                // 平移动画 , 从原位置 平移 到圆心 , 只需要把打开动画的参数位置换一下即可 , 因此方向的计算应该放到if外面

                transAn = new TranslateAnimation(0, deltaX * xflag, 0, deltaY
                        * yflag);
                // 平移动画只是把控件移动到圆心, 实质上, 这个控件还在原位置 , 只是看不见, 并不是被设置为GONE了,
                // 而是平移动画重绘了, 被移动到圆心了, 但其实是可以点击的, 所以这里要设置不可点击
                childView.setClickable(false);
                childView.setFocusable(false);
            }

            transAn.setFillAfter(true);
            transAn.setDuration(duration);
            // 设置动画起始的时间不同
            transAn.setStartOffset((i * 100) / count);

            // 旋转动画
            RotateAnimation rotateAnim = new RotateAnimation(0, 720,
                    Animation.RELATIVE_TO_SELF, 0.5f,
                    Animation.RELATIVE_TO_SELF, 0.5f);
            rotateAnim.setDuration(duration);
            rotateAnim.setFillAfter(true);

            AnimationSet anSet = new AnimationSet(true);
            anSet.addAnimation(rotateAnim);
            anSet.addAnimation(transAn);
            // childView.startAnimation(rotateAnim);
            // childView.startAnimation(transAn);
            // childView.startAnimation(anSet);

            /**
             * 子view 设置visibility为GONE 失败, 只能用这个方法了
             */
            if (mStatus == status.CLOSED) {
                anSet.setFillAfter(true); // animationSet 同样需要设置fillAfter
                                            // 否则子菜单消失
            } else {
                //关闭菜单 加个透明度变化
                AlphaAnimation alphaAni = new AlphaAnimation(1.0f, 0.5f);
                anSet.addAnimation(alphaAni);

                anSet.setFillAfter(false);
            }
            anSet.setAnimationListener(new AnimationListener() {

                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                }

                /**
                 * 这个地方运行起不到作用?
                 */
                @Override
                public void onAnimationEnd(Animation animation) {
                    if (mStatus == status.CLOSED) {
                        childView.setVisibility(View.GONE);
                    }
                }
            });
            childView.startAnimation(anSet);

            /**
             * 设置子菜单的点击事件
             */
            final int pos = i + 1; // 我们遍历子菜单都是 i + 1 除开了CButton
            childView.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    // 调用我们自定义的接口实现
                    if (null != mMenuItemClickListener) {
                        mMenuItemClickListener.onClick(v, pos);
                    }
                    // 子菜单的点击动画, pos -1 是把下标还原了, 包括了CButton为0
                    menuItemAnim(pos - 1);
                    // 点击子菜单后, 菜单状态设置为关闭
                    changeStatus();

                }
            });
        }

        // 改变菜单的当前状态
        changeStatus();
        Log.i("MYTAG", "mStatus : " + mStatus);
    }

    private void changeStatus() {
        mStatus = (mStatus == status.CLOSED ? status.OPEN : status.CLOSED);
    }

    /**
     * 子菜单动画 , pos 从0开始 是原下标一致 子菜单动画对点击的进行放大, 其他的全部缩小, 所以需要对所有的子菜单项遍历
     */
    protected void menuItemAnim(int pos) {
        int count = getChildCount();
        for (int i = 0; i < count - 1; i++) {
            View child = getChildAt(i + 1);
            if (pos == i) {
                // 放大
                child.startAnimation(scaleBigAni(500));
            } else {
                // 缩小
                child.startAnimation(scaleSmallAnimation(500,0.0f));
            }
            // 设置菜单项不可用
            child.setClickable(false);
            child.setFocusable(false);
        }

    }

    private Animation scaleBigAni(int duration) {
        AnimationSet animationSet = new AnimationSet(true);
        // 放大4倍
        ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        // 从可见到消失
        AlphaAnimation alphaAnim = new AlphaAnimation(1f, 0.0f);

        animationSet.addAnimation(scaleAnim);
        animationSet.addAnimation(alphaAnim);

        animationSet.setDuration(duration);
        animationSet.setFillAfter(true);
        return animationSet;
    }

    private Animation scaleSmallAnimation(int duration , float toScale) {
        AnimationSet animationSet = new AnimationSet(true);

        ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, toScale, 1.0f, toScale,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        AlphaAnimation alphaAnim = new AlphaAnimation(1.f, 0.0f);

        animationSet.addAnimation(scaleAnim);
        animationSet.addAnimation(alphaAnim);

        animationSet.setDuration(duration);
        animationSet.setFillAfter(true);

        return animationSet;
    }

    private void rotateCButton(View v, float start, float end, int duration) {
        RotateAnimation anima = new RotateAnimation(start, end,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        anima.setDuration(duration);
        anima.setFillAfter(true); // 动画完成后保持完成时状态
        v.startAnimation(anima);
    }

    public Boolean isOpen() {
        return mStatus == status.OPEN;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值