Android 实现卫星菜单

步骤:一:自定义ViewGroup

        1、自定义属性

               a、attr.xml

               b、在布局文件中使用activity_main.xml

               c、在自定义控件中进行读取

        2、onMeasure

        3、onLayout

        4、设置主按钮的旋转动画

             为menuItem添加平移动画和旋转动画

             实现menuItem的点击事件

 

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private List<String> mData;
    private ArrayAdapter mAdapter;

    private ArcMenuActivity mArc;

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

        listView = (ListView) findViewById(R.id.listview);
        mArc = (ArcMenuActivity) findViewById(R.id.view_arc);

        addData();
        mAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, mData);
        listView.setAdapter(mAdapter);

        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

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

        mArc.setOnMenuItemClickListener(new ArcMenuActivity.onMenuItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this,position+" : "+view.getTag(),Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void addData() {
        mData = new ArrayList<String>();
        for (int i = 'A'; i <= 'z'; i++) {
            mData.add((char) i + "");
        }
    }
    
}

 

ArcMenuActivity.java

/**
 * 自定义的ViewGroup,在activity_main.xml里面调用
 */
public class ArcMenuActivity extends ViewGroup {
    private static final int POS_LEFT_TOP = 0;
    private static final int POS_RIGHT_TOP = 1;
    private static final int POS_LEFT_BOTTOM = 2;
    private static final int POS_RIGHT_BOTTOM = 3;

    private Position mPosition = Position.RIGHT_BOTTOM;
    private Status mCurrentStatus = Status.CLOSE;
    private onMenuItemClickListener onMenuItemClick;
    private View mCButton;       //主菜单按钮
    private int mRadius;         //卫星半径

    //卫星菜单位置枚举类
    public enum Position {
        LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM
    }

    //主菜单的状态
    public enum Status {
        OPEN, CLOSE
    }

    //定义一个点击点击子菜单项的回调接口
    public interface onMenuItemClickListener {
        void onItemClick(View view, int position);
    }

    //自定义的点击方法
    public void setOnMenuItemClickListener(onMenuItemClickListener onMenuItemClick) {
        this.onMenuItemClick = onMenuItemClick;
    }

    public ArcMenuActivity(Context context) {
//        super(context);
        this(context, null);
    }

    public ArcMenuActivity(Context context, AttributeSet attrs) {
//        super(context, attrs);
        this(context, attrs, 0);
    }

    public ArcMenuActivity(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //获取自定义属性的值
        TypedArray ta = context.getTheme().obtainStyledAttributes(
                attrs, R.styleable.ArcMenu, defStyleAttr, 0);

        //getInt()方法
        //参数1:所需要赋予给pos的值
        // 参数2:如果参数1无值,则取该值,就是custom:position="right_bottom"没有定义时
        int pos = ta.getInt(R.styleable.ArcMenu_position, POS_RIGHT_BOTTOM);
        switch (pos) {
            case POS_LEFT_TOP:
                mPosition = Position.LEFT_TOP;
                break;
            case POS_LEFT_BOTTOM:
                mPosition = Position.LEFT_BOTTOM;
                break;
            case POS_RIGHT_TOP:
                mPosition = Position.RIGHT_TOP;
                break;
            case POS_RIGHT_BOTTOM:
                mPosition = Position.RIGHT_BOTTOM;
                break;
        }

        //半径的默认值
        mRadius = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());

