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中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

基于Spring实现自定义错误信息返回详解

《基于Spring实现自定义错误信息返回详解》这篇文章主要为大家详细介绍了如何基于Spring实现自定义错误信息返回效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景目标实现产出背景Spring 提供了 @RestConChina编程trollerAdvice 用来实现 HTT

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D

Android如何获取当前CPU频率和占用率

《Android如何获取当前CPU频率和占用率》最近在优化App的性能,需要获取当前CPU视频频率和占用率,所以本文小编就来和大家总结一下如何在Android中获取当前CPU频率和占用率吧... 最近在优化 App 的性能,需要获取当前 CPU视频频率和占用率,通过查询资料,大致思路如下:目前没有标准的