概述

现在很多App会在入口比较浅的页面添加一些快捷操作入口,一方面是为了方便用户操作,一方面是为了提高产品一些关键入口的使用率,让用户能够在浏览信息流的过程中能快速切换至其他一些功能页面。例如豆瓣的首页 (右下角红框选中部分):

豆瓣菜单

本文将仿照这种菜单效果进行实现,最终效果如下:

弧形菜单效果图

需要定制的特性

1.菜单展开半径

2.设置菜单主按钮Icon

3.设置菜单子项的各个Icon

4.展开和收缩的动画时长

5.所有菜单按钮的宽高

6.是否在展开收缩的同时旋转主按钮

注:理论上可以设置无数个菜单项,但是会出现重叠情况(空间有限),这种情况得自行调整按钮数量和宽高。

实现思路

可以看到这个菜单是由多个按钮组合而成,所以可以考虑用ViewGroup来作为载体,其中的子View再通过属性动画进行配合达成效果,而各个菜单项的弹出角度可以针对90°来进行弧度平分,再通过三角函数得到最终展开的目标坐标,关键要注意View的宽高边距的计算,否则可能会出现超出边界的情况。

1)初始化基本框架

由于菜单是由多个按钮叠加在一个平面,所以可以考虑采用继承FrameLayout,然后根据设置的Icon资源Id的数量来作为按钮的数量进行初始化,代码如下:List mImgViews = new ArrayList<>();

List mMenuItemResIds = new ArrayList<>();    /**

* 初始化主按钮

* @param context

*/

private void initMenuView(Context context) {

mMenuIv = new ImageView(context);

mMenuIv.setImageResource(mMenuResId);

FrameLayout.LayoutParams params = new LayoutParams(mMenuWidth, mMenuWidth);

params.bottomMargin = mMenuItemWidth / 2;

params.rightMargin = mMenuItemWidth / 2;

params.gravity = Gravity.BOTTOM | Gravity.RIGHT;

addView(mMenuIv, params);

mMenuIv.setOnClickListener(this);

}    /**

* 初始化菜单子项按钮

* @param context

*/

private void initMenuItemViews(Context context) {

mImgViews.clear();        for (int index = 0; index

ImageView menuItem = new ImageView(context);

menuItem.setImageResource(mMenuItemResIds.get(index));

FrameLayout.LayoutParams params = new LayoutParams(mMenuItemWidth, mMenuItemWidth);

params.bottomMargin = mMenuItemWidth / 2;

params.rightMargin = mMenuItemWidth / 2;

params.gravity = Gravity.BOTTOM | Gravity.RIGHT;

menuItem.setTag(index);

menuItem.setOnClickListener(this);

addView(menuItem, params);

menuItem.setScaleX(0f);

menuItem.setScaleY(0f);

mImgViews.add(menuItem);

}

}

可以看到都设置在了父容器的右下角,且设置了margin值,这是由于我们点击菜单瞬间有放大双倍的效果,所以这里需要为其边缘腾出一点空间,否则处于边缘的菜单项放大时,会有部分被切掉影响观感,记得为每个子项设置Tag(这里设置为下标),后面触发点击事件时会用到。

2)展开菜单

前面说过了,主要是根据平分弧度的思路来计算,从效果图中可以看出,我们的整个展开角度是90°,那么每个菜单项的角度应该是90°/(菜单的数量-1),计算出这个角度有什么作用呢?可以先通过下图帮忙理解:

弧形菜单弹出距离计算示意图

可以看到,要做弹出动画,就需要计算出弹出的横向距离和纵向距离,刚才计算出来的角度在这就派上用场啦,利用三角函数可以得到:

tranX = 弹出半径*sin(90 * i / (count - 1));

tranY = 弹出半径*cos(90 * i / (count - 1));

再结合透明度和大小的变化,代码如下:/**

* 菜单展开动画

*/

private void startOpenAnim() {        int count = mMenuItemResIds.size();

List animators = new ArrayList<>();        for (int i = 0; i

ObjectAnimator animatorX = ObjectAnimator.ofFloat(mImgViews.get(i), "translationX", 0f, tranX);

ObjectAnimator animatorY = ObjectAnimator.ofFloat(mImgViews.get(i), "translationY", 0f, tranY);

ObjectAnimator alpha = ObjectAnimator.ofFloat(mImgViews.get(i), "alpha", 0, 1);

ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleX", 0.1f, 1);

ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleY", 0.1f, 1);

animators.add(animatorX);

animators.add(animatorY);

animators.add(alpha);

animators.add(scaleX);

animators.add(scaleY);

}

AnimatorSet animatorSet = new AnimatorSet();

animatorSet.setDuration(mDuration);

animatorSet.playTogether(animators);

animatorSet.start();

}

3)收回菜单

上一步已经理解了如何展开菜单,回收菜单自然就容易多了,没错,就是反其道而行之:/**

* 菜单收回动画

*/

private void startCloseAnim() {        int count = mMenuItemResIds.size();

List animators = new ArrayList<>();        for (int i = 0; i

ObjectAnimator animatorX = ObjectAnimator.ofFloat(mImgViews.get(i), "translationX", tranX, 0f);

ObjectAnimator animatorY = ObjectAnimator.ofFloat(mImgViews.get(i), "translationY", tranY, 0f);

ObjectAnimator alpha = ObjectAnimator.ofFloat(mImgViews.get(i), "alpha", 1, 0);

ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleX", 1, 0.3f);

ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleY", 1, 0.3f);

animators.add(animatorX);

animators.add(animatorY);

animators.add(alpha);

animators.add(scaleX);

animators.add(scaleY);

}

AnimatorSet animatorSet = new AnimatorSet();

animatorSet.setDuration(mDuration);

animatorSet.playTogether(animators);

animatorSet.start();

}

其实主要就是在做位移动画的时候,从tranX和tranY位移到0,回到原来的位置。

4)菜单子项点击动画

以上完成了菜单的展开和收缩,基本的模样已经出来了,还可以为其子项添加一些点击效果,让整个View更为生动,代码如下:/**

* 菜单子项点击动画

*

* @param index 子项下标

*/

private void startClickItemAnim(int index) {        int count = mMenuItemResIds.size();

List animators = new ArrayList<>();        //当前被点击按钮放大且逐渐变透明,造成消散效果

ObjectAnimator clickItemAlpha = ObjectAnimator.ofFloat(mImgViews.get(index), "alpha", 1, 0);

ObjectAnimator clickItemScaleX = ObjectAnimator.ofFloat(mImgViews.get(index), "scaleX", 1, 2);

ObjectAnimator clickItemScaleY = ObjectAnimator.ofFloat(mImgViews.get(index), "scaleY", 1, 2);

animators.add(clickItemAlpha);

animators.add(clickItemScaleX);

animators.add(clickItemScaleY);        for (int i = 0; i

continue;

}            //其他选项缩小且变透明

ObjectAnimator alpha = ObjectAnimator.ofFloat(mImgViews.get(i), "alpha", 1, 0);

ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleX", 1, 0.1f);

ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImgViews.get(i), "scaleY", 1, 0.1f);

animators.add(alpha);

animators.add(scaleX);

animators.add(scaleY);

}

AnimatorSet animatorSet = new AnimatorSet();

animatorSet.setDuration(500);

animatorSet.playTogether(animators);

animatorSet.start();

animatorSet.addListener(new Animator.AnimatorListener() {            @Override

public void onAnimationStart(Animator animator) {

}            @Override

public void onAnimationEnd(Animator animator) {                //点击动画结束之后要将所有子项归位

resetItems();

}            @Override

public void onAnimationCancel(Animator animator) {

}            @Override

public void onAnimationRepeat(Animator animator) {

}

});

}

首先传进来一个index参数,其实就是之前我们在初始化的时候为每个子View设置的Tag,在每次onClick的时候,通过 view.getTag() 获取到对应的下标,传进来之后,循环遍历所有子View,根据这个下标来判断当前点击的是哪个菜单项,将其做放大消散的动画效果,其他菜单项则单纯消散即可。

并且这里注意,要在动画结束时,将所有子项设置回展开之前的位置,否则当再次点击菜单按钮时,菜单项会在圆弧上闪现了一下,体验很差,因此要在onAnimationEnd的回调中重置所有子项,重置代码如下:/**

* 重置所有子项位置

*/

private void resetItems() {        int count = mImgViews.size();        for (int i = 0; i

mImgViews.get(i).setTranslationX(tranX);

mImgViews.get(i).setTranslationY(tranY);

}

mIsOpen = false;

}

5)旋转主菜单按钮

我们还可以在展开收缩的同时,还可以为菜单按钮添加上一些花样,将其旋转一下,使整个动画更加自然:/**

* 旋转主菜单按钮

*

* @param startAngel 起始角度

* @param endAngel   结束角度

*/

private void rotateMenu(int startAngel, int endAngel) {        if (!mCanRotate) {            return;

}

ObjectAnimator clickItemAlpha = ObjectAnimator.ofFloat(mMenuIv, "rotation", startAngel, endAngel);

clickItemAlpha.setDuration(mDuration);

clickItemAlpha.start();

}

6)添加外部点击监听

提供一个供外界设置banner数据的方法:ClickMenuListener mItemListener;    public void setClickItemListener(ClickMenuListener mItemListener) {        this.mItemListener = mItemListener;

}    public interface ClickMenuListener {        void clickMenuItem(int resId);

}    @Override

public void onClick(View view) {        if (view == mMenuIv) {

...

} else {

...            if (mItemListener != null && index

mItemListener.clickMenuItem(mMenuItemResIds.get(index));

}

}

}

就是正常的暴露接口,将菜单对应的资源id传出去,供外界判断点击的是哪个菜单项。

应用

xml布局中引用(这里的宽高由设置的弧长半径决定,只需设置wrap_conetnt即可):

android:id="@+id/arc_menu"

android:layout_width="match_parent"

android:layout_height="wrap_content"

app:spread_radius="150dp"

app:duration="1000"

app:menu_width="64dp"

app:menu_item_width="64dp"

app:can_rotate="true"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintBottom_toBottomOf="parent"

/>

Acitivity中实例代码如下:mArcMenuView = findViewById(R.id.arc_menu);

List menuItems = new ArrayList<>();

menuItems.add(R.drawable.ic_menu_camera);

menuItems.add(R.drawable.ic_menu_photo);

menuItems.add(R.drawable.ic_menu_share);

mArcMenuView.setMenuItems(menuItems);

mArcMenuView.setClickItemListener(new YArcMenuView.ClickMenuListener() {            @Override

public void clickMenuItem(int resId) {                switch (resId){                    case R.drawable.ic_menu_camera:

Toast.makeText(getApplicationContext(), "点击了相机", Toast.LENGTH_SHORT).show();                        break;                    case R.drawable.ic_menu_photo:

Toast.makeText(getApplicationContext(), "点击了相册", Toast.LENGTH_SHORT).show();                        break;                    case R.drawable.ic_menu_share:

Toast.makeText(getApplicationContext(), "点击了分享", Toast.LENGTH_SHORT).show();                        break;

}

}

});

作者:Android小Y

链接:https://www.jianshu.com/p/220da4460e5d

android自定义弧形,Android 自定义弧形旋转菜单栏——卫星菜单相关推荐

  1. Android自定义View之圆弧形进度条,支持背景图片设置

    Android下自定义的一款圆弧形进度条,支持中心图片设置,有兴趣的朋友可以尝试下. Github地址点击打开链接 package com.julyapp.progressdemo.view;impo ...

  2. Android 自定义View消除锯齿实现图片旋转,添加边框及文字说明

    先看看图片的效果,左边是原图,右边是旋转之后的图:   之所以把这个写出来是因为在一个项目中需要用到这样的效果,我试过用FrameLayout布局如上的画面,然后旋转FrameLayout,随之而来也 ...

  3. Android 自定义拍照,解决图片旋转,拍照参数设置兼容问题

    Android 自定义拍照,解决图片旋转,拍照参数设置兼容问题 参考文章: (1)Android 自定义拍照,解决图片旋转,拍照参数设置兼容问题 (2)https://www.cnblogs.com/ ...

  4. Android 自定义View之3D骰子旋转

    你可以指定立方体中每一面骰子的点数,颜色和背景,同时也可以指定执行的动画时间和动画插值器 更多有趣的view 使用 在根目录的build.gradle添加这一句代码: allprojects {rep ...

  5. android 自定义菜单栏,GitHub - earthWo/AndroidBottomNavigation: android 底部菜单栏,自定义样式,自定义菜单数量,添加滚动动画和水波纹动画...

    AndroidBottomNavigation 截图 使用方法 gradle: compile 'com.whitelife.library:library:1.0.1' maven: com.whi ...

  6. android编程绘图,Android编程绘图操作之弧形绘制方法示例

    本文实例讲述了Android编程绘图操作之弧形绘制方法.分享给大家供大家参考,具体如下: /** * 绘制弧形图案 * @description: * @author ldm * @date 2016 ...

  7. android 带弧形背景,Android 弧形ViewPager 和弧形HeaderView(升级版)

    封面.png 前段时间写了一篇项目总结的文章,总结了项目中使用的弧形View 和弧形ViewPager 效果,采用的是自定义View 的方法,然后绘制弧形采用的是二阶贝塞尔曲线,具体的思路和详情请看文 ...

  8. android 画布叠加,Android自定义图形,图形的拼接、叠加、相容

    直接上Xfermode子类: AvoidXfermode  指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图). PixelXorXfermode  当覆盖已有的颜色时,应用一 ...

  9. android+自定义皮肤,android studio自定义更换皮肤详细图文教程

    android studio这款app程序开发软件内也内置了多种皮肤主题,程序开发人员如果感觉一种皮肤太过单调乏味,可以选择使用软件内的其他皮肤风格,软件默认的皮肤是IntelliJ,还有黑色的Dra ...

最新文章

  1. SAP MM 巴西采购订单中的NCM Code
  2. java做的一个将中文转换成Unicode码的工具类【转载】做个标记,明天研究下
  3. LeetCode 120. 三角形最小路径和
  4. python编程实现语音数据分帧及分帧还原
  5. android webview 清空内容,Android WebView清空缓存
  6. STC15W408AS系列管脚说明
  7. 俄罗斯方块c语言程序方案设计,c语言俄罗斯方块游戏程序方案设计书报告.doc
  8. OpenCV-Python实现有参照物条件下的长方形物体尺寸推算(可实时、附源码)
  9. python正则表达式爬取链家租房信息
  10. ABAP BDC代码
  11. 项目介绍之论文格式的自动检测与修改系统
  12. 2020奇安信模拟笔试
  13. Java直接AXIS调用远程WebService
  14. linux 编码文件,linux文件编码
  15. PyCharm + PySide2/PySide6 外部工具配置
  16. AI中怎么给文字加粗
  17. Qt开发技术:QDBus介绍、编译与Demo
  18. AI算法 - 抽样方法
  19. Linux三剑客老三 grep
  20. 几行代码,把zip文件直接破解

热门文章

  1. mysql数据库介绍笔记_MySQL数据库之MySQL读书笔记
  2. Trimble RealWorks处理点云数据基础教程
  3. 【编程马拉松】【004-包含一】
  4. win10怎么添加开机启动项
  5. 6. STM32——用串口发送数据点亮LED(串口的中断接收)
  6. SiT9375:200fs超低抖动差分晶振,25-644.53125MHz,LVPECL/LVDS/HCSL
  7. 利用admixtools进行群体分析
  8. Java解析XML的四种方法
  9. Android 全面屏适配(小米真恶心)
  10. java声明数组的时候,同时赋值