        //getDimension()方法
        //参数1:所需要赋予给radius的值
        // 参数2:如果参数1无值,则取该值,就是custom:radius="100"没有定义时
        mRadius = (int) ta.getDimension(R.styleable.ArcMenu_radius, TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics()));

        //radius输出的值为px
        Log.v("TGA", "position = " + mPosition + ",radius = " + mRadius);

        ta.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取activity_main.xml里面的View控件的个数
        // <my.com.example.x550v.view.ArcMenuActivity/>里面的
        int count = getChildCount();
        //测量child
        for (int i = 0; i < count; i++) {
            //xml文件里面控件的位置,宽,高
            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();
            //子菜单按钮
            subItemButton();
        }
    }

    //定义主菜单按钮
    private void layoutCButton() {
        //或者使用findViewById()的方法
        mCButton = getChildAt(0); //获取第一个xml文件里面的第一个View控件
        mCButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //主菜单动画
                //这里得使用getContext()来获取context
                Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.anim);
                //如果使用setAnimation()只会转一次
                mCButton.startAnimation(animation);

                //子菜单动画
                toggleMenu(200);
            }
        });

        //l为主菜单距离父布局的左边距离,t为主菜单距离父布局的顶边距离
        int l = 0, t = 0;
        //获取主按钮的宽和高
        int width = mCButton.getMeasuredWidth();
        int height = mCButton.getMeasuredHeight();

        switch (mPosition) {
            case LEFT_TOP:
                //0,0表示坐上角的位置
                l = 0;
                t = 0;
                break;
            case RIGHT_TOP:
                l = getMeasuredWidth() - width;   //getMeasuredWidth()取得容器的宽度
                t = 0;
                break;
            case LEFT_BOTTOM:
                l = 0;
                t = getMeasuredHeight() - height; //getMeasuredHeight()取得容器的高度
                break;
            case RIGHT_BOTTOM:
                l = getMeasuredWidth() - width;
                t = getMeasuredHeight() - height;
                break;
        }
        mCButton.layout(l, t, l + width, t + height);
    }

    //定义子菜单按钮
    public void subItemButton() {
        //获取activity_main.xml里面的View控件的个数
        // <my.com.example.x550v.view.ArcMenuActivity/>里面的
        int count = getChildCount();

        for (int i = 0; i < count - 1; i++) { //去掉主菜单按钮的一个
            View child = getChildAt(i + 1);   //从第一个子菜单开始获取,而不是主菜单

            //开始时设置子菜单为隐藏
            child.setVisibility(GONE);

            //当子菜单为左上角时,cl为子菜单距离父布局的左边距离,ct为子菜单距离父布局的顶边距离
            //当子菜单为右上角时,cl为子菜单距离父布局的右边距离,ct为子菜单距离父布局的顶边距离
            //当子菜单为左下角时,cl为子菜单距离父布局的左边距离,ct为子菜单距离父布局的底边距离
            //当子菜单为右下角时,cl为子菜单距离父布局的右边距离,ct为子菜单距离父布局的底边距离
            //Math.PI的值为圆周率pai,角度为180度
            //Math.PI / 2 / (count - 2)是取出平均角,
            //*i是看子菜单拥有几个平均角
            int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
//            Log.v("TAG","cl = "+cl);
            int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));

            //获取子菜单View的宽和高
            int cWidth = child.getMeasuredWidth();
            int cHeight = child.getMeasuredHeight();

            //如果子菜单在左下,右下
            if (mPosition == Position.LEFT_BOTTOM || mPosition == Position.RIGHT_BOTTOM) {
                ct = getMeasuredHeight() - cHeight - ct;
                Log.v("TAG","getMeasuredHeight()"+getMeasuredHeight());
            }
            //如果子菜单在右上,右下
            if (mPosition == Position.RIGHT_TOP || mPosition == Position.RIGHT_BOTTOM) {
                cl = getMeasuredWidth() - cWidth - cl;
            }
            child.layout(cl, ct, cl + cWidth, ct + cHeight);
        }
    }

    //定义点击主菜单后子菜单出现动画
    public void toggleMenu(int duration) {
        int count = getChildCount();
        for (int i = 0; i < count - 1; i++) {
            final View childView = getChildAt(i + 1);
            childView.setVisibility(View.VISIBLE);

            //end 0,0
            //start
            int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
            int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));

            int xFlag = 1;
            int yFlag = 1;

            if (mPosition == Position.LEFT_TOP || mPosition == Position.LEFT_BOTTOM) {
                xFlag = -1;
            }
            if (mPosition == Position.LEFT_TOP || mPosition == Position.RIGHT_TOP) {
                yFlag = -1;
            }

            final AnimationSet animationSet = new AnimationSet(true);

            //位移动画
            Animation tranAnim = null;
            //如果为关闭状态,点击后会散开
            if (mCurrentStatus == Status.CLOSE) {
                //子菜单一开始就是扇形排布,其坐标为(0,0)
                tranAnim = new TranslateAnimation(xFlag * cl, 0, yFlag * ct, 0);
                childView.setClickable(true);
                childView.setFocusable(true);
            }
            //否则为打开状态,点击后会收缩
            else {
                tranAnim = new TranslateAnimation(0, xFlag * cl, 0, yFlag * ct);
                childView.setClickable(false);
                childView.setFocusable(false);
            }
            tranAnim.setFillAfter(true);
            tranAnim.setDuration(duration);
            tranAnim.setStartOffset((i * 100) / count); //越是后面的子菜单,延迟越多
            tranAnim.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    if (mCurrentStatus == Status.CLOSE) {
                        childView.setVisibility(GONE);
                    }
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

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

            //先旋转,再位移
            //先位移,再旋转更炫
//            animationSet.addAnimation(tranAnim);
            animationSet.addAnimation(rotateAnim);
            animationSet.addAnimation(tranAnim);
            childView.startAnimation(animationSet);

            final int pos = i + 1;
            //子菜单的点击监听
            childView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (onMenuItemClick != null) {
                        onMenuItemClick.onItemClick(childView, pos);
                    }
                    menuItemAnim(pos - 1);  //子菜单点击动画
                    changeStatus();
                }
            });
        }
        //切换菜单状态,在for()循环之外
        changeStatus();
    }

    //切换菜单状态
    private void changeStatus() {
        mCurrentStatus = (mCurrentStatus == Status.CLOSE ? Status.OPEN : Status.CLOSE);
    }

    //添加子菜单点击动画
    private void menuItemAnim(int pos) {
        for (int i = 0; i < getChildCount() - 1; i++) {
            View childView = getChildAt(i + 1);
            if (i == pos) {
                childView.startAnimation(scaleBigAnimation(300));
            } else {
                childView.startAnimation(scaleSmallAnimation(300));
            }
            childView.setClickable(false);
            childView.setFocusable(false);
        }
    }

    //子菜单变大,变小动画
    private Animation scaleBigAnimation(int duration) {
        AnimationSet set = new AnimationSet(true);
        Animation scaleAnim = new ScaleAnimation(1, 2, 1, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        Animation alphaAnim = new AlphaAnimation(1, 0);
        set.setDuration(duration);
        set.addAnimation(scaleAnim);
        set.addAnimation(alphaAnim);
        set.setFillAfter(true);
        return set;
    }

    private Animation scaleSmallAnimation(int duration) {
        AnimationSet set = new AnimationSet(true);
        Animation scaleAnim = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        Animation alphaAnim = new AlphaAnimation(1, 0);
        set.setDuration(duration);
        set.addAnimation(scaleAnim);
        set.addAnimation(alphaAnim);
        set.setFillAfter(true);
        return set;
    }

    public boolean isOpen(){
        return mCurrentStatus == Status.OPEN;
    }

}

 

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    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="match_parent"/>

    <my.com.example.x550v.view.ArcMenuActivity
        android:id="@+id/view.arc"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        custom:position="right_bottom"
        custom:radius="200dp">

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

            <ImageView
                android:id="@+id/iv_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_camera"
            android:tag="Camera" />

        <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="Thought" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/composer_with"
            android:tag="With" />

    </my.com.example.x550v.view.ArcMenuActivity>

</RelativeLayout>

 

attr.xml

<resources>
    <attr name="position">
        <enum name="left_top" value="0" />
        <enum name="right_top" value="1" />
        <enum name="left_bottom" 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>

 

anim.xml

<set
    xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
        android:duration="500"
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter="true"/>
</set>

 

运行效果:

posted @ 2016-03-10 19:46  玉天恒  阅读(591)  评论(0编辑  收藏  举报