├── .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 |
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 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 | 
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 | 
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 | 
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 | 
168 |
169 | 这是一个OptionButton围绕着MenuButton的布局,Option最大数量为8个,MenuButton的位置位于ViewGroup正中间,如果想改变的话可以继承Circle8YMenu,单单重写`setMenuPosition()`方法就可以了,这个是以`optionXMargin`为圆的半径。
170 |
171 | #### TreeYMenu
172 | 
173 |
174 | 这是一个OptionButton分布成分叉树的布局,Option最大数量为9个,MenuButton的位置位于ViewGroup中下方,支持了`menuToParentYMargin`属性。
175 |
176 | #### SquareYMenu
177 |
178 | 
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 | 
231 |
232 | ```
233 | //对TreeYMenu
234 | mYMenu.setBanArray(2,8,7);
235 | ```
236 |
237 | 
238 |
239 | ```
240 | //对SquareYMenu
241 | mYMenu.setBanArray(3,4,7,5,6);
242 | ```
243 |
244 | 
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 | 
7 |
8 | ## 使用
9 |
10 | ### Gradle
11 |
12 | ```
13 | compile 'com.yanzhikaijky:YMenuView:1.0.3'
14 | ```
15 |
16 | ### 属性
17 | 
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 | 
170 |
171 | #### 横放式
172 | **XML:**
173 |
174 | ```
175 |
186 | ```
187 | **效果:**
188 | 
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 | 
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 | 
76 |
77 | ## 实例演示
78 | 上面的流程并不复杂,下面通过介绍YMenu子类中实现比较复杂的TreeYMenu的实现来演示一下如何实现自定义YMenu:
79 | 
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 |
--------------------------------------------------------------------------------