Android 自定义卫星式弧形菜单

2024-01-03 03:20

本文主要是介绍Android 自定义卫星式弧形菜单,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

学自鸿洋(hyman)的imooc视频



import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;import com.stone.satellitemenu.R;/*** 卫星式菜单* author : stone* email  : aa86799@163.com* time   : 15/5/7 14 17*/public class SateliteMenu extends ViewGroup implements View.OnClickListener {public enum Position {POS_LEFT_TOP, POS_RIGHT_TOP, POS_LEFT_BOTTOM, POS_RIGHT_BOTTOM}private final int LEFT_TOP = 1;private final int RIGHT_TOP = 2;private final int LEFT_BOTTOM = 4;private final int RIGHT_BOTTOM = 8;private final int STATUS_OPEN = 0; //菜单的状态 打开private final int STATUS_CLOSE = 1; //菜单的状态 关闭private Position mPosition;private int mRadius;private int mStatus;private onMenuItemClickListener mMenuItemClickListener;private View mMenuButton;public interface onMenuItemClickListener {/*** @param view  item-view* @param position  item-position*/void onItemClick(View view, int position);}public SateliteMenu(Context context) {this(context, null);}public SateliteMenu(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SateliteMenu(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SateliteMenu);int position = typedArray.getInt(R.styleable.SateliteMenu_position, LEFT_TOP);//定义半径默认值float defRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());switch (position) {case LEFT_TOP:mPosition = Position.POS_LEFT_TOP;break;case RIGHT_TOP:mPosition = Position.POS_RIGHT_TOP;break;case LEFT_BOTTOM:mPosition = Position.POS_LEFT_BOTTOM;break;case RIGHT_BOTTOM:mPosition = Position.POS_RIGHT_BOTTOM;break;}mRadius = (int) typedArray.getDimension(R.styleable.SateliteMenu_radius, defRadius);typedArray.recycle(); //回收mStatus = STATUS_CLOSE; //默认关闭状态}public void setOnMenuItemClickListener(onMenuItemClickListener menuItemClickListener) {this.mMenuItemClickListener = menuItemClickListener;}public void setPosition(Position position) {if (mPosition == position) {return;}this.mPosition = position;View child;int count = getChildCount();for (int i = 0; i < count; i++) {child = getChildAt(i);child.clearAnimation();}
//        invalidate(); //会触发 测量、布局和绘制requestLayout(); //这里只要请求布局}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//测量子viewfor (int i = 0, count = getChildCount(); i < count; i++) {//需要传入父view的specmeasureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);}}@Override //lt 左上点  rb 右下点  如果 r<l 或 b<t 则无法显示了protected void onLayout(boolean changed, int l, int t, int r, int b) {//l=0, t=0  因为是相对于父view的位置layoutMenuButton();/*分析:menuButton距离每个item为radius。到item作直线,其夹角,应为90度均分。90/(item-1)=每个夹角的度数。有角度,就能求出正弦值sina。根据正弦公式:sina=a/c,且已知c=radius,求出a边长,即x坐标。有角度,就能求出正弦值cosa。余弦公式:cosa=b/c,且已知radius(斜边),求出b边长,即y坐标*/int count = getChildCount();double angle = 90.0f / (count - 2);//这里-2,是多减去了一个menuButtonView child;int w,h;for (int i = 1; i < count; i++) {child = getChildAt(i);child.setVisibility(View.GONE);w = child.getMeasuredWidth();h = child.getMeasuredHeight();double sin = 0, cos = 0;//Math.toRadians:math.pi/180 * angle = 弧度   angel/180*pi<==>angel*pi/180sin = Math.sin(Math.toRadians(angle * (i - 1))); //第i个角度的 sin(0)=0   i-1即从0开始,会有与屏幕直角边平行的 math.sin需要传弧度值cos = Math.cos(Math.toRadians(angle * (i - 1)));// 邻直角边/斜边   cos(0)=1l = (int) (mRadius * sin); //对横边长t = (int) (mRadius * cos); //邻纵边长//左上,左下 left值 就是上面的l l递增    符合默认变化规则//左上,右上 top值 就是上面的t  t递减    符合默认变化规则//右上、右下 left值一样: 从右向左 递减if (mPosition == Position.POS_RIGHT_TOP || mPosition == Position.POS_RIGHT_BOTTOM) {l = getMeasuredWidth() - w - l;}//左下、右下 top值一样: 从上向下 递增if (mPosition == Position.POS_LEFT_BOTTOM || mPosition == Position.POS_RIGHT_BOTTOM) {t = getMeasuredHeight() - h - t;}child.layout(l, t, l + w, t + h);final int pos = i;child.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if (mMenuItemClickListener != null) {mMenuItemClickListener.onItemClick(v, pos);itemAnim(pos);}mStatus = STATUS_CLOSE; //关闭状态}});}}/*** 菜单按钮设置layout*/private void layoutMenuButton() {mMenuButton = getChildAt(0);int l = 0, t = 0;int w = mMenuButton.getMeasuredWidth();int h = mMenuButton.getMeasuredHeight();switch (mPosition) {case POS_LEFT_TOP:l = t = 0;break;case POS_RIGHT_TOP:l = getMeasuredWidth() - w;t = 0;break;case POS_LEFT_BOTTOM:l = 0;t = getMeasuredHeight() - h;break;case POS_RIGHT_BOTTOM:l = getMeasuredWidth() - w;t = getMeasuredHeight() - h;break;}mMenuButton.layout(l, t, w + l, h + t);mMenuButton.setOnClickListener(this);}@Overridepublic void onClick(View v) {rotateMenuBotton(mMenuButton, 360, 500);toggleMenu(500);}private void rotateMenuBotton(View view, int angle, int duration) {RotateAnimation anim = new RotateAnimation(0, angle, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);anim.setDuration(duration);anim.setFillAfter(true); //view保持在动画结束位置view.startAnimation(anim);}/*** 展开/隐藏子菜单* 子菜单动画 平移*/private void toggleMenu(int duration) {int count = getChildCount();for (int i = 1; i < count; i++) {final View child = getChildAt(i);/*平移动画 以layout中计算的长度 再乘以1或-1close:左上   r->l b->t右上   l->r b->t左下   r->l t->b右下   l->r t->bopen:左上右上左下右下*/int xflag = 1, yflag = 1;//if (mPosition == Position.POS_LEFT_TOP || mPosition == Position.POS_LEFT_BOTTOM) {xflag = -1;}//if (mPosition == Position.POS_LEFT_TOP || mPosition == Position.POS_RIGHT_TOP) {yflag = -1;}double angle = 90 / (count - 2);/*一个圆的弧度是2π,角度是360°   π/2即90度的弧度*/int oppositeLen = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * (i - 1))); //对边 横向int adjacentLen = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * (i - 1))); //邻边 纵向
/*一个圆的弧度是2π,角度是360°  π/180,每角度对应的弧度   然后乘以角度数=其所对应的弧度*/
//            int oppositeLen = (int) (mRadius * Math.sin(angle * Math.PI / 180 * (i-1))); //对边 横向
//            int adjacentLen = (int) (mRadius * Math.cos(angle * Math.PI / 180 * (i-1))); //邻边 纵向int stopx = xflag * oppositeLen;int stopy = yflag * adjacentLen;AnimationSet set = new AnimationSet(true);if (mStatus == STATUS_OPEN) {//如是打开,则要关闭//4个值是起始点和结束点,相对于自身x、y的距离TranslateAnimation tranAnim = new TranslateAnimation(0, stopx, 0, stopy);tranAnim.setStartOffset(mRadius/6);//偏移set.addAnimation(tranAnim);AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);set.addAnimation(alphaAnim);set.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {setItemClickable(child, false);}@Overridepublic void onAnimationEnd(Animation animation) {}@Overridepublic void onAnimationRepeat(Animation animation) {}});} else { //要打开TranslateAnimation tranAnim = new TranslateAnimation(stopx, 0, stopy, 0);set.addAnimation(tranAnim);AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);set.addAnimation(alphaAnim);set.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {setItemClickable(child, false);}@Overridepublic void onAnimationEnd(Animation animation) {setItemClickable(child, true);}@Overridepublic void onAnimationRepeat(Animation animation) {}});}set.setDuration(duration);set.setFillAfter(true);child.startAnimation(set);}if (mStatus == STATUS_OPEN) {mStatus = STATUS_CLOSE;} else {mStatus = STATUS_OPEN;}}/*** item点击动画* @param position*/private void itemAnim(int position) {View child;int count = getChildCount();for (int i = 1; i < count; i++) {child = getChildAt(i);if (position == i) {scaleBigAnim(child);} else {scaleSmallAnim(child);}setItemClickable(child, false);}}private void scaleBigAnim(View view) {ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 3f, 1.0f, 3f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);AnimationSet set = new AnimationSet(true);set.addAnimation(alphaAnim);set.addAnimation(scaleAnim);set.setDuration(800);set.setFillAfter(true);view.startAnimation(set);}private void scaleSmallAnim(View view) {ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 0, 1.0f, 0,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0);AnimationSet set = new AnimationSet(true);set.addAnimation(alphaAnim);set.addAnimation(scaleAnim);set.setFillAfter(true);set.setDuration(500);view.startAnimation(set);}private void setItemClickable(View view, boolean flag) {view.setClickable(flag);view.setFocusable(flag);}}

我的自定义View项目地址: https://github.com/aa86799/MyCustomView (欢迎start&fork)

本文地址:https://github.com/aa86799/MyCustomView/tree/master/satellitemenu

这篇关于Android 自定义卫星式弧形菜单的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/564437

相关文章

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

springboot自定义注解RateLimiter限流注解技术文档详解

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,... 目录什么是限流系统架构核心组件详解1. 限流注解 (@RateLimiter)2. 限流类型枚举 (

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

一文详解Java Stream的sorted自定义排序

《一文详解JavaStream的sorted自定义排序》Javastream中的sorted方法是用于对流中的元素进行排序的方法,它可以接受一个comparator参数,用于指定排序规则,sorte... 目录一、sorted 操作的基础原理二、自定义排序的实现方式1. Comparator 接口的 Lam

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级