├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── README_1.x.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── yanzhikai │ │ └── ymenuview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── yanzhikai │ │ │ └── ymenuview │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable │ │ ├── eight.png │ │ ├── five.png │ │ ├── four.png │ │ ├── one.png │ │ ├── seven.png │ │ ├── six.png │ │ ├── three.png │ │ ├── two.png │ │ └── zero.png │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── yanzhikai │ └── ymenuview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── ymenuview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yanzhikai │ │ └── ymenuview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yanzhikai │ │ │ └── ymenuview │ │ │ ├── Circle8YMenu.java │ │ │ ├── OptionButton.java │ │ │ ├── PositionBuilders │ │ │ ├── MenuPositionBuilder.java │ │ │ ├── OptionPositionBuilder.java │ │ │ └── PositionBuilder.java │ │ │ ├── SquareYMenu.java │ │ │ ├── TreeYMenu.java │ │ │ ├── YMenu.java │ │ │ └── YMenuView.java │ └── res │ │ ├── anim │ │ ├── anim_null.xml │ │ ├── rotate_close.xml │ │ └── rotate_open.xml │ │ ├── drawable │ │ ├── background_option_button.xml │ │ ├── five.png │ │ ├── four.png │ │ ├── null_drawable.xml │ │ ├── one.png │ │ ├── option_button_shape.xml │ │ ├── option_button_shape_press.xml │ │ ├── setting.png │ │ ├── seven.png │ │ ├── six.png │ │ ├── three.png │ │ ├── two.png │ │ └── zero.png │ │ └── values │ │ ├── attr.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── yanzhikai │ └── ymenuview │ └── ExampleUnitTest.java └── 自定义YMenu教程.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YMenuView 2 |   欢迎 Star or Fork!有什么意见和建议可以在Issue上提出。 3 | 4 | ![](https://i.imgur.com/GK8b2P0.gif) 5 | 6 | (前面是之前YMenuView1.x的效果,后面是新加的) 7 | 8 | > 传送门 9 | 10 | > [YMenuView1.x分析](http://blog.csdn.net/totond/article/details/77364137) 11 | 12 | > [YMenuView2.x分析](http://blog.csdn.net/totond/article/details/78059196) 13 | 14 | > [自定义YMenu教程](https://github.com/totond/YMenuView/blob/master/自定义YMenu教程.md) 15 | 16 | ## 简介 17 |   这是一个类似Path菜单的自定义View,这是经过大改版之后的YMenuView2.x,之前[1.x版本的README](https://github.com/totond/YMenuView/blob/master/README_1.x.md)。2.x版本可以自定义MenuButton和OptionButton的布局和显示消失动画,效果可参考上图,下面来开始看看如何自定义炫酷的YMenu吧。 18 | 19 | 20 | ## 使用 21 | 22 | ### Gradle 23 | 24 | ``` 25 | compile 'com.yanzhikaijky:YMenuView:2.0.1' 26 | ``` 27 | 28 | ### 抽象类YMenu 29 |   2.x版本后由之前单一的YMenuView把一些通用逻辑提取出来形成了抽象父类YMenu,开放出4个抽象方法来让用户自定义MenuButton和OptionButton的布局和显示消失动画,而YMenuView的功能则没有变化,增加了3个YMenu的子类,实现了不同的效果。 30 | 31 | ![](https://i.imgur.com/5ze9jNT.png) 32 | 33 | ### 属性 34 | 35 | > 和1.x版本相比,属性名字有所改变 36 | 37 | 38 | |**属性名称**|**意义**|**类型**|**默认值**| 39 | |--|--|:--:|:--:| 40 | |menuButtonWidth | 菜单键宽度 | dimension| 80px| 41 | |menuButtonHeight | 菜单键高度 | dimension| 80px| 42 | |optionButtonWidth | 选项键宽度 | dimension| 80px| 43 | |optionButtonHeight | 选项键高度 | dimension| 80px| 44 | |~~menuButtonRightMargin~~ menuToParentXMargin | 菜单键距离YMenuView边缘距离 | dimension| 50px| 45 | |~~menuButtonBottomMargin~~ menuToParentYMargin | 菜单键距离YMenuView边缘距离 | dimension| 50px| 46 | |~~optionToMenuRightMargin~~ optionToParentXMargin | 选项键距离YMenuView边缘距离 | dimension| 35px| 47 | |~~optionToMenuBottomMargin~~ optionToParentYMargin | 选项键距离YMenuView边缘距离 | dimension| 160px| 48 | |~~optionHorizontalMargin~~ optionXMargin | 选项键之间水平间距 | dimension| 15px| 49 | |~~optionVerticalMargin~~ optionYMargin | 选项键之间垂直间距 | dimension| 15px| 50 | |menuButtonBackGround | 菜单键背景Drawable | reference|@drawable/setting | 51 | |optionsBackGround | 选项键背景Drawable | reference|@drawable/null_drawable| 52 | |optionPositionCounts | “选项”占格个数 | integer| 8| 53 | |optionColumns | “选项”占格列数 | integer| 1| 54 | |isShowMenu | 一开始时是否展开菜单 | boolean| false| 55 | |sd_duration | 进出动画耗时 | integer| 600| 56 | |sd_animMode | 进出动画选择 | enum| FROM_BUTTON_TOP| 57 | 58 | 由于YMenuView2.0重构之后实现了继承YMenu可以实现各种布局,所以下图只是参考YMenu的一个子类——YMenuView的属性,MenuButton和OptionButton都是依靠右下边缘。 59 | 60 | ![](https://i.imgur.com/GIQRp3t.png) 61 | 62 | **对应的sd_animMode模式有(这是YMenuView特有的):** 63 | 64 | |sd_animMode|描述| 65 | |--|--| 66 | |FROM_BUTTON_LEFT|选项从菜单键左边缘飞入| 67 | |FROM_BUTTON_TOP|选项从菜单键上边缘飞入| 68 | |FROM_RIGHT|选项从View左边缘飞入| 69 | |FROM_BOTTOM|选项从View左边缘飞入| 70 | 71 | **除了上面的XML属性的set、get方法,还有下面的一些重要方法:** 72 | 73 | ``` 74 | //清除所有View,用于之后刷新 75 | private void cleanMenu(){ 76 | removeAllViews(); 77 | if (optionButtonList != null) { 78 | optionButtonList.clear(); 79 | } 80 | isShowMenu = false; 81 | if (onPreDrawListener != null) { 82 | getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener); 83 | } 84 | } 85 | 86 | /* 87 | * 对整个YMenuView进行重新初始化,用于在做完一些设定之后刷新 88 | */ 89 | public void refresh(){ 90 | cleanMenu(); 91 | init(mContext); 92 | } 93 | 94 | //获取实际的OptionButton数量 95 | public int getOptionButtonCount() 96 | 97 | //设置MenuButton弹出菜单选项时候MenuButton自身的动画,默认为顺时针旋转180度,为空则是关闭动画 98 | public void setMenuOpenAnimation(Animation menuOpenAnimation) 99 | 100 | //设置MenuButton收回菜单选项时候MenuButton自身的动画,默认为逆时针旋转180度,为空则是关闭动画 101 | public void setMenuCloseAnimation(Animation menuCloseAnimation) 102 | 103 | /* 104 | * 下面的set方法需要在View还没有初始化的时候调用,例如Activity的onCreate方法里 105 | * 如果不在View还没初始化的时候调用,请使用完set这些方法之后调用refresh()方法刷新 106 | */ 107 | 108 | //设置OptionButton的Drawable资源 109 | public void setOptionDrawableIds(int... drawableIds) 110 | 111 | //设置禁止放置选项的位置序号,注意不能输入负数、重复数字或者大于等于optionPositionCounts的数,会报错。 112 | public void setBanArray(int... banArray) 113 | ``` 114 | 115 |   **PS: **banArray里面设置的位置序号就是告诉YMenuView不要在那个位置上放置OptionButton,所以这里有一条规则就是:`optionPositionCounts >= banArray.length + drawableIds.length` 116 | 117 | ### 点击监听 118 |   实现YMenuView.OnOptionsClickListener接口,重写里面的onOptionsClick()方法即可: 119 | 120 | ``` 121 | public class MainActivity extends AppCompatActivity implements YMenuView.OnOptionsClickListener{ 122 | private YMenuView mYMenuView; 123 | 124 | @Override 125 | protected void onCreate(Bundle savedInstanceState) { 126 | super.onCreate(savedInstanceState); 127 | setContentView(R.layout.activity_main); 128 | mYMenuView = (YMenuView) findViewById(R.id.ymv); 129 | mYMenuView.setOnOptionsClickListener(this); 130 | } 131 | 132 | 133 | @Override 134 | public void onOptionsClick(int index) { 135 | switch (index){ 136 | case 0: 137 | makeToast("0"); 138 | break; 139 | case 1: 140 | makeToast("1"); 141 | break; 142 | case 2: 143 | makeToast("2"); 144 | break; 145 | case 3: 146 | makeToast("3"); 147 | break; 148 | case 4: 149 | makeToast("4"); 150 | break; 151 | case 5: 152 | makeToast("5"); 153 | break; 154 | } 155 | } 156 | 157 | 158 | private void makeToast(String str){ 159 | Toast.makeText(this,str,Toast.LENGTH_SHORT).show(); 160 | } 161 | } 162 | ``` 163 | 164 | ### YMenu其他子类 165 | #### Circle8YMenu 166 | 167 | ![](https://i.imgur.com/TKPOKZq.gif) 168 | 169 |   这是一个OptionButton围绕着MenuButton的布局,Option最大数量为8个,MenuButton的位置位于ViewGroup正中间,如果想改变的话可以继承Circle8YMenu,单单重写`setMenuPosition()`方法就可以了,这个是以`optionXMargin`为圆的半径。 170 | 171 | #### TreeYMenu 172 | ![](https://i.imgur.com/niud6YI.gif) 173 | 174 |   这是一个OptionButton分布成分叉树的布局,Option最大数量为9个,MenuButton的位置位于ViewGroup中下方,支持了`menuToParentYMargin`属性。 175 | 176 | #### SquareYMenu 177 | 178 | ![](https://i.imgur.com/ucD4RQv.gif) 179 | 180 |   这是一个OptionButton和MenuButton组成正方形的布局,Option最大数量为8个,MenuButton为位置依靠右下,支持了`menuToParentYMargin`和`menuToParentXMargin`属性。 181 | 182 | ### 自定义YMenu 183 |   这里才是YMenuView2.x的重头戏,但是由于篇幅过长,可以跳转到[自定义YMenu教程](https://github.com/totond/YMenuView/blob/master/自定义YMenu教程.md)查看。 184 | 185 | 186 | ### demo演示 187 | 188 | ### ViewGroup的宽高 189 |   由于YMenuView是用ViewGroup来实现的,所以如果选项OptionButton的位置超出YMenuView的范围的话,会出现不能收回的情况。demo里面使用的是match_parent,实际中使用可以把YMenuView放到其他视图的上层。 190 | 191 | #### YMenu的子类实现演示 192 |   Demo里面的activity_main.xml有效果图上各种效果的YMenu实现定义,查看的时候只要反注释一下,就可以运行了,xml里面的格式如下: 193 | 194 | ``` 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | ... 207 | 208 | 209 | ... 210 | 211 | 212 | ... 213 | 214 | 215 | ... 216 | 217 | 218 | ... 219 | ``` 220 | 221 | 222 | #### Ban列表式 223 |   YMenuView1.x就有有Ban功能,能把一些位置设为不放置OptionButton,那么YMenuView2.0也支持这个,例如: 224 | 225 | ``` 226 | //对Circle8YMenu 227 | mYMenu.setBanArray(0,2,4,6); 228 | ``` 229 | 230 | ![](https://i.imgur.com/PVfbx8D.gif) 231 | 232 | ``` 233 | //对TreeYMenu 234 | mYMenu.setBanArray(2,8,7); 235 | ``` 236 | 237 | ![](https://i.imgur.com/96aVxwk.gif) 238 | 239 | ``` 240 | //对SquareYMenu 241 | mYMenu.setBanArray(3,4,7,5,6); 242 | ``` 243 | 244 | ![](https://i.imgur.com/WnloJhv.gif) 245 | 246 |   但是动画延时还是会算上不被填充的位置,这暂时无法避免,所以想要更好的体验效果的话就继承YMenu重写方法吧。 247 | 248 | ## 更新 249 | 250 | - *version 2.0.0*:2017/09/21 全新版本更新:可以实现自定义YMenu,增加3个YMenu类型,原YMenuView现在也是YMenu的一个子类。 251 | - *version 2.0.1*:2017/09/25 修复`isShowMenu`属性设置无效问题,增加`menu_duration`属性,用于设置MenuButton的动画时长。 252 | 253 | ## 后续 254 |   可能会实现自定义动画,还有给这些OptionButton加上轨迹等。 255 | 256 | ## 开源协议 257 |   YMenuView遵循Apache 2.0开源协议。 258 | 259 | ## 关于作者 260 | > id:炎之铠 261 | 262 | > 炎之铠的邮箱:yanzhikai_yjk@qq.com 263 | 264 | > CSDN:http://blog.csdn.net/totond 265 | 266 | > YMenuView原理分析博客: 267 | > YMenuView1.x分析:http://blog.csdn.net/totond/article/details/77364137 268 | > YMenuView2.x分析:http://blog.csdn.net/totond/article/details/78059196 -------------------------------------------------------------------------------- /README_1.x.md: -------------------------------------------------------------------------------- 1 | # YMenuView 2 |   欢迎 Star or Fork!有什么意见和建议可以在Issue上提出。 3 | 4 | ## 简介 5 |   效果如下图,这是一个类似Path菜单的自定义View,暂时是可以设定4种菜单进出方式,目前只能在右下方展现(因为其他方向代码差不多,重复性的工作太多,暂时没空弄),然后这里有[具体原理分析博客](http://blog.csdn.net/totond/article/details/77364137),方便二次开发。 6 | ![](http://i.imgur.com/gDmX0mM.gif) 7 | 8 | ## 使用 9 | 10 | ### Gradle 11 | 12 | ``` 13 | compile 'com.yanzhikaijky:YMenuView:1.0.3' 14 | ``` 15 | 16 | ### 属性 17 | ![](http://i.imgur.com/9efDIGX.png) 18 | 19 | |**属性名称**|**意义**|**类型**|**默认值**| 20 | |--|--|:--:|:--:| 21 | |menuButtonWidth | 菜单键宽度 | dimension| 80px| 22 | |menuButtonHeight | 菜单键高度 | dimension| 80px| 23 | |optionButtonWidth | 选项键宽度 | dimension| 80px| 24 | |optionButtonHeight | 选项键高度 | dimension| 80px| 25 | |menuButtonRightMargin | 菜单键距离View右边缘距离 | dimension| 50px| 26 | |menuButtonBottomMargin | 菜单键距离View下边缘距离 | dimension| 50px| 27 | |optionToMenuRightMargin | 选项键距离View右边缘距离 | dimension| 35px| 28 | |optionToMenuBottomMargin | 选项键距离View下边缘距离 | dimension| 160px| 29 | |optionHorizontalMargin | 选项键之间水平间距 | dimension| 15px| 30 | |optionVerticalMargin | 选项键之间垂直间距 | dimension| 15px| 31 | |menuButtonBackGround | 菜单键背景Drawable | reference|@drawable/setting | 32 | |optionsBackGround | 选项键背景Drawable | reference|@drawable/null_drawable| 33 | |optionPositionCounts | “选项”占格个数 | integer| 8| 34 | |optionColumns | “选项”占格列数 | integer| 1| 35 | |isShowMenu | 一开始时是否展开菜单 | boolean| false| 36 | |sd_duration | 进出动画耗时 | integer| 600| 37 | |sd_animMode | 进出动画选择 | enum| FROM_BUTTON_TOP| 38 | 39 | **对应的sd_animMode模式有:** 40 | 41 | |sd_animMode|描述| 42 | |--|--| 43 | |FROM_BUTTON_LEFT|选项从菜单键左边缘飞入| 44 | |FROM_BUTTON_TOP|选项从菜单键上边缘飞入| 45 | |FROM_RIGHT|选项从View左边缘飞入| 46 | |FROM_BOTTOM|选项从View左边缘飞入| 47 | 48 | **除了上面的XML属性的set、get方法,还有下面的一些重要方法:** 49 | 50 | ``` 51 | //清除所有View,用于之后刷新 52 | private void cleanMenu(){ 53 | removeAllViews(); 54 | if (optionButtonList != null) { 55 | optionButtonList.clear(); 56 | } 57 | isShowMenu = false; 58 | if (onPreDrawListener != null) { 59 | getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener); 60 | } 61 | } 62 | 63 | /* 64 | * 对整个YMenuView进行重新初始化,用于在做完一些设定之后刷新 65 | */ 66 | public void refresh(){ 67 | cleanMenu(); 68 | init(mContext); 69 | } 70 | 71 | 72 | /* 73 | * 下面的set方法需要在View还没有初始化的时候调用,例如Activity的onCreate方法里 74 | * 如果不在View还没初始化的时候调用,请使用完set这些方法之后调用refresh()方法刷新 75 | */ 76 | 77 | //设置OptionButton的Drawable资源 78 | public void setOptionDrawableIds(int... drawableIds) { 79 | this.drawableIds = drawableIds; 80 | } 81 | 82 | //设置MenuButton弹出菜单选项时候MenuButton自身的动画,默认为顺时针旋转180度,为空则是关闭动画 83 | public void setMenuOpenAnimation(Animation menuOpenAnimation) { 84 | menuOpenAnimation.setAnimationListener(animationListener); 85 | this.menuOpenAnimation = menuOpenAnimation; 86 | 87 | } 88 | 89 | //设置MenuButton收回菜单选项时候MenuButton自身的动画,默认为逆时针旋转180度,为空则是关闭动画 90 | public void setMenuCloseAnimation(Animation menuCloseAnimation) { 91 | menuCloseAnimation.setAnimationListener(animationListener); 92 | this.menuCloseAnimation = menuCloseAnimation; 93 | } 94 | 95 | //设置禁止放置选项的位置序号,注意不能输入负数、重复数字或者大于等于optionPositionCounts的数,会报错。 96 | public void setBanArray(int... banArray) { 97 | this.banArray = banArray; 98 | } 99 | ``` 100 | 101 |   **PS: **banArray里面设置的位置序号就是告诉YMenuView不要在那个位置上放置OptionButton,所以这里有一条规则就是:`optionPositionCounts >= banArray.length + drawableIds.length` 102 | 103 | ### 点击监听 104 |   实现YMenuView.OnOptionsClickListener接口,重写里面的onOptionsClick()方法即可: 105 | 106 | ``` 107 | public class MainActivity extends AppCompatActivity implements YMenuView.OnOptionsClickListener{ 108 | private YMenuView mYMenuView; 109 | 110 | @Override 111 | protected void onCreate(Bundle savedInstanceState) { 112 | super.onCreate(savedInstanceState); 113 | setContentView(R.layout.activity_main); 114 | mYMenuView = (YMenuView) findViewById(R.id.ymv); 115 | mYMenuView.setOnOptionsClickListener(this); 116 | } 117 | 118 | 119 | @Override 120 | public void onOptionsClick(int index) { 121 | switch (index){ 122 | case 0: 123 | makeToast("0"); 124 | 125 | break; 126 | case 1: 127 | makeToast("1"); 128 | break; 129 | case 2: 130 | makeToast("2"); 131 | break; 132 | case 3: 133 | makeToast("3"); 134 | break; 135 | case 4: 136 | makeToast("4"); 137 | break; 138 | case 5: 139 | makeToast("5"); 140 | break; 141 | } 142 | } 143 | 144 | 145 | private void makeToast(String str){ 146 | Toast.makeText(this,str,Toast.LENGTH_SHORT).show(); 147 | } 148 | } 149 | ``` 150 | 151 | 152 | ### demo演示 153 | ###ViewGroup的宽高 154 |   由于YMenuView是用ViewGroup来实现的,所以如果选项OptionButton的位置超出YMenuView的范围的话,会出现不能收回的情况。demo里面使用的是match_parent,实际中使用可以把YMenuView放到其他视图的上层。 155 | 156 | #### 竖放式 157 | **XML:** 158 | 159 | ``` 160 | 167 | ``` 168 | **效果:** 169 | ![](http://i.imgur.com/ns7fcnE.gif) 170 | 171 | #### 横放式 172 | **XML:** 173 | 174 | ``` 175 | 186 | ``` 187 | **效果:** 188 | ![](http://i.imgur.com/4gSGuyi.gif) 189 | 190 | #### Ban列表式 191 | **XML:** 192 | 193 | ``` 194 | 204 | ``` 205 | 206 | **Java代码:** 207 | 208 | ``` 209 | mYMenuView = (YMenuView) findViewById(R.id.ymv); 210 | mYMenuView.setBanArray(0,2,6); 211 | ``` 212 | 213 | **效果:** 214 | ![](http://i.imgur.com/V9wTXJz.gif) 215 |   可以看出0、2、6号位都没有放置OptionButton。 216 | 217 | ## 后续 218 |   可能会考虑有空把它做成多个位置的,不只是右下,还有加入更多的进出动画,目前正在写源码解析的博客。 219 | 220 | ## 开源协议 221 |   YMenuView遵循Apache 2.0开源协议。 222 | 223 | ## 关于作者 224 | > id:炎之铠 225 | 226 | > 炎之铠的邮箱:yanzhikai_yjk@qq.com 227 | 228 | > CSDN:http://blog.csdn.net/totond 229 | 230 | > YMenuView原理分析博客:http://blog.csdn.net/totond/article/details/77364137 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "yanzhikai.ymenuview" 8 | minSdkVersion 17 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.3.1' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testCompile 'junit:junit:4.12' 30 | compile project(':ymenuview') 31 | } 32 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\tools\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/yanzhikai/ymenuview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("yanzhikai.ymenuview", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/yanzhikai/ymenuview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package yanzhikai.ymenuview; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.widget.Toast; 6 | 7 | import com.yanzhikai.ymenuview.YMenu; 8 | 9 | public class MainActivity extends AppCompatActivity implements YMenu.OnOptionsClickListener{ 10 | private YMenu mYMenu; 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | mYMenu = (YMenu) findViewById(R.id.ymv); 17 | mYMenu.setOnOptionsClickListener(this); 18 | // mYMenu.setBanArray(3,4,7,5,6); 19 | mYMenu.setOptionDrawableIds(R.drawable.zero,R.drawable.one,R.drawable.two 20 | ,R.drawable.three,R.drawable.four,R.drawable.five,R.drawable.six 21 | ,R.drawable.seven,R.drawable.eight); 22 | // mYMenu.setMenuOpenAnimation(null); 23 | // mYMenu.setMenuCloseAnimation(null); 24 | } 25 | 26 | 27 | @Override 28 | public void onOptionsClick(int index) { 29 | switch (index){ 30 | case 0: 31 | makeToast("0"); 32 | break; 33 | case 1: 34 | makeToast("1"); 35 | break; 36 | case 2: 37 | makeToast("2"); 38 | break; 39 | case 3: 40 | makeToast("3"); 41 | break; 42 | case 4: 43 | makeToast("4"); 44 | break; 45 | case 5: 46 | makeToast("5"); 47 | break; 48 | case 6: 49 | makeToast("6"); 50 | break; 51 | case 7: 52 | makeToast("7"); 53 | break; 54 | case 8: 55 | makeToast("8"); 56 | break; 57 | 58 | } 59 | } 60 | 61 | 62 | private void makeToast(String str){ 63 | Toast.makeText(this,str,Toast.LENGTH_SHORT).show(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/eight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/eight.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/five.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/four.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/one.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/seven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/seven.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/six.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/six.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/three.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/two.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/zero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/drawable/zero.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | YMenuView 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/yanzhikai/ymenuview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package yanzhikai.ymenuview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.2' 9 | classpath 'com.novoda:bintray-release:0.3.4' 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | //防止中文注释出错 20 | tasks.withType(Javadoc) { 21 | options { 22 | encoding "UTF-8" 23 | charSet 'UTF-8' 24 | links "http://docs.oracle.com/javase/7/docs/api" 25 | } 26 | } 27 | 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 04 17:22:15 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':ymenuview' 2 | -------------------------------------------------------------------------------- /ymenuview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ymenuview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release'//添加 3 | 4 | 5 | android { 6 | compileSdkVersion 25 7 | buildToolsVersion "25.0.2" 8 | 9 | defaultConfig { 10 | minSdkVersion 17 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | encoding "UTF-8" 26 | } 27 | } 28 | 29 | dependencies { 30 | compile fileTree(dir: 'libs', include: ['*.jar']) 31 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 32 | exclude group: 'com.android.support', module: 'support-annotations' 33 | }) 34 | compile 'com.android.support:appcompat-v7:25.3.1' 35 | testCompile 'junit:junit:4.12' 36 | } 37 | 38 | publish { 39 | userOrg = 'yanzhikaijky' 40 | repoName = 'CustomViewRepository' 41 | groupId = 'com.yanzhikaijky' 42 | artifactId = 'YMenuView' 43 | publishVersion = '2.0.1' 44 | desc = 'a menuView similar to PathMenu' 45 | website = 'https://github.com/totond/YMenuView' 46 | } 47 | -------------------------------------------------------------------------------- /ymenuview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\tools\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /ymenuview/src/androidTest/java/com/yanzhikai/ymenuview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.yanzhikai.ymenuview.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ymenuview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/Circle8YMenu.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.view.animation.AlphaAnimation; 8 | import android.view.animation.Animation; 9 | import android.view.animation.AnimationSet; 10 | import android.view.animation.TranslateAnimation; 11 | 12 | import com.yanzhikai.ymenuview.PositionBuilders.MenuPositionBuilder; 13 | import com.yanzhikai.ymenuview.PositionBuilders.OptionPositionBuilder; 14 | import com.yanzhikai.ymenuview.PositionBuilders.PositionBuilder; 15 | 16 | /** 17 | * OptionButton围绕着MenuButton的布局,Option最大数量为8个 18 | */ 19 | 20 | public class Circle8YMenu extends YMenu{ 21 | //8个Option位置的x、y乘积因子 22 | private float[] xyTimes = {0,0.707f,1,0.707f,0,-0.707f,-1,-0.707f}; 23 | 24 | public Circle8YMenu(Context context) { 25 | super(context); 26 | } 27 | 28 | public Circle8YMenu(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | } 31 | 32 | public Circle8YMenu(Context context, AttributeSet attrs, int defStyleAttr) { 33 | super(context, attrs, defStyleAttr); 34 | } 35 | 36 | //设置MenuButton的位置,这里设置成位于ViewGroup中心 37 | @Override 38 | public void setMenuPosition(View menuButton) { 39 | new MenuPositionBuilder(menuButton) 40 | .setWidthAndHeight(getYMenuButtonWidth(),getYMenuButtonHeight()) 41 | .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM) 42 | .setIsXYCenter(true,true) 43 | .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin()) 44 | .finish(); 45 | } 46 | 47 | //设置OptionButton的位置,这里是设置成圆形围绕着MenuButton 48 | @Override 49 | public void setOptionPosition(OptionButton optionButton, View menuButton, int index) { 50 | if (index >= 8){ 51 | try { 52 | throw new Exception("Circle8YMenuView的OptionPosition最大数量为8,超过将会发生错误"); 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | 58 | 59 | int centerX = menuButton.getLeft() + menuButton.getWidth()/2; 60 | int centerY = menuButton.getTop() + menuButton.getHeight()/2; 61 | int halfOptionWidth = getYOptionButtonWidth()/2; 62 | int halfOptionHeight = getYOptionButtonHeight()/2; 63 | //利用乘积因子来决定不同位置 64 | float x = xyTimes[index % 8]; 65 | float y = xyTimes[(index + 6) % 8]; 66 | 67 | OptionPositionBuilder OptionPositionBuilder = new OptionPositionBuilder(optionButton,menuButton); 68 | OptionPositionBuilder 69 | .isAlignMenuButton(false,false) 70 | .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight()) 71 | .setMarginOrientation(PositionBuilder.MARGIN_LEFT,PositionBuilder.MARGIN_TOP) 72 | //计算OptionButton的位置 73 | .setXYMargin( 74 | (int)(centerX + x * getYOptionXMargin() - halfOptionWidth) 75 | ,(int)(centerY + y * getYOptionXMargin() - halfOptionHeight) 76 | ) 77 | .finish(); 78 | } 79 | 80 | //设置OptionButton的显示动画 81 | @Override 82 | public Animation createOptionShowAnimation(OptionButton optionButton, int index) { 83 | AnimationSet animationSet = new AnimationSet(true); 84 | TranslateAnimation translateAnimation= new TranslateAnimation( 85 | getYMenuButton().getX() - optionButton.getX() 86 | ,0 87 | ,getYMenuButton().getY() - optionButton.getY() 88 | ,0); 89 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 90 | AlphaAnimation alphaAnimation = new AlphaAnimation(0,1); 91 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 92 | animationSet.addAnimation(alphaAnimation); 93 | animationSet.addAnimation(translateAnimation); 94 | //为不同的Option设置延时 95 | if (index % 2 == 1) { 96 | animationSet.setStartOffset(getOptionSD_AnimationDuration()/2); 97 | } 98 | return animationSet; 99 | } 100 | 101 | //设置OptionButton的消失动画 102 | @Override 103 | public Animation createOptionDisappearAnimation(OptionButton optionButton, int index) { 104 | AnimationSet animationSet = new AnimationSet(true); 105 | TranslateAnimation translateAnimation= new TranslateAnimation( 106 | 0 107 | ,getYMenuButton().getX() - optionButton.getX() 108 | ,0 109 | ,getYMenuButton().getY() - optionButton.getY() 110 | ); 111 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 112 | AlphaAnimation alphaAnimation = new AlphaAnimation(1,0); 113 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 114 | animationSet.addAnimation(translateAnimation); 115 | animationSet.addAnimation(alphaAnimation); 116 | //为不同的Option设置延时 117 | if (index % 2 == 0) { 118 | animationSet.setStartOffset(getOptionSD_AnimationDuration()/2); 119 | } 120 | return animationSet; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/OptionButton.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.IntDef; 5 | import android.util.AttributeSet; 6 | import android.view.ViewTreeObserver; 7 | import android.view.animation.Animation; 8 | 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | 12 | /** 13 | * @author yanzhikai 14 | * Description: 这个是菜单弹出的选项按钮 15 | */ 16 | 17 | public class OptionButton extends android.support.v7.widget.AppCompatImageView { 18 | private Animation showAnimation,disappearAnimation; 19 | public static final int FROM_BUTTON_LEFT = 0 , FROM_BUTTON_TOP = 1,FROM_RIGHT = 2,FROM_BOTTOM = 3; 20 | private @SD_Animation int mSD_Animation = FROM_BUTTON_LEFT; 21 | private int mDuration = 600; 22 | private int mIndex; 23 | private OptionPrepareListener mOptionPrepareListener; 24 | 25 | 26 | @IntDef({FROM_BUTTON_LEFT, FROM_BUTTON_TOP,FROM_RIGHT,FROM_BOTTOM}) 27 | @Retention(RetentionPolicy.SOURCE) 28 | public @interface SD_Animation {} 29 | 30 | public OptionButton(Context context, int index) { 31 | super(context); 32 | mIndex = index; 33 | init(); 34 | } 35 | 36 | public OptionButton(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | init(); 39 | } 40 | 41 | public OptionButton(Context context, AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | init(); 44 | } 45 | 46 | private void init(){ 47 | setClickable(true); 48 | 49 | //在获取到宽高参数之后再进行初始化 50 | ViewTreeObserver viewTreeObserver = getViewTreeObserver(); 51 | viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 52 | @Override 53 | public void onGlobalLayout() { 54 | if (getX() != 0 && getY() != 0 && getWidth() != 0 && getHeight() != 0) { 55 | if (mOptionPrepareListener != null){ 56 | mOptionPrepareListener.onOptionPrepare(OptionButton.this,mIndex); 57 | } 58 | // setShowAndDisappear(); 59 | 60 | //在这里才设置Gone很重要,让View可以一开始就触发onGlobalLayout()进行初始化 61 | // setVisibility(GONE); 62 | //设置完后立刻注销,不然会不断回调,浪费很多资源 63 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 64 | } 65 | 66 | } 67 | }); 68 | 69 | } 70 | 71 | @Override 72 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 73 | 74 | super.onLayout(changed, left, top, right, bottom); 75 | } 76 | 77 | /* 78 | * 此方法需要在View初始化之前使用 79 | */ 80 | public void setSD_Animation(@SD_Animation int sd_animation){ 81 | mSD_Animation = sd_animation; 82 | } 83 | 84 | /* 85 | * 此方法需要在View初始化之前使用 86 | */ 87 | public void setDuration(int duration) { 88 | this.mDuration = duration; 89 | } 90 | 91 | private void setShowAndDisappear() { 92 | // setShowAnimation(mDuration); 93 | // setDisappearAnimation(mDuration); 94 | //在这里才设置Gone很重要,让View可以一开始就触发onGlobalLayout()进行初始化 95 | setVisibility(GONE); 96 | } 97 | 98 | public void setShowAnimation(Animation showAnimation) { 99 | this.showAnimation = showAnimation; 100 | } 101 | 102 | public void setDisappearAnimation(Animation disappearAnimation) { 103 | this.disappearAnimation = disappearAnimation; 104 | disappearAnimation.setAnimationListener(new Animation.AnimationListener() { 105 | @Override 106 | public void onAnimationStart(Animation animation) { 107 | } 108 | 109 | @Override 110 | public void onAnimationEnd(Animation animation) { 111 | setVisibility(GONE); 112 | } 113 | 114 | @Override 115 | public void onAnimationRepeat(Animation animation) { 116 | 117 | } 118 | }); 119 | } 120 | 121 | 122 | 123 | 124 | public void setOptionPrepareListener(OptionPrepareListener optionPrepareListener) { 125 | this.mOptionPrepareListener = optionPrepareListener; 126 | } 127 | 128 | public int getmSD_Animation() { 129 | return mSD_Animation; 130 | } 131 | 132 | public Animation getShowAnimation() { 133 | return showAnimation; 134 | } 135 | 136 | public Animation getDisappearAnimation() { 137 | return disappearAnimation; 138 | } 139 | 140 | public void onShow() { 141 | setVisibility(VISIBLE); 142 | if (showAnimation != null) { 143 | startAnimation(showAnimation); 144 | } 145 | } 146 | 147 | public void onClose() { 148 | if (disappearAnimation != null) { 149 | startAnimation(disappearAnimation); 150 | } 151 | } 152 | 153 | public void onDisappear(){ 154 | setVisibility(GONE); 155 | } 156 | 157 | public interface OptionPrepareListener{ 158 | void onOptionPrepare(OptionButton optionButton, int index); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/PositionBuilders/MenuPositionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview.PositionBuilders; 2 | 3 | import android.util.Log; 4 | import android.view.View; 5 | import android.widget.RelativeLayout; 6 | 7 | import static android.widget.RelativeLayout.ABOVE; 8 | import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM; 9 | import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT; 10 | import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT; 11 | import static android.widget.RelativeLayout.ALIGN_PARENT_TOP; 12 | import static android.widget.RelativeLayout.BELOW; 13 | import static android.widget.RelativeLayout.CENTER_HORIZONTAL; 14 | import static android.widget.RelativeLayout.CENTER_VERTICAL; 15 | import static android.widget.RelativeLayout.LEFT_OF; 16 | import static android.widget.RelativeLayout.RIGHT_OF; 17 | 18 | /** 19 | * MenuButton位置信息的Builder类, 20 | * 具体功能就是运用建造者模式,传入一个OptionButton对象,按需求配置出一个LayoutParams来缺点OptionButton的位置 21 | */ 22 | 23 | public class MenuPositionBuilder extends PositionBuilder { 24 | private View mMenuButton; 25 | private int mWidth = 0, mHeight = 0; 26 | private int mXMargin = 0, mYMargin = 0; 27 | private @PositionBuilder.MarginOrientationX int mMarginOrientationX = MARGIN_LEFT; 28 | private @PositionBuilder.MarginOrientationY int mMarginOrientationY = MARGIN_TOP; 29 | private boolean mIsXCenter = false, mIsYCenter = false; 30 | 31 | public MenuPositionBuilder(View menuButton) { 32 | mMenuButton = menuButton; 33 | } 34 | 35 | @Override 36 | public MenuPositionBuilder setWidthAndHeight(int width, int height) { 37 | mWidth = width; 38 | mHeight = height; 39 | return this; 40 | } 41 | 42 | @Override 43 | public MenuPositionBuilder setXYMargin(int XMargin, int YMargin) { 44 | mXMargin = XMargin; 45 | mYMargin = YMargin; 46 | return this; 47 | } 48 | 49 | @Override 50 | public MenuPositionBuilder setMarginOrientation(@MarginOrientationX int marginOrientationX, @MarginOrientationY int marginOrientationY) { 51 | mMarginOrientationX = marginOrientationX; 52 | mMarginOrientationY = marginOrientationY; 53 | return this; 54 | } 55 | 56 | //设置是否在XY方向处于中心,这个优先于setXYMargin()方法和setMarginOrientation()方法 57 | public MenuPositionBuilder setIsXYCenter(boolean isXCenter, boolean isYCenter) { 58 | mIsXCenter = isXCenter; 59 | mIsYCenter = isYCenter; 60 | return this; 61 | } 62 | 63 | //进行最后的配置操作,就是把前面配置的属性加入到LayoutParams里面,然后绑定MenuButton。 64 | @Override 65 | public void finish() { 66 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(mWidth, mHeight); 67 | 68 | if (mIsXCenter){ 69 | //设置为中心的优先 70 | layoutParams.addRule(CENTER_HORIZONTAL); 71 | }else { 72 | if (mMarginOrientationX == MARGIN_LEFT) { 73 | layoutParams.leftMargin = mXMargin; 74 | layoutParams.addRule(ALIGN_PARENT_LEFT); 75 | } else { 76 | layoutParams.rightMargin = mXMargin; 77 | layoutParams.addRule(ALIGN_PARENT_RIGHT); 78 | } 79 | } 80 | 81 | if (mIsYCenter){ 82 | layoutParams.addRule(CENTER_VERTICAL); 83 | }else { 84 | if (mMarginOrientationY == MARGIN_TOP) { 85 | layoutParams.topMargin = mYMargin; 86 | layoutParams.addRule(ALIGN_PARENT_TOP); 87 | 88 | } else { 89 | layoutParams.bottomMargin = mYMargin; 90 | layoutParams.addRule(ALIGN_PARENT_BOTTOM); 91 | } 92 | } 93 | 94 | mMenuButton.setLayoutParams(layoutParams); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/PositionBuilders/OptionPositionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview.PositionBuilders; 2 | 3 | import android.view.View; 4 | import android.widget.RelativeLayout; 5 | 6 | import com.yanzhikai.ymenuview.OptionButton; 7 | 8 | import static android.widget.RelativeLayout.ABOVE; 9 | import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM; 10 | import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT; 11 | import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT; 12 | import static android.widget.RelativeLayout.ALIGN_PARENT_TOP; 13 | import static android.widget.RelativeLayout.BELOW; 14 | import static android.widget.RelativeLayout.LEFT_OF; 15 | import static android.widget.RelativeLayout.RIGHT_OF; 16 | 17 | /** 18 | * OptionButton位置信息的Builder类, 19 | * 具体功能就是运用建造者模式,传入一个OptionButton对象,按需求配置出一个LayoutParams来缺点OptionButton的位置 20 | */ 21 | 22 | public class OptionPositionBuilder extends PositionBuilder { 23 | private OptionButton mOptionButton; 24 | private View mMenuButton; 25 | private int mWidth = 0, mHeight = 0; 26 | private int mXMargin = 0, mYMargin = 0; 27 | private @PositionBuilder.MarginOrientationX int mMarginOrientationX = MARGIN_LEFT; 28 | private @PositionBuilder.MarginOrientationY int mMarginOrientationY = MARGIN_TOP; 29 | private boolean mIsAlignMenuButtonX = false,mIsAlignMenuButtonY = false; 30 | 31 | public OptionPositionBuilder(OptionButton optionButton, View menuButton) { 32 | mOptionButton = optionButton; 33 | mMenuButton = menuButton; 34 | } 35 | 36 | @Override 37 | public OptionPositionBuilder setWidthAndHeight(int width,int height){ 38 | mWidth = width; 39 | mHeight = height; 40 | return this; 41 | } 42 | 43 | @Override 44 | public OptionPositionBuilder setXYMargin(int XMargin, int YMargin) { 45 | mXMargin = XMargin; 46 | mYMargin = YMargin; 47 | return this; 48 | } 49 | 50 | @Override 51 | public OptionPositionBuilder setMarginOrientation(@MarginOrientationX int marginOrientationX, @MarginOrientationY int marginOrientationY) { 52 | mMarginOrientationX = marginOrientationX; 53 | mMarginOrientationY = marginOrientationY; 54 | return this; 55 | } 56 | 57 | //设置是否以MenuButton为参考 58 | public OptionPositionBuilder isAlignMenuButton(boolean isAlignMenuButtonX,boolean isAlignMenuButtonY){ 59 | mIsAlignMenuButtonX = isAlignMenuButtonX; 60 | mIsAlignMenuButtonY = isAlignMenuButtonY; 61 | return this; 62 | } 63 | 64 | //进行最后的配置操作,就是把前面配置的属性加入到LayoutParams里面,然后绑定OptionButton。 65 | @Override 66 | public void finish(){ 67 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(mWidth,mHeight); 68 | 69 | if (mMarginOrientationX == MARGIN_LEFT){ 70 | layoutParams.leftMargin = mXMargin; 71 | if (mIsAlignMenuButtonX){ 72 | layoutParams.addRule(RIGHT_OF,mMenuButton.getId()); 73 | }else { 74 | layoutParams.addRule(ALIGN_PARENT_LEFT); 75 | } 76 | }else { 77 | layoutParams.rightMargin = mXMargin; 78 | if (mIsAlignMenuButtonX){ 79 | layoutParams.addRule(LEFT_OF,mMenuButton.getId()); 80 | }else { 81 | layoutParams.addRule(ALIGN_PARENT_RIGHT); 82 | } 83 | } 84 | if (mMarginOrientationY == MARGIN_TOP){ 85 | layoutParams.topMargin = mYMargin; 86 | if (mIsAlignMenuButtonY){ 87 | layoutParams.addRule(BELOW,mMenuButton.getId()); 88 | }else { 89 | layoutParams.addRule(ALIGN_PARENT_TOP); 90 | } 91 | }else { 92 | layoutParams.bottomMargin = mYMargin; 93 | if (mIsAlignMenuButtonY){ 94 | layoutParams.addRule(ABOVE,mMenuButton.getId()); 95 | }else { 96 | layoutParams.addRule(ALIGN_PARENT_BOTTOM); 97 | } 98 | } 99 | mOptionButton.setLayoutParams(layoutParams); 100 | } 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/PositionBuilders/PositionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview.PositionBuilders; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | 9 | /** 10 | * 用于建立MenuButton和OptionButton位置信息的Builder抽象类 11 | */ 12 | 13 | public abstract class PositionBuilder { 14 | //以某对象的边缘做为参考物 15 | public static final int MARGIN_LEFT = 0, MARGIN_TOP = 1, MARGIN_RIGHT = 2, MARGIN_BOTTOM = 3; 16 | 17 | @IntDef({MARGIN_LEFT,MARGIN_RIGHT}) 18 | @Retention(RetentionPolicy.SOURCE) 19 | public @interface MarginOrientationX {} 20 | 21 | @IntDef({MARGIN_TOP,MARGIN_BOTTOM}) 22 | @Retention(RetentionPolicy.SOURCE) 23 | public @interface MarginOrientationY {} 24 | 25 | //设置宽高 26 | public abstract PositionBuilder setWidthAndHeight(int width,int height); 27 | 28 | //设置XY距离 29 | public abstract PositionBuilder setXYMargin(int XMargin,int YMargin); 30 | 31 | //设置XY方向的参考,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离 32 | public abstract PositionBuilder setMarginOrientation(@MarginOrientationX int marginOrientationX,@MarginOrientationY int marginOrientationY); 33 | 34 | //进行最后的配置操作 35 | public abstract void finish(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/SquareYMenu.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.view.animation.AlphaAnimation; 8 | import android.view.animation.Animation; 9 | import android.view.animation.AnimationSet; 10 | import android.view.animation.LinearInterpolator; 11 | import android.view.animation.TranslateAnimation; 12 | 13 | import com.yanzhikai.ymenuview.PositionBuilders.MenuPositionBuilder; 14 | import com.yanzhikai.ymenuview.PositionBuilders.OptionPositionBuilder; 15 | import com.yanzhikai.ymenuview.PositionBuilders.PositionBuilder; 16 | 17 | /** 18 | * OptionButton和MenuButton组成一个正方形的布局,Option最大数量为8个 19 | */ 20 | 21 | public class SquareYMenu extends YMenu { 22 | //8个Option位置的x、y乘积因子 23 | private static final int[] xTimes = {-1,-1,0,-2,-2,-1,-2,0}; 24 | private static final int[] yTimes = {-1,0,-1,-2,-1,-2,0,-2}; 25 | 26 | public SquareYMenu(Context context) { 27 | super(context); 28 | } 29 | 30 | public SquareYMenu(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | } 33 | 34 | public SquareYMenu(Context context, AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | } 37 | 38 | //设置MenuButton的位置,这里是和默认一样放在右下 39 | @Override 40 | public void setMenuPosition(View menuButton) { 41 | new MenuPositionBuilder(menuButton) 42 | .setWidthAndHeight(getYMenuButtonWidth(), getYMenuButtonHeight()) 43 | .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM) 44 | .setIsXYCenter(false,false) 45 | .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin()) 46 | .finish(); 47 | } 48 | 49 | //设置OptionButton的位置,这里是8个Option和MenuButton一起组成9宫格布局 50 | @Override 51 | public void setOptionPosition(OptionButton optionButton, View menuButton, int index) { 52 | if (index > 7){ 53 | try { 54 | throw new Exception("SquareYMenuView的OptionPosition最大数量为8,超过将会发生错误"); 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | 60 | int centerX = menuButton.getLeft() + menuButton.getWidth()/2; 61 | int centerY = menuButton.getTop() + menuButton.getHeight()/2; 62 | int halfOptionWidth = getYOptionButtonWidth()/2; 63 | int halfOptionHeight = getYOptionButtonHeight()/2; 64 | 65 | int x = xTimes[index]; 66 | int y = yTimes[index]; 67 | 68 | OptionPositionBuilder OptionPositionBuilder = new OptionPositionBuilder(optionButton,menuButton); 69 | OptionPositionBuilder 70 | .isAlignMenuButton(false,false)//是否以MenuButton为参考 71 | .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight()) 72 | .setMarginOrientation(PositionBuilder.MARGIN_LEFT,PositionBuilder.MARGIN_TOP) 73 | .setXYMargin( 74 | centerX + x * getYOptionXMargin() - halfOptionWidth 75 | ,centerY + y * getYOptionYMargin() - halfOptionHeight 76 | ) 77 | .finish(); 78 | } 79 | 80 | //设置OptionButton的显示动画,先出现三个,然后后面的在这三个位置上出现 81 | @Override 82 | public Animation createOptionShowAnimation(OptionButton optionButton, int index) { 83 | float fromX,fromY; 84 | AnimationSet animationSet = new AnimationSet(true); 85 | if (index < 3){ 86 | fromX = getYMenuButton().getX() - optionButton.getX(); 87 | fromY = getYMenuButton().getY() - optionButton.getY(); 88 | }else if (index < 6){ 89 | fromX = getOptionButtonList().get(0).getX() - optionButton.getX(); 90 | fromY = getOptionButtonList().get(0).getY() - optionButton.getY(); 91 | animationSet.setStartOffset(getOptionSD_AnimationDuration() ); 92 | }else { 93 | int oldIndex = index % 5; 94 | fromX = getOptionButtonList().get(oldIndex).getX() - optionButton.getX(); 95 | fromY = getOptionButtonList().get(oldIndex).getY() - optionButton.getY(); 96 | animationSet.setStartOffset(getOptionSD_AnimationDuration() ); 97 | } 98 | 99 | TranslateAnimation translateAnimation= new TranslateAnimation( 100 | fromX 101 | ,0 102 | ,fromY 103 | ,0); 104 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 105 | AlphaAnimation alphaAnimation = new AlphaAnimation(0,1); 106 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 107 | animationSet.addAnimation(alphaAnimation); 108 | animationSet.addAnimation(translateAnimation); 109 | animationSet.setInterpolator(new LinearInterpolator()); 110 | return animationSet; 111 | 112 | } 113 | 114 | //设置OptionButton的消失动画 115 | @Override 116 | public Animation createOptionDisappearAnimation(OptionButton optionButton, int index) { 117 | float toX,toY; 118 | AnimationSet animationSet = new AnimationSet(true); 119 | if (index < 3){ 120 | toX = getYMenuButton().getX() - optionButton.getX(); 121 | toY = getYMenuButton().getY() - optionButton.getY(); 122 | if (getOptionButtonCount() > 3) { 123 | animationSet.setStartOffset(getOptionSD_AnimationDuration()); 124 | } 125 | }else if (index < 6){ 126 | toX = getOptionButtonList().get(0).getX() - optionButton.getX(); 127 | toY = getOptionButtonList().get(0).getY() - optionButton.getY(); 128 | }else { 129 | int oldIndex = index % 5; 130 | toX = getOptionButtonList().get(oldIndex).getX() - optionButton.getX(); 131 | toY = getOptionButtonList().get(oldIndex).getY() - optionButton.getY(); 132 | } 133 | 134 | TranslateAnimation translateAnimation= new TranslateAnimation( 135 | 0 136 | ,toX 137 | ,0 138 | ,toY); 139 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 140 | AlphaAnimation alphaAnimation = new AlphaAnimation(1,0); 141 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 142 | animationSet.addAnimation(alphaAnimation); 143 | animationSet.addAnimation(translateAnimation); 144 | 145 | return animationSet; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/TreeYMenu.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.animation.AlphaAnimation; 7 | import android.view.animation.Animation; 8 | import android.view.animation.AnimationSet; 9 | import android.view.animation.LinearInterpolator; 10 | import android.view.animation.TranslateAnimation; 11 | 12 | import com.yanzhikai.ymenuview.PositionBuilders.MenuPositionBuilder; 13 | import com.yanzhikai.ymenuview.PositionBuilders.OptionPositionBuilder; 14 | import com.yanzhikai.ymenuview.PositionBuilders.PositionBuilder; 15 | 16 | /** 17 | * OptionButton分布成一个分叉树的布局,Option最大数量为9个 18 | */ 19 | 20 | public class TreeYMenu extends YMenu { 21 | //9个Option位置的x、y乘积因子 22 | private static final float[] xTimes = {-1,1,0,-2,-2,2,2,-1,1}; 23 | private static final float[] yTimes = {-1,-1,-2,0,-2,0,-2,-3,-3}; 24 | 25 | public TreeYMenu(Context context) { 26 | super(context); 27 | } 28 | 29 | public TreeYMenu(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | } 32 | 33 | public TreeYMenu(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | } 36 | 37 | //设置MenuButton的位置,这是设置在屏幕中下方 38 | @Override 39 | public void setMenuPosition(View menuButton) { 40 | new MenuPositionBuilder(menuButton) 41 | //设置宽高 42 | .setWidthAndHeight(getYMenuButtonWidth(), getYMenuButtonHeight()) 43 | //设置参考方向 44 | .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM) 45 | //设置是否在XY方向处于中心 46 | .setIsXYCenter(true,false) 47 | //设置XY方向距离 48 | .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin()) 49 | .finish(); 50 | 51 | // LayoutParams layoutParams = new LayoutParams(getYMenuButtonWidth(), getYMenuButtonHeight()); 52 | // layoutParams.bottomMargin = getYMenuToParentYMargin(); 53 | // layoutParams.addRule(ALIGN_PARENT_BOTTOM); 54 | // layoutParams.addRule(CENTER_HORIZONTAL); 55 | // menuButton.setLayoutParams(layoutParams); 56 | } 57 | 58 | //设置OptionButton的位置,这里是把9个Option设置为树状布局 59 | @Override 60 | public void setOptionPosition(OptionButton optionButton, View menuButton, int index) { 61 | if (index > 8){ 62 | try { 63 | throw new Exception("TreeYMenuView的OptionPosition最大数量为9,超过将会发生错误"); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | 69 | int centerX = menuButton.getLeft() + menuButton.getWidth()/2; 70 | int centerY = menuButton.getTop() + menuButton.getHeight()/2; 71 | int halfOptionWidth = getYOptionButtonWidth()/2; 72 | int halfOptionHeight = getYOptionButtonHeight()/2; 73 | 74 | //利用乘积因子来决定不同位置 75 | float x = xTimes[index]; 76 | float y = yTimes[index]; 77 | 78 | new OptionPositionBuilder(optionButton,menuButton) 79 | //设置是否以MenuButton为参考 80 | .isAlignMenuButton(false,false) 81 | //设置宽高 82 | .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight()) 83 | //设置XY方向的参考,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离 84 | .setMarginOrientation(PositionBuilder.MARGIN_LEFT,PositionBuilder.MARGIN_TOP) 85 | //设置XY距离 86 | .setXYMargin( 87 | (int)(centerX + x * getYOptionXMargin() - halfOptionWidth) 88 | ,(int)(centerY + y * getYOptionYMargin() - halfOptionHeight) 89 | ) 90 | .finish(); 91 | 92 | // LayoutParams layoutParams = new LayoutParams(getYOptionButtonWidth(), getYOptionButtonHeight()); 93 | // layoutParams.addRule(ALIGN_LEFT); 94 | // layoutParams.addRule(ALIGN_TOP); 95 | // layoutParams.leftMargin = (int)(centerX + x * getYOptionXMargin() - halfOptionWidth); 96 | // layoutParams.topMargin = (int)(centerY + y * getYOptionYMargin() - halfOptionHeight); 97 | // optionButton.setLayoutParams(layoutParams); 98 | } 99 | 100 | //设置OptionButton的显示动画,这里是为前三个先从MenuButton冒出,后面的分别从这三个冒出 101 | @Override 102 | public Animation createOptionShowAnimation(OptionButton optionButton, int index) { 103 | float fromX,fromY; 104 | AnimationSet animationSet = new AnimationSet(true); 105 | if (index < 3){ 106 | fromX = getYMenuButton().getX() - optionButton.getX(); 107 | fromY = getYMenuButton().getY() - optionButton.getY(); 108 | }else { 109 | int oldIndex = (index - 3) / 2; 110 | fromX = getOptionButtonList().get(oldIndex).getX() - optionButton.getX(); 111 | fromY = getOptionButtonList().get(oldIndex).getY() - optionButton.getY(); 112 | //设置冒出动画延时 113 | animationSet.setStartOffset(getOptionSD_AnimationDuration()); 114 | } 115 | 116 | TranslateAnimation translateAnimation= new TranslateAnimation( 117 | fromX 118 | ,0 119 | ,fromY 120 | ,0); 121 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 122 | 123 | AlphaAnimation alphaAnimation = new AlphaAnimation(0,1); 124 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 125 | 126 | animationSet.addAnimation(alphaAnimation); 127 | animationSet.addAnimation(translateAnimation); 128 | animationSet.setInterpolator(new LinearInterpolator()); 129 | return animationSet; 130 | } 131 | 132 | //设置OptionButton的消失动画,这里设置的是直接从当前位置移动到MenuButton位置消失 133 | @Override 134 | public Animation createOptionDisappearAnimation(OptionButton optionButton, int index) { 135 | 136 | AnimationSet animationSet = new AnimationSet(true); 137 | TranslateAnimation translateAnimation= new TranslateAnimation( 138 | 0 139 | ,getYMenuButton().getX() - optionButton.getX() 140 | ,0 141 | ,getYMenuButton().getY() - optionButton.getY() 142 | ); 143 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 144 | AlphaAnimation alphaAnimation = new AlphaAnimation(1,0); 145 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 146 | 147 | animationSet.addAnimation(translateAnimation); 148 | animationSet.addAnimation(alphaAnimation); 149 | //设置动画延时 150 | animationSet.setStartOffset(60*(getOptionPositionCount() - index)); 151 | return animationSet; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/YMenu.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.annotation.DrawableRes; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.view.ViewTreeObserver; 10 | import android.view.animation.Animation; 11 | import android.view.animation.AnimationUtils; 12 | import android.widget.Button; 13 | import android.widget.RelativeLayout; 14 | 15 | import java.util.ArrayList; 16 | 17 | /** 18 | * 一个可以弹出收回菜单栏的自定义View,带有动画效果 19 | * @author Yanzhikai 20 | */ 21 | 22 | public abstract class YMenu extends RelativeLayout implements OptionButton.OptionPrepareListener { 23 | public final static String TAG = "ymenuview"; 24 | 25 | private Context mContext; 26 | private Button mYMenuButton; 27 | 28 | private int drawableIds[] = {R.drawable.zero, R.drawable.one, R.drawable.two, R.drawable.three, 29 | R.drawable.four, R.drawable.five, R.drawable.six, R.drawable.seven}; 30 | private ArrayList optionButtonList; 31 | //“选项”占格个数 32 | private int optionPositionCount = 8; 33 | //“选项”占格列数 34 | private int optionColumns = 1; 35 | //OptionButton实际数量 36 | private int optionButtonCount = 0; 37 | private int[] banArray = {}; 38 | private ArrayList banList; 39 | //MenuButton宽高 40 | private int mYMenuButtonWidth = 80, mYMenuButtonHeight = 80; 41 | //OptionButton宽高 42 | private int mYOptionButtonWidth = 80, mYOptionButtonHeight = 80; 43 | //MenuButton的X方向边距和Y方向边距(距离父ViewGroup边界) 44 | private int mYMenuToParentXMargin = 50, mYMenuToParentYMargin = 50; 45 | //第一个OptionButton的X方向间隔和Y方向间隔 46 | private int mYOptionYMargin = 15, mYOptionXMargin = 15; 47 | //第一个OptionButton的X方向边距和Y方向边距(距离父ViewGroup边界) 48 | private int mYOptionToParentYMargin = 160, mYOptionToParentXMargin = 50; 49 | 50 | private 51 | @DrawableRes 52 | int mMenuButtonBackGroundId = R.drawable.setting; 53 | private 54 | @DrawableRes 55 | int mOptionsBackGroundId = R.drawable.null_drawable; 56 | 57 | //菜单是否正在打开 58 | private boolean isShowMenu = false; 59 | //菜单打开关闭动画 60 | private Animation menuOpenAnimation, menuCloseAnimation; 61 | private Animation.AnimationListener animationListener; 62 | 63 | private int mOptionSD_AnimationMode = OptionButton.FROM_BUTTON_TOP; 64 | private int mOptionSD_AnimationDuration = 600; 65 | private int mMenuAnimationDuration = 600; 66 | private OnOptionsClickListener mOnOptionsClickListener; 67 | 68 | 69 | public YMenu(Context context) { 70 | super(context); 71 | init(context); 72 | } 73 | 74 | public YMenu(Context context, AttributeSet attrs) { 75 | super(context, attrs); 76 | initAttr(context, attrs); 77 | init(context); 78 | } 79 | 80 | public YMenu(Context context, AttributeSet attrs, int defStyleAttr) { 81 | super(context, attrs, defStyleAttr); 82 | initAttr(context, attrs); 83 | init(context); 84 | } 85 | 86 | private void initAttr(Context context, AttributeSet attrs) { 87 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.YMenuView, 0, 0); 88 | mYMenuButtonWidth = typedArray.getDimensionPixelSize(R.styleable.YMenuView_menuButtonWidth, mYMenuButtonWidth); 89 | mYMenuButtonHeight = typedArray.getDimensionPixelSize(R.styleable.YMenuView_menuButtonHeight, mYMenuButtonHeight); 90 | mYOptionButtonWidth = typedArray.getDimensionPixelSize(R.styleable.YMenuView_optionButtonWidth, mYOptionButtonWidth); 91 | mYOptionButtonHeight = typedArray.getDimensionPixelSize(R.styleable.YMenuView_optionButtonHeight, mYOptionButtonHeight); 92 | mYMenuToParentXMargin = typedArray.getDimensionPixelSize(R.styleable.YMenuView_menuToParentXMargin, mYMenuToParentXMargin); 93 | mYMenuToParentYMargin = typedArray.getDimensionPixelSize(R.styleable.YMenuView_menuToParentYMargin, mYMenuToParentYMargin); 94 | optionPositionCount = typedArray.getInteger(R.styleable.YMenuView_optionPositionCounts, optionPositionCount); 95 | optionColumns = typedArray.getInteger(R.styleable.YMenuView_optionColumns, optionColumns); 96 | mYOptionToParentYMargin = typedArray.getDimensionPixelSize(R.styleable.YMenuView_optionToParentYMargin, mYOptionToParentYMargin); 97 | mYOptionToParentXMargin = typedArray.getDimensionPixelSize(R.styleable.YMenuView_optionToParentXMargin, mYOptionToParentXMargin); 98 | mYOptionYMargin = typedArray.getDimensionPixelSize(R.styleable.YMenuView_optionYMargin, mYOptionYMargin); 99 | mYOptionXMargin = typedArray.getDimensionPixelSize(R.styleable.YMenuView_optionXMargin, mYOptionXMargin); 100 | mMenuButtonBackGroundId = typedArray.getResourceId(R.styleable.YMenuView_menuButtonBackGround, mMenuButtonBackGroundId); 101 | mOptionsBackGroundId = typedArray.getResourceId(R.styleable.YMenuView_optionsBackGround, R.drawable.null_drawable); 102 | mOptionSD_AnimationMode = typedArray.getInt(R.styleable.YMenuView_sd_animMode, mOptionSD_AnimationMode); 103 | mOptionSD_AnimationDuration = typedArray.getInt(R.styleable.YMenuView_sd_duration, mOptionSD_AnimationDuration); 104 | isShowMenu = typedArray.getBoolean(R.styleable.YMenuView_isShowMenu, isShowMenu); 105 | 106 | typedArray.recycle(); 107 | } 108 | 109 | private void init(Context context) { 110 | mContext = context; 111 | initMenuAnim(); 112 | setMenuButton(); 113 | 114 | //在获取到宽高参数之后再进行初始化 115 | ViewTreeObserver viewTreeObserver = getViewTreeObserver(); 116 | viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 117 | @Override 118 | public void onGlobalLayout() { 119 | if (getWidth() != 0 && getHeight() != 0) { 120 | try { 121 | // Log.d(TAG, "onGlobalLayout: "); 122 | setOptionButtons(); 123 | setOptionBackGrounds(mOptionsBackGroundId); 124 | setOptionsImages(drawableIds); 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | } 128 | //设置完后立刻注销,不然会不断回调,浪费很多资源 129 | getViewTreeObserver().removeOnGlobalLayoutListener(this); 130 | } 131 | } 132 | }); 133 | } 134 | 135 | 136 | //初始化MenuButton的点击动画 137 | private void initMenuAnim() { 138 | menuOpenAnimation = AnimationUtils.loadAnimation(mContext, R.anim.rotate_open); 139 | menuCloseAnimation = AnimationUtils.loadAnimation(mContext, R.anim.rotate_close); 140 | animationListener = new Animation.AnimationListener() { 141 | @Override 142 | public void onAnimationStart(Animation animation) { 143 | mYMenuButton.setClickable(false); 144 | // Log.d(TAG, "onAnimationStart: "); 145 | 146 | } 147 | 148 | @Override 149 | public void onAnimationEnd(Animation animation) { 150 | mYMenuButton.setClickable(true); 151 | } 152 | 153 | @Override 154 | public void onAnimationRepeat(Animation animation) { 155 | 156 | } 157 | }; 158 | menuOpenAnimation.setDuration(mMenuAnimationDuration); 159 | menuCloseAnimation.setDuration(mMenuAnimationDuration); 160 | menuOpenAnimation.setAnimationListener(animationListener); 161 | menuCloseAnimation.setAnimationListener(animationListener); 162 | } 163 | 164 | 165 | //初始化BanList 166 | private void initBan() throws Exception { 167 | banList = new ArrayList<>(optionPositionCount); 168 | optionButtonCount = optionPositionCount; 169 | for (int i = 0; i < optionPositionCount; i++) { 170 | banList.add(false); 171 | } 172 | for (Integer i : banArray) { 173 | if (i >= 0 && i < optionPositionCount) { 174 | banList.set(i, true); 175 | optionButtonCount--; 176 | } else { 177 | throw new Exception("Ban数组设置不合理,含有负数或者超出范围"); 178 | } 179 | } 180 | } 181 | 182 | 183 | private void setMenuButton() { 184 | mYMenuButton = new Button(mContext); 185 | //生成ID 186 | mYMenuButton.setId(generateViewId()); 187 | //调用抽象方法来确定MenuButton的位置 188 | setMenuPosition(mYMenuButton); 189 | // mSetting.setMenuPosition(mYMenuButton); 190 | 191 | //设置打开关闭事件 192 | mYMenuButton.setOnClickListener(new OnClickListener() { 193 | @Override 194 | public void onClick(View v) { 195 | if (!isShowMenu) { 196 | showMenu(); 197 | } else { 198 | closeMenu(); 199 | } 200 | } 201 | }); 202 | mYMenuButton.setBackgroundResource(mMenuButtonBackGroundId); 203 | 204 | addView(mYMenuButton); 205 | } 206 | 207 | 208 | //设置选项按钮 209 | private void setOptionButtons() throws Exception { 210 | optionButtonList = new ArrayList<>(optionPositionCount); 211 | initBan(); 212 | for (int i = 0; i < optionPositionCount; i++) { 213 | // if (isBan && banArray.length > 0) { 214 | // //Ban判断 215 | // if (i > banArray[n] || banArray[n] > optionPositionCount - 1) { 216 | // throw new Exception("Ban数组设置不合理,含有负数、重复数字或者超出范围"); 217 | // } else if (i == banArray[n]) { 218 | // if (n < banArray.length - 1) { 219 | // n++; 220 | // }else { 221 | // isBan = false; 222 | // } 223 | // continue; 224 | // } 225 | // } 226 | if (!banList.get(i)) { 227 | OptionButton optionButton = new OptionButton(mContext, i); 228 | setOptionPosition(optionButton, mYMenuButton, i); 229 | optionButton.setOptionPrepareListener(this); 230 | // mSetting.setOptionPosition(optionButton, mYMenuButton, i); 231 | addView(optionButton); 232 | optionButtonList.add(optionButton); 233 | } 234 | } 235 | } 236 | 237 | 238 | @Override 239 | public void onOptionPrepare(OptionButton optionButton, int index) { 240 | optionButton.setShowAnimation(createOptionShowAnimation(optionButton, index)); 241 | optionButton.setDisappearAnimation(createOptionDisappearAnimation(optionButton, index)); 242 | //在这里才设置Gone很重要,让View可以一开始就触发onGlobalLayout()进行初始化 243 | if (isShowMenu){ 244 | optionButton.setVisibility(VISIBLE); 245 | }else { 246 | optionButton.setVisibility(GONE); 247 | } 248 | } 249 | 250 | /** 251 | * 设置MenuButton的位置,重写该方法进行自定义设置 252 | * 253 | * @param menuButton 传入传入MenuButton,此时它的宽高位置属性还未设置,需要在此方法设置。 254 | */ 255 | public abstract void setMenuPosition(View menuButton); 256 | 257 | /** 258 | * 设置OptionButton的位置,重写该方法进行自定义设置 259 | * 260 | * @param optionButton 传入OptionButton,此时它的宽高位置属性还未设置,需要在此方法设置。 261 | * @param menuButton 传入MenuButton,此时它已经初始化完毕,可以利用。 262 | * @param index 传入的是该OptionButton的索引,用于区分不同OptionButton。 263 | */ 264 | public abstract void setOptionPosition(OptionButton optionButton, View menuButton, int index); 265 | 266 | /** 267 | * 设置OptionButton的显示动画,重写该方法进行自定义设置 268 | * 269 | * @param optionButton 传入了该动画所属的OptionButton,此时它的宽高位置属性已初始化完毕,可以利用。 270 | * @param index 传入的是该OptionButton的索引,用于区分不同OptionButton。 271 | * @return 返回的是创建好的动画 272 | */ 273 | public abstract Animation createOptionShowAnimation(OptionButton optionButton, int index); 274 | 275 | 276 | /** 277 | * 设置OptionButton的消失动画,重写该方法进行自定义设置 278 | * 279 | * @param optionButton 传入了该动画所属的OptionButton,此时它的宽高位置属性已初始化完毕,可以利用。 280 | * @param index 传入的是该OptionButton的索引,用于区分不同OptionButton。 281 | * @return 返回的是创建好的动画 282 | */ 283 | public abstract Animation createOptionDisappearAnimation(OptionButton optionButton, int index); 284 | 285 | //设置选项按钮的background 286 | public void setOptionBackGrounds(@DrawableRes Integer drawableId) { 287 | for (int i = 0; i < optionButtonList.size(); i++) { 288 | if (drawableId == null) { 289 | optionButtonList.get(i).setBackground(null); 290 | } else { 291 | optionButtonList.get(i).setBackgroundResource(drawableId); 292 | } 293 | 294 | } 295 | 296 | } 297 | 298 | //设置选项按钮的图片资源,顺便设置点击事件 299 | private void setOptionsImages(int... drawableIds) throws Exception { 300 | this.drawableIds = drawableIds; 301 | if (optionPositionCount > drawableIds.length + banArray.length) { 302 | throw new Exception("Drawable资源数量不足"); 303 | } 304 | 305 | for (int i = 0; i < optionButtonList.size(); i++) { 306 | optionButtonList.get(i).setOnClickListener(new MyOnClickListener(i)); 307 | if (drawableIds == null) { 308 | optionButtonList.get(i).setImageDrawable(null); 309 | } else { 310 | optionButtonList.get(i).setImageResource(drawableIds[i]); 311 | } 312 | 313 | } 314 | } 315 | 316 | //设置MenuButton弹出菜单选项时候MenuButton自身的动画,默认为顺时针旋转180度,为空则是关闭动画效果 317 | public void setMenuOpenAnimation(Animation menuOpenAnimation) { 318 | if (menuOpenAnimation == null) { 319 | menuOpenAnimation = AnimationUtils.loadAnimation(mContext, R.anim.anim_null); 320 | } 321 | if (menuOpenAnimation.getDuration() == 0) { 322 | menuOpenAnimation.setDuration(mMenuAnimationDuration); 323 | } 324 | menuOpenAnimation.setAnimationListener(animationListener); 325 | this.menuOpenAnimation = menuOpenAnimation; 326 | 327 | 328 | } 329 | 330 | //设置MenuButton收回菜单选项时候MenuButton自身的动画,默认为逆时针旋转180度,为空则是关闭动画动画效果 331 | public void setMenuCloseAnimation(Animation menuCloseAnimation) { 332 | if (menuCloseAnimation == null) { 333 | menuCloseAnimation = AnimationUtils.loadAnimation(mContext, R.anim.anim_null); 334 | } 335 | if (menuCloseAnimation.getDuration() == 0) { 336 | menuCloseAnimation.setDuration(mOptionSD_AnimationDuration); 337 | } 338 | menuCloseAnimation.setAnimationListener(animationListener); 339 | this.menuCloseAnimation = menuCloseAnimation; 340 | } 341 | 342 | //弹出菜单 343 | public void showMenu() { 344 | if (!isShowMenu) { 345 | for (OptionButton button : optionButtonList) { 346 | button.onShow(); 347 | } 348 | if (menuOpenAnimation != null) { 349 | mYMenuButton.startAnimation(menuOpenAnimation); 350 | } 351 | isShowMenu = true; 352 | } 353 | } 354 | 355 | //关闭菜单 356 | public void closeMenu() { 357 | if (isShowMenu) { 358 | for (OptionButton button : optionButtonList) { 359 | button.onClose(); 360 | } 361 | if (menuCloseAnimation != null) { 362 | mYMenuButton.startAnimation(menuCloseAnimation); 363 | } 364 | isShowMenu = false; 365 | } 366 | } 367 | 368 | //让OptionButton直接消失,不执行关闭动画 369 | public void disappearMenu() { 370 | if (isShowMenu) { 371 | for (OptionButton button : optionButtonList) { 372 | button.onDisappear(); 373 | } 374 | isShowMenu = false; 375 | } 376 | } 377 | 378 | 379 | //清除所有View,用于之后刷新 380 | private void cleanMenu() { 381 | removeAllViews(); 382 | if (optionButtonList != null) { 383 | optionButtonList.clear(); 384 | } 385 | isShowMenu = false; 386 | } 387 | 388 | /* 389 | * 对整个YMenuView进行重新初始化,用于在做完一些设定之后刷新 390 | */ 391 | public void refresh() { 392 | cleanMenu(); 393 | init(mContext); 394 | } 395 | 396 | public Button getYMenuButton() { 397 | return mYMenuButton; 398 | } 399 | 400 | public int getOptionPositionCount() { 401 | return optionPositionCount; 402 | } 403 | 404 | public int getOptionColumns() { 405 | return optionColumns; 406 | } 407 | 408 | //获取实际的OptionButton数量 409 | public int getOptionButtonCount() { 410 | return optionButtonCount; 411 | } 412 | 413 | public int[] getDrawableIds() { 414 | return drawableIds; 415 | } 416 | 417 | public int getMenuButtonBackGroundId() { 418 | return mMenuButtonBackGroundId; 419 | } 420 | 421 | public int getOptionsBackGroundId() { 422 | return mOptionsBackGroundId; 423 | } 424 | 425 | public int getOptionSD_AnimationDuration() { 426 | return mOptionSD_AnimationDuration; 427 | } 428 | 429 | public 430 | @OptionButton.SD_Animation 431 | int getOptionSD_AnimationMode() { 432 | return mOptionSD_AnimationMode; 433 | } 434 | 435 | public int getYMenuToParentYMargin() { 436 | return mYMenuToParentYMargin; 437 | } 438 | 439 | public int getYMenuToParentXMargin() { 440 | return mYMenuToParentXMargin; 441 | } 442 | 443 | public int getYMenuButtonWidth() { 444 | return mYMenuButtonWidth; 445 | } 446 | 447 | public int getYMenuButtonHeight() { 448 | return mYMenuButtonHeight; 449 | } 450 | 451 | public int getYOptionButtonWidth() { 452 | return mYOptionButtonWidth; 453 | } 454 | 455 | public int getYOptionButtonHeight() { 456 | return mYOptionButtonHeight; 457 | } 458 | 459 | public int getYOptionXMargin() { 460 | return mYOptionXMargin; 461 | } 462 | 463 | public int getYOptionYMargin() { 464 | return mYOptionYMargin; 465 | } 466 | 467 | public int getYOptionToParentYMargin() { 468 | return mYOptionToParentYMargin; 469 | } 470 | 471 | public int getYOptionToParentXMargin() { 472 | return mYOptionToParentXMargin; 473 | } 474 | 475 | public ArrayList getOptionButtonList() { 476 | return optionButtonList; 477 | } 478 | 479 | 480 | public void setOnOptionsClickListener(OnOptionsClickListener onOptionsClickListener) { 481 | this.mOnOptionsClickListener = onOptionsClickListener; 482 | } 483 | 484 | 485 | private class MyOnClickListener implements OnClickListener { 486 | private int index; 487 | 488 | public MyOnClickListener(int index) { 489 | this.index = index; 490 | } 491 | 492 | @Override 493 | public void onClick(View v) { 494 | if (mOnOptionsClickListener != null) { 495 | mOnOptionsClickListener.onOptionsClick(index); 496 | } 497 | } 498 | } 499 | 500 | 501 | /* 502 | * 下面的set方法需要在View还没有初始化的时候调用,例如Activity的onCreate方法里 503 | * 如果不在View还没初始化的时候调用,请使用完set这些方法之后调用refresh()方法刷新 504 | */ 505 | 506 | //设置OptionButton的Drawable资源 507 | public void setOptionDrawableIds(int... drawableIds) { 508 | this.drawableIds = drawableIds; 509 | } 510 | 511 | 512 | //设置禁止放置选项的位置序号,注意不能输入负数、重复数字或者大于等于optionPositionCounts的数,会报错。 513 | public void setBanArray(int... banArray) { 514 | this.banArray = banArray; 515 | 516 | } 517 | 518 | //设置“选项”占格个数 519 | public void setOptionPositionCount(int optionPositionCount) { 520 | this.optionPositionCount = optionPositionCount; 521 | } 522 | 523 | //设置一开始的时候是否展开菜单 524 | public void setIsShowMenu(boolean isShowMenu) { 525 | this.isShowMenu = isShowMenu; 526 | } 527 | 528 | public void setOptionColumns(int optionColumns) { 529 | this.optionColumns = optionColumns; 530 | } 531 | 532 | 533 | public void setYMenuButtonWidth(int mYMenuButtonWidth) { 534 | this.mYMenuButtonWidth = mYMenuButtonWidth; 535 | } 536 | 537 | public void setYMenuButtonHeight(int mYMenuButtonHeight) { 538 | this.mYMenuButtonHeight = mYMenuButtonHeight; 539 | } 540 | 541 | public void setYMenuButtonBottomMargin(int mYMenuButtonBottomMargin) { 542 | this.mYMenuToParentYMargin = mYMenuButtonBottomMargin; 543 | } 544 | 545 | public void setYMenuButtonRightMargin(int mYMenuButtonRightMargin) { 546 | this.mYMenuToParentXMargin = mYMenuButtonRightMargin; 547 | } 548 | 549 | public void setYOptionButtonWidth(int mYOptionButtonWidth) { 550 | this.mYOptionButtonWidth = mYOptionButtonWidth; 551 | } 552 | 553 | public void setYOptionButtonHeight(int mYOptionButtonHeight) { 554 | this.mYOptionButtonHeight = mYOptionButtonHeight; 555 | } 556 | 557 | public void setYOptionToParentYMargin(int mYOptionToParentYMargin) { 558 | this.mYOptionToParentYMargin = mYOptionToParentYMargin; 559 | } 560 | 561 | public void setYOptionToParentXMargin(int mYOptionToParentXMargin) { 562 | this.mYOptionToParentXMargin = mYOptionToParentXMargin; 563 | } 564 | 565 | public void setYOptionXMargin(int mYOptionXMargin) { 566 | this.mYOptionXMargin = mYOptionXMargin; 567 | } 568 | 569 | public void setmYOptionYMargin(int mYOptionYMargin) { 570 | this.mYOptionYMargin = mYOptionYMargin; 571 | } 572 | 573 | //使用OptionButton里面的静态变量,如OptionButton.FROM_BUTTON_LEFT 574 | public void setOptionSD_AnimationMode(int optionSD_AnimationMode) { 575 | this.mOptionSD_AnimationMode = optionSD_AnimationMode; 576 | } 577 | 578 | public void setOptionSD_AnimationDuration(int optionSD_AnimationDuration) { 579 | this.mOptionSD_AnimationDuration = optionSD_AnimationDuration; 580 | } 581 | 582 | public void setMenuButtonBackGroundId(int menuButtonBackGroundId) { 583 | this.mMenuButtonBackGroundId = menuButtonBackGroundId; 584 | } 585 | 586 | public void setOptionsBackGroundId(int optionsBackGroundId) { 587 | this.mOptionsBackGroundId = optionsBackGroundId; 588 | } 589 | 590 | 591 | //用于让用户在外部实现点击事件的接口,index可以区分OptionButton 592 | public interface OnOptionsClickListener { 593 | public void onOptionsClick(int index); 594 | } 595 | 596 | } 597 | -------------------------------------------------------------------------------- /ymenuview/src/main/java/com/yanzhikai/ymenuview/YMenuView.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.view.animation.AlphaAnimation; 8 | import android.view.animation.Animation; 9 | import android.view.animation.AnimationSet; 10 | import android.view.animation.TranslateAnimation; 11 | 12 | import com.yanzhikai.ymenuview.PositionBuilders.MenuPositionBuilder; 13 | import com.yanzhikai.ymenuview.PositionBuilders.OptionPositionBuilder; 14 | import com.yanzhikai.ymenuview.PositionBuilders.PositionBuilder; 15 | 16 | import static com.yanzhikai.ymenuview.OptionButton.FROM_BOTTOM; 17 | import static com.yanzhikai.ymenuview.OptionButton.FROM_BUTTON_LEFT; 18 | import static com.yanzhikai.ymenuview.OptionButton.FROM_BUTTON_TOP; 19 | import static com.yanzhikai.ymenuview.OptionButton.FROM_RIGHT; 20 | 21 | /** 22 | * @author Yanzhikai 23 | * Description: 一个可以弹出收回菜单栏的自定义View,带有动画效果 24 | */ 25 | 26 | public class YMenuView extends YMenu implements OptionButton.OptionPrepareListener{ 27 | public final static String TAG = "ymenuview"; 28 | 29 | public YMenuView(Context context) { 30 | super(context); 31 | } 32 | 33 | public YMenuView(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | } 36 | 37 | public YMenuView(Context context, AttributeSet attrs, int defStyleAttr) { 38 | super(context, attrs, defStyleAttr); 39 | } 40 | 41 | @Override 42 | public void setMenuPosition(View menuButton){ 43 | new MenuPositionBuilder(menuButton) 44 | //设置宽高 45 | .setWidthAndHeight(getYMenuButtonWidth(), getYMenuButtonHeight()) 46 | //设置参考方向 47 | .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM) 48 | //设置是否在XY方向处于中心 49 | .setIsXYCenter(false,false) 50 | //设置XY方向的距离,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离 51 | .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin()) 52 | //最后确认时候调用 53 | .finish(); 54 | 55 | 56 | // LayoutParams layoutParams = new LayoutParams(getYMenuButtonWidth(), getYMenuButtonHeight()); 57 | // layoutParams.setMarginEnd(getYMenuToParentXMargin()); 58 | // layoutParams.bottomMargin = getYMenuToParentYMargin(); 59 | // layoutParams.addRule(ALIGN_PARENT_RIGHT); 60 | // layoutParams.addRule(ALIGN_PARENT_BOTTOM); 61 | // menuButton.setLayoutParams(layoutParams); 62 | } 63 | 64 | 65 | @Override 66 | public void setOptionPosition(OptionButton optionButton, View menuButton, int index){ 67 | // Log.d(TAG, "setOptionPosition: " + menuButton.getX()); 68 | //设置动画模式和时长 69 | optionButton.setSD_Animation(getOptionSD_AnimationMode()); 70 | optionButton.setDuration(getOptionSD_AnimationDuration()); 71 | 72 | //计算OptionButton的位置 73 | int position = index % getOptionColumns(); 74 | 75 | new OptionPositionBuilder(optionButton,menuButton) 76 | //设置宽高 77 | .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight()) 78 | //设置在XY方向是否以MenuButton作为参照物 79 | .isAlignMenuButton(false,false) 80 | //设置参考方向 81 | .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM) 82 | //设置XY方向的距离,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离 83 | .setXYMargin( 84 | getYOptionToParentXMargin() 85 | + getYOptionXMargin() * position 86 | + getYOptionButtonWidth() * position, 87 | getYOptionToParentYMargin() 88 | + (getYOptionButtonHeight() + getYOptionYMargin()) 89 | * (index / getOptionColumns())) 90 | //最后确认时候调用 91 | .finish(); 92 | 93 | // RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( 94 | // getYOptionButtonWidth() 95 | // , getYOptionButtonHeight()); 96 | // 97 | // layoutParams.rightMargin = getYOptionToParentXMargin() 98 | // + getYOptionXMargin() * position 99 | // + getYOptionButtonWidth() * position; 100 | // 101 | // layoutParams.bottomMargin = getYOptionToParentYMargin() 102 | // + (getYOptionButtonHeight() + getYOptionYMargin()) 103 | // * (index / getOptionColumns()); 104 | // layoutParams.addRule(ALIGN_PARENT_BOTTOM); 105 | // layoutParams.addRule(ALIGN_PARENT_RIGHT); 106 | // 107 | // optionButton.setLayoutParams(layoutParams); 108 | } 109 | 110 | @Override 111 | public Animation createOptionShowAnimation(OptionButton optionButton, int index){ 112 | AnimationSet animationSet = new AnimationSet(true); 113 | AlphaAnimation alphaAnimation = new AlphaAnimation(0,1); 114 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 115 | TranslateAnimation translateAnimation = new TranslateAnimation(0,0,0,0); 116 | switch (optionButton.getmSD_Animation()){ 117 | //从MenuButton的左边移入 118 | case FROM_BUTTON_LEFT: 119 | translateAnimation= new TranslateAnimation(getYMenuButton().getX() - optionButton.getRight(),0 120 | ,0,0); 121 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 122 | break; 123 | case FROM_RIGHT: 124 | //从右边缘移入 125 | translateAnimation= new TranslateAnimation((getWidth() - optionButton.getX()),0,0,0); 126 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 127 | // showAnimation.setInterpolator(new OvershootInterpolator(1.3f)); 128 | break; 129 | case FROM_BUTTON_TOP: 130 | //从MenuButton的上边缘移入 131 | translateAnimation= new TranslateAnimation(0,0, 132 | getYMenuButton().getY() - optionButton.getBottom(),0); 133 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 134 | break; 135 | case FROM_BOTTOM: 136 | //从下边缘移入 137 | translateAnimation = new TranslateAnimation(0,0,getHeight() - optionButton.getY(),0); 138 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 139 | } 140 | 141 | animationSet.addAnimation(translateAnimation); 142 | animationSet.addAnimation(alphaAnimation); 143 | return animationSet; 144 | } 145 | 146 | @Override 147 | public Animation createOptionDisappearAnimation(OptionButton optionButton, int index){ 148 | AnimationSet animationSet = new AnimationSet(true); 149 | AlphaAnimation alphaAnimation = new AlphaAnimation(1,0); 150 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 151 | TranslateAnimation translateAnimation = new TranslateAnimation(0,0,0,0); 152 | switch (optionButton.getmSD_Animation()) { 153 | case FROM_BUTTON_LEFT: 154 | //从MenuButton的左边移入 155 | translateAnimation= new TranslateAnimation(0,getYMenuButton().getX() - optionButton.getRight() 156 | ,0,0); 157 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 158 | break; 159 | case FROM_RIGHT: 160 | //从右边缘移出 161 | translateAnimation = new TranslateAnimation(0, (getWidth()- optionButton.getX()), 162 | 0, 0); 163 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 164 | break; 165 | case FROM_BUTTON_TOP: 166 | //从MenuButton的上边移入 167 | translateAnimation = new TranslateAnimation(0, 0, 168 | 0, getYMenuButton().getY() - optionButton.getBottom()); 169 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 170 | break; 171 | case FROM_BOTTOM: 172 | //从下边缘移出 173 | translateAnimation = new TranslateAnimation(0,0,0,getHeight() - optionButton.getY()); 174 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 175 | } 176 | 177 | animationSet.addAnimation(translateAnimation); 178 | animationSet.addAnimation(alphaAnimation); 179 | return animationSet; 180 | } 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | } 189 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/anim/anim_null.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/anim/rotate_close.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/anim/rotate_open.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/background_option_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/five.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/four.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/null_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/one.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/option_button_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/option_button_shape_press.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/setting.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/seven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/seven.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/six.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/six.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/three.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/two.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/drawable/zero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totond/YMenuView/d52b8cc2ab54f42f733262226b1eee9bc7ed9615/ymenuview/src/main/res/drawable/zero.png -------------------------------------------------------------------------------- /ymenuview/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ymenuview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | YMenuView 3 | 4 | -------------------------------------------------------------------------------- /ymenuview/src/test/java/com/yanzhikai/ymenuview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yanzhikai.ymenuview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /自定义YMenu教程.md: -------------------------------------------------------------------------------- 1 | # 自定义YMenu教程 2 | 3 | ## 介绍 4 |   YMenu是所有YMenu的父类,通过继承它,重写里面的四个抽象方法,可以实现各种各样的YMenu。 5 |    6 | 7 | ## 实现流程 8 |   实现自定义YMenu的话,总体流程如下: 9 | - 继承YMenu或者它的子类 10 | - 重写构造方法(这个是自定义View必须的) 11 | - 重写四个抽象方法 12 | 13 | 14 | ### 继承YMenu或者它的子类 15 |   继承YMenu的话就必须实现它的4个抽象方法,继承它的子类的话只要重写控制自己想改变内容的方法就行了(如Circle8YMenu的MenuButton的位置固定位于ViewGroup正中间,如果想改变的话可以继承Circle8YMenu,单单重写`setMenuPosition()`方法就可以了)。 16 | 17 | ### 重写构造方法 18 |   这是每个继承View的子类都要做的,如果没有特殊需求的话ALT+ENTER自动生成就好了(如下面`SquareYMenu`的构造方法): 19 | 20 | ``` 21 | public SquareYMenu(Context context) { 22 | super(context); 23 | } 24 | 25 | public SquareYMenu(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | public SquareYMenu(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | } 32 | ``` 33 | 34 | ### 重写四个抽象方法 35 |   这四个抽象方法分别决定不同的功能,实现了它们就能做出一个完整的YMenu。 36 | 37 | ``` 38 | /** 39 | * 设置MenuButton的位置,重写该方法进行自定义设置 40 | * 41 | * @param menuButton 传入传入MenuButton,此时它的宽高位置属性还未设置,需要在此方法设置。 42 | */ 43 | public abstract void setMenuPosition(View menuButton); 44 | 45 | /** 46 | * 设置OptionButton的位置,重写该方法进行自定义设置 47 | * 48 | * @param optionButton 传入OptionButton,此时它的宽高位置属性还未设置,需要在此方法设置。 49 | * @param menuButton 传入MenuButton,此时它已经初始化完毕,可以利用。 50 | * @param index 传入的是该OptionButton的索引,用于区分不同OptionButton。 51 | */ 52 | public abstract void setOptionPosition(OptionButton optionButton, View menuButton, int index); 53 | 54 | /** 55 | * 设置OptionButton的显示动画,重写该方法进行自定义设置 56 | * 57 | * @param optionButton 传入了该动画所属的OptionButton,此时它的宽高位置属性已初始化完毕,可以利用。 58 | * @param index 传入的是该OptionButton的索引,用于区分不同OptionButton。 59 | * @return 返回的是创建好的动画 60 | */ 61 | public abstract Animation createOptionShowAnimation(OptionButton optionButton, int index); 62 | 63 | 64 | /** 65 | * 设置OptionButton的消失动画,重写该方法进行自定义设置 66 | * 67 | * @param optionButton 传入了该动画所属的OptionButton,此时它的宽高位置属性已初始化完毕,可以利用。 68 | * @param index 传入的是该OptionButton的索引,用于区分不同OptionButton。 69 | * @return 返回的是创建好的动画 70 | */ 71 | public abstract Animation createOptionDisappearAnimation(OptionButton optionButton, int index); 72 | 73 | ``` 74 |     这4个抽象方法是有执行顺序的,所以后面的方法能用到前面设置好的对象参数: 75 | ![](https://i.imgur.com/3ttowbC.png) 76 | 77 | ## 实例演示 78 |   上面的流程并不复杂,下面通过介绍YMenu子类中实现比较复杂的TreeYMenu的实现来演示一下如何实现自定义YMenu: 79 | ![](https://i.imgur.com/0lmsS7q.gif) 80 | ### setMenuPosition()方法 81 |   此方法的实现决定了MenuButton的位置,TreeYMenu的MenuButton位置默认是在中下,所以需要我们队传入的MenuButton做处理,以前我们采取的是这种方法: 82 | 83 | ``` 84 | //设置MenuButton的位置,这是设置在屏幕中下方 85 | @Override 86 | public void setMenuPosition(View menuButton) { 87 | LayoutParams layoutParams = new LayoutParams(getYMenuButtonWidth(), getYMenuButtonHeight()); 88 | layoutParams.bottomMargin = getYMenuToParentYMargin(); 89 | layoutParams.addRule(ALIGN_PARENT_BOTTOM); 90 | layoutParams.addRule(CENTER_HORIZONTAL); 91 | menuButton.setLayoutParams(layoutParams); 92 | } 93 | ``` 94 | 95 |   然而我觉得这样逻辑不是很清晰,所以用Builder模式来封装了这个操作,让MenuButton的定位操作逻辑更简单一些: 96 | 97 | ``` 98 | //设置MenuButton的位置,这是设置在屏幕中下方 99 | @Override 100 | public void setMenuPosition(View menuButton) { 101 | new MenuPositionBuilder(menuButton) 102 | //设置宽高 103 | .setWidthAndHeight(getYMenuButtonWidth(), getYMenuButtonHeight()) 104 | //设置参考方向 105 | .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM) 106 | //设置是否在XY方向处于中心,这个优先于setXYMargin()方法和setMarginOrientation()方法 107 | .setIsXYCenter(true,false) 108 | //设置XY方向的参考,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离 109 | .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin()) 110 | //进行最后的配置操作 111 | .finish(); 112 | } 113 | ``` 114 |   上面这两种方法是等价的,使用两种方法都可以。 115 | 116 | ### setOptionPosition()方法 117 |   此方法的实现决定了OptionButton的位置, 118 | 119 | ``` 120 | //设置OptionButton的位置,这里是把9个Option设置为树状布局 121 | @Override 122 | public void setOptionPosition(OptionButton optionButton, View menuButton, int index) { 123 | if (index > 8){ 124 | try { 125 | throw new Exception("TreeYMenuView的OptionPosition最大数量为9,超过将会发生错误"); 126 | } catch (Exception e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | 131 | int centerX = menuButton.getLeft() + menuButton.getWidth()/2; 132 | int centerY = menuButton.getTop() + menuButton.getHeight()/2; 133 | int halfOptionWidth = getYOptionButtonWidth()/2; 134 | int halfOptionHeight = getYOptionButtonHeight()/2; 135 | 136 | //利用乘积因子来决定不同位置 137 | float x = xTimes[index]; 138 | float y = yTimes[index]; 139 | LayoutParams layoutParams = new LayoutParams(getYOptionButtonWidth(), getYOptionButtonHeight()); 140 | layoutParams.addRule(ALIGN_LEFT); 141 | layoutParams.addRule(ALIGN_TOP); 142 | layoutParams.leftMargin = (int)(centerX + x * getYOptionXMargin() - halfOptionWidth); 143 | layoutParams.topMargin = (int)(centerY + y * getYOptionYMargin() - halfOptionHeight); 144 | optionButton.setLayoutParams(layoutParams); 145 | } 146 | ``` 147 | 148 |   和MenuButton一样,这个方法也有一个专属的OptionPositionBuilder,下面使用它来设置: 149 | 150 | ``` 151 | //设置OptionButton的位置,这里是把9个Option设置为树状布局 152 | @Override 153 | public void setOptionPosition(OptionButton optionButton, View menuButton, int index) { 154 | if (index > 8){ 155 | try { 156 | throw new Exception("TreeYMenuView的OptionPosition最大数量为9,超过将会发生错误"); 157 | } catch (Exception e) { 158 | e.printStackTrace(); 159 | } 160 | } 161 | 162 | int centerX = menuButton.getLeft() + menuButton.getWidth()/2; 163 | int centerY = menuButton.getTop() + menuButton.getHeight()/2; 164 | int halfOptionWidth = getYOptionButtonWidth()/2; 165 | int halfOptionHeight = getYOptionButtonHeight()/2; 166 | 167 | //利用乘积因子来决定不同位置 168 | float x = xTimes[index]; 169 | float y = yTimes[index]; 170 | new OptionPositionBuilder(optionButton,menuButton) 171 | .isAlignMenuButton(false,false) 172 | .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight()) 173 | .setMarginOrientation(PositionBuilder.MARGIN_LEFT,PositionBuilder.MARGIN_TOP) 174 | .setXYMargin( 175 | (int)(centerX + x * getYOptionXMargin() - halfOptionWidth) 176 | ,(int)(centerY + y * getYOptionYMargin() - halfOptionHeight) 177 | ) 178 | .finish(); 179 | } 180 | ``` 181 |   这里用到了乘积因子,它们是xy方向距离对应的比例值:**以MenuButton的中心作为参考点,以optionXMargin和optionYMargin作为单位长度,xy乘积因子分别乘以单位长度就是OptionButton到参考点的x,y方向距离,根据这个距离和MenuButton的位置信息,就可以得出OptionButton的布局了**: 182 | 183 | ``` 184 | //9个Option位置的x、y乘积因子 185 | private static final float[] xTimes = {-1,1,0,-2,-2,2,2,-1,1}; 186 | private static final float[] yTimes = {-1,-1,-2,0,-2,0,-2,-3,-3}; 187 | ``` 188 | > 如index为0的OptionButton,它的x=-1,y=-1,以ViewGroup的左上边缘为参考,所以它最后就处在MenuButton的左上方了 189 | 190 | ### createOptionShowAnimation()和createOptionDisappearAnimation()方法 191 |   此方法用于创造OptionButton出现动画,并且把它返回。这里动画思路是:前三个从MenuButton里冒出来,后面的6个分别两两从这前三个位置里冒出来,还设置了延时,让前三个出现完了再让后面的出现,实现分叉树的生成效果。 192 | 193 | ``` 194 | //设置OptionButton的显示动画,这里是为前三个先从MenuButton冒出,后面的分别从这三个冒出 195 | @Override 196 | public Animation createOptionShowAnimation(OptionButton optionButton, int index) { 197 | float fromX,fromY; 198 | AnimationSet animationSet = new AnimationSet(true); 199 | if (index < 3){ 200 | fromX = getYMenuButton().getX() - optionButton.getX(); 201 | fromY = getYMenuButton().getY() - optionButton.getY(); 202 | }else { 203 | int oldIndex = (index - 3) / 2; 204 | fromX = getOptionButtonList().get(oldIndex).getX() - optionButton.getX(); 205 | fromY = getOptionButtonList().get(oldIndex).getY() - optionButton.getY(); 206 | //设置冒出动画延时 207 | animationSet.setStartOffset(getOptionSD_AnimationDuration()); 208 | } 209 | 210 | TranslateAnimation translateAnimation= new TranslateAnimation( 211 | fromX 212 | ,0 213 | ,fromY 214 | ,0); 215 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 216 | 217 | AlphaAnimation alphaAnimation = new AlphaAnimation(0,1); 218 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 219 | 220 | animationSet.addAnimation(alphaAnimation); 221 | animationSet.addAnimation(translateAnimation); 222 | animationSet.setInterpolator(new LinearInterpolator()); 223 | return animationSet; 224 | } 225 | ``` 226 | 227 | ### createOptionDisappearAnimation()方法 228 |   此方法用于创造OptionButton消失动画,并且把它返回。实现思路比较简单粗暴:直接让OptionButton从原来的位置移动到MenuButton位置上消失,不过每一个OptionButton的延时都不一样。 229 | 230 | ``` 231 | //设置OptionButton的消失动画,这里设置的是直接从当前位置移动到MenuButton位置消失 232 | @Override 233 | public Animation createOptionDisappearAnimation(OptionButton optionButton, int index) { 234 | 235 | AnimationSet animationSet = new AnimationSet(true); 236 | TranslateAnimation translateAnimation= new TranslateAnimation( 237 | 0 238 | ,getYMenuButton().getX() - optionButton.getX() 239 | ,0 240 | ,getYMenuButton().getY() - optionButton.getY() 241 | ); 242 | translateAnimation.setDuration(getOptionSD_AnimationDuration()); 243 | AlphaAnimation alphaAnimation = new AlphaAnimation(1,0); 244 | alphaAnimation.setDuration(getOptionSD_AnimationDuration()); 245 | 246 | animationSet.addAnimation(translateAnimation); 247 | animationSet.addAnimation(alphaAnimation); 248 | //设置动画延时 249 | animationSet.setStartOffset(60*(getOptionPositionCount() - index)); 250 | return animationSet; 251 | } 252 | ``` 253 | 254 | ## 最后 255 |   所以实现自定义YMenu最核心就是这四个抽象方法的实现,继承YMenu实现它们、或者继承子类重写里面的几个,就能够按照你的想法实现炫酷的菜单按钮啦。 256 |    --------------------------------------------------------------------------------