22 |
23 |
24 | # Import
25 |
26 | ### Gradle
27 |
28 | Step 1. Add the JitPack repository to your build file
29 |
30 | allprojects {
31 | repositories {
32 | ...
33 | maven { url "https://jitpack.io" }
34 | }
35 | }
36 |
37 | Step 2. Add the dependency
38 |
39 | dependencies {
40 | compile 'com.github.Hitomis:CircleMenu:v1.1.0'
41 | }
42 |
43 | ### Maven
44 |
45 | Step 1. Add the JitPack repository to your build file
46 |
47 |
35 | * github : https://github.com/Hitomis
36 | *
37 | * email : 196425254@qq.com
38 | */
39 | public class CircleMenu extends View {
40 |
41 | private static final int STATUS_MENU_OPEN = 1;
42 |
43 | private static final int STATUS_MENU_OPENED = 1 << 1;
44 |
45 | private static final int STATUS_MENU_CLOSE = 1 << 2;
46 |
47 | private static final int STATUS_MENU_CLOSE_CLEAR = 1 << 3;
48 |
49 | private static final int STATUS_MENU_CLOSED = 1 << 4;
50 |
51 | private static final int STATUS_MENU_CANCEL = 1 << 5;
52 |
53 | private static final int MAX_SUBMENU_NUM = 8;
54 |
55 | private final int shadowRadius = 5;
56 |
57 | private int partSize;
58 |
59 | private int iconSize;
60 |
61 | private float circleMenuRadius;
62 |
63 | private int itemNum;
64 |
65 | private float itemMenuRadius;
66 |
67 | private float fraction, rFraction;
68 |
69 | private float pathLength;
70 |
71 | private int mainMenuColor;
72 |
73 | private Drawable openMenuIcon, closeMenuIcon;
74 |
75 | private List
496 | * 根据 fraction 调整 color 的 Alpha 值
497 | *
498 | * @param color 被调整 Alpha 值的颜色
499 | * @param reverse true : 由不透明到透明的顺序调整,否则就逆序
500 | * @return
501 | */
502 | private int calcAlphaColor(int color, boolean reverse) {
503 | int alpha;
504 | if (reverse) { // 由不透明到透明
505 | alpha = (int) (255 * (1.f - fraction));
506 | } else { // 由透明到不透明
507 | alpha = (int) (255 * fraction);
508 | }
509 | if (alpha >= 255) alpha = 255;
510 | if (alpha <= 0) alpha = 0;
511 | return ColorUtils.setAlphaComponent(color, alpha);
512 | }
513 |
514 | /**
515 | * 启动打开菜单动画
516 | */
517 | private void startOpenMenuAnima() {
518 | ValueAnimator openAnima = ValueAnimator.ofFloat(1.f, 100.f);
519 | openAnima.setDuration(500);
520 | openAnima.setInterpolator(new OvershootInterpolator());
521 | openAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
522 | @Override
523 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
524 | fraction = valueAnimator.getAnimatedFraction();
525 | itemMenuRadius = fraction * partSize;
526 | itemIconSize = (int) (fraction * iconSize);
527 | invalidate();
528 | }
529 | });
530 | openAnima.addListener(new AnimatorListenerAdapter() {
531 | @Override
532 | public void onAnimationEnd(Animator animation) {
533 | status = STATUS_MENU_OPENED;
534 | if (onMenuStatusChangeListener != null)
535 | onMenuStatusChangeListener.onMenuOpened();
536 | }
537 | });
538 | openAnima.start();
539 | }
540 |
541 | /**
542 | * 启动取消动画
543 | */
544 | private void startCancelMenuAnima() {
545 | ValueAnimator cancelAnima = ValueAnimator.ofFloat(1.f, 100.f);
546 | cancelAnima.setDuration(500);
547 | cancelAnima.setInterpolator(new AnticipateInterpolator());
548 | cancelAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
549 | @Override
550 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
551 | fraction = valueAnimator.getAnimatedFraction();
552 | itemMenuRadius = (1 - fraction) * partSize;
553 | itemIconSize = (int) ((1 - fraction) * iconSize);
554 | invalidate();
555 | }
556 | });
557 | cancelAnima.addListener(new AnimatorListenerAdapter() {
558 | @Override
559 | public void onAnimationEnd(Animator animation) {
560 | status = STATUS_MENU_CLOSED;
561 | if (onMenuStatusChangeListener != null)
562 | onMenuStatusChangeListener.onMenuClosed();
563 | }
564 | });
565 | cancelAnima.start();
566 | }
567 |
568 | /**
569 | * 开启关闭菜单动画
570 | * 关闭菜单动画分为三部分
495 | *
640 | * 中心菜单下标为0,周围菜单从正上方顺时针计数1~5
641 | *
642 | * @param x
643 | * @param y
644 | * @return
645 | */
646 | private int clickWhichRectF(float x, float y) {
647 | int which = -1;
648 | for (RectF rectF : menuRectFList) {
649 | if (rectF.contains(x, y)) {
650 | which = menuRectFList.indexOf(rectF);
651 | break;
652 | }
653 | }
654 | return which;
655 | }
656 |
657 | private Drawable convertDrawable(int iconRes) {
658 | return getResources().getDrawable(iconRes);
659 | }
660 |
661 | private Drawable convertBitmap(Bitmap bitmap) {
662 | return new BitmapDrawable(getResources(), bitmap);
663 | }
664 |
665 | private void resetMainDrawableBounds() {
666 | openMenuIcon.setBounds(centerX - iconSize / 2, centerY - iconSize / 2,
667 | centerX + iconSize / 2, centerY + iconSize / 2);
668 | closeMenuIcon.setBounds(centerX - iconSize / 2, centerY - iconSize / 2,
669 | centerX + iconSize / 2, centerY + iconSize / 2);
670 | }
671 |
672 | /**
673 | * 设置主菜单的背景色,以及打开/关闭的图标
674 | *
675 | * @param mainMenuColor 主菜单背景色
676 | * @param openMenuRes 菜单打开图标,Resource 格式
677 | * @param closeMenuRes 菜单关闭图标,Resource 格式
678 | * @return
679 | */
680 | public CircleMenu setMainMenu(int mainMenuColor, int openMenuRes, int closeMenuRes) {
681 | openMenuIcon = convertDrawable(openMenuRes);
682 | closeMenuIcon = convertDrawable(closeMenuRes);
683 | this.mainMenuColor = mainMenuColor;
684 | return this;
685 | }
686 |
687 | /**
688 | * 设置主菜单的背景色,以及打开/关闭的图标
689 | *
690 | * @param mainMenuColor 主菜单背景色
691 | * @param openMenuBitmap 菜单打开图标,Bitmap 格式
692 | * @param closeMenuBitmap 菜单关闭图标,Bitmap 格式
693 | * @return
694 | */
695 | public CircleMenu setMainMenu(int mainMenuColor, Bitmap openMenuBitmap, Bitmap closeMenuBitmap) {
696 | openMenuIcon = convertBitmap(openMenuBitmap);
697 | closeMenuIcon = convertBitmap(closeMenuBitmap);
698 | this.mainMenuColor = mainMenuColor;
699 | return this;
700 | }
701 |
702 | /**
703 | * 设置主菜单的背景色,以及打开/关闭的图标
704 | *
705 | * @param mainMenuColor 主菜单背景色
706 | * @param openMenuDrawable 菜单打开图标,Drawable 格式
707 | * @param closeMenuDrawable 菜单关闭图标,Drawable 格式
708 | * @return
709 | */
710 | public CircleMenu setMainMenu(int mainMenuColor, Drawable openMenuDrawable, Drawable closeMenuDrawable) {
711 | openMenuIcon = openMenuDrawable;
712 | closeMenuIcon = closeMenuDrawable;
713 | this.mainMenuColor = mainMenuColor;
714 | return this;
715 | }
716 |
717 | /**
718 | * 添加一个子菜单项,包括子菜单的背景色以及图标
719 | *
720 | * @param menuColor 子菜单的背景色
721 | * @param menuRes 子菜单图标,Resource 格式
722 | * @return
723 | */
724 | public CircleMenu addSubMenu(int menuColor, int menuRes) {
725 | if (subMenuColorList.size() < MAX_SUBMENU_NUM && subMenuDrawableList.size() < MAX_SUBMENU_NUM) {
726 | subMenuColorList.add(menuColor);
727 | subMenuDrawableList.add(convertDrawable(menuRes));
728 | itemNum = Math.min(subMenuColorList.size(), subMenuDrawableList.size());
729 | }
730 | return this;
731 | }
732 |
733 | /**
734 | * 添加一个子菜单项,包括子菜单的背景色以及图标
735 | *
736 | * @param menuColor 子菜单的背景色
737 | * @param menuBitmap 子菜单图标,Bitmap 格式
738 | * @return
739 | */
740 | public CircleMenu addSubMenu(int menuColor, Bitmap menuBitmap) {
741 | if (subMenuColorList.size() < MAX_SUBMENU_NUM && subMenuDrawableList.size() < MAX_SUBMENU_NUM) {
742 | subMenuColorList.add(menuColor);
743 | subMenuDrawableList.add(convertBitmap(menuBitmap));
744 | itemNum = Math.min(subMenuColorList.size(), subMenuDrawableList.size());
745 | }
746 | return this;
747 | }
748 |
749 | /**
750 | * 添加一个子菜单项,包括子菜单的背景色以及图标
751 | *
752 | * @param menuColor 子菜单的背景色
753 | * @param menuDrawable 子菜单图标,Drawable 格式
754 | * @return
755 | */
756 | public CircleMenu addSubMenu(int menuColor, Drawable menuDrawable) {
757 | if (subMenuColorList.size() < MAX_SUBMENU_NUM && subMenuDrawableList.size() < MAX_SUBMENU_NUM) {
758 | subMenuColorList.add(menuColor);
759 | subMenuDrawableList.add(menuDrawable);
760 | itemNum = Math.min(subMenuColorList.size(), subMenuDrawableList.size());
761 | }
762 | return this;
763 | }
764 |
765 | /**
766 | * 打开菜单
767 | * Open the CircleMenu
768 | */
769 | public void openMenu() {
770 | if (status == STATUS_MENU_CLOSED) {
771 | status = STATUS_MENU_OPEN;
772 | startOpenMenuAnima();
773 | }
774 | }
775 |
776 | /**
777 | * 关闭菜单
778 | * Close the CircleMenu
779 | */
780 | public void closeMenu() {
781 | if (status == STATUS_MENU_OPENED) {
782 | status = STATUS_MENU_CANCEL;
783 | startCancelMenuAnima();
784 | }
785 | }
786 |
787 | /**
788 | * 菜单是否关闭
789 | * Returns whether the menu is alread open
790 | *
791 | * @return
792 | */
793 | public boolean isOpened() {
794 | return status == STATUS_MENU_OPENED;
795 | }
796 |
797 | public CircleMenu setOnMenuSelectedListener(OnMenuSelectedListener listener) {
798 | this.onMenuSelectedListener = listener;
799 | return this;
800 | }
801 |
802 | public CircleMenu setOnMenuStatusChangeListener(OnMenuStatusChangeListener listener) {
803 | this.onMenuStatusChangeListener = listener;
804 | return this;
805 | }
806 |
807 | private int dip2px(float dpValue) {
808 | final float scale = getContext().getResources().getDisplayMetrics().density;
809 | return (int) (dpValue * scale + 0.5f);
810 | }
811 | }
812 |
--------------------------------------------------------------------------------