├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── dictionaries
│ └── felix.xml
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── kareluo
│ │ └── popupmenuview
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── me
│ │ │ └── kareluo
│ │ │ └── popupmenuview
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── ic_heart.png
│ │ ├── ic_location.png
│ │ ├── ic_more.png
│ │ └── ic_red_heart.png
│ │ ├── drawable
│ │ ├── bg_custom_menu.xml
│ │ ├── s_heart.xml
│ │ └── universe.jpg
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── layout_custom_menu.xml
│ │ └── layout_menus.xml
│ │ ├── menu
│ │ └── menu_pop.xml
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── me
│ └── kareluo
│ └── popupmenuview
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── popmenu
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── kareluo
│ │ └── ui
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── me
│ │ │ └── kareluo
│ │ │ └── ui
│ │ │ ├── OptionItemView.java
│ │ │ ├── OptionMenu.java
│ │ │ ├── OptionMenuView.java
│ │ │ ├── PopHorizontalScrollView.java
│ │ │ ├── PopLayout.java
│ │ │ ├── PopVerticalScrollView.java
│ │ │ ├── PopupMenuView.java
│ │ │ └── PopupView.java
│ └── res
│ │ ├── drawable
│ │ └── bg_menu.xml
│ │ ├── layout
│ │ └── layout_menu_item.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── dimens.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── me
│ └── kareluo
│ └── ui
│ └── ExampleUnitTest.java
├── screenshot
├── code.png
├── device-2016-11-22-173850.png
├── device-2016-11-22-174011.png
├── device-2016-11-22-174803.png
└── preview_image.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | PopupMenuView
--------------------------------------------------------------------------------
/.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/dictionaries/felix.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | cornute
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.7
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PopupMenuView
2 | 此库主要实现了一个类似iOS中的`UIMenuController`控件的Popup控件。
3 | 主要控件如下:
4 |
5 | - `PopLayout`继承自`FrameLayout`,用于实现控件的气泡化。
6 | - `OptionMenuView`继承自`LinearLayout`,用于实现Menu控件。
7 | - `PopupView`继承自`PopupWindow`,用于实现控件的指定方位弹出效果。
8 | - `PopupMenuView`是上述三者的集合,实现了弹出气泡菜单的功能。
9 |
10 | 
11 |
12 | # Demo
13 | 安装 [apk](https://www.pgyer.com/menu) 文件预览效果,或者通过下面二维码去下载安装:
14 |
15 | 
16 |
17 | # Usage
18 |
19 | Use Gradle:
20 | ``` java
21 | compile 'me.kareluo.ui:popmenu:1.1.0'
22 | ```
23 |
24 | Or Maven:
25 | ``` maven
26 |
27 | me.kareluo.ui
28 | popmenu
29 | 1.1.0
30 | pom
31 |
32 | ```
33 |
34 | # Sample
35 |
36 | `PopupMenuView`可以通过menu布局文件加载出预先准备的菜单:
37 |
38 | ``` java
39 | // 根据menu资源文件创建
40 | PopupMenuView menuView = new PopupMenuView(this, R.menu.menu_pop, new MenuBuilder(context));
41 |
42 | // 设置点击监听事件
43 | menuView.setOnMenuClickListener(new OptionMenuView.OnOptionMenuClickListener() {
44 | @Override
45 | public boolean onOptionMenuClick(int position, OptionMenu menu) {
46 | Toast.makeText(this, menu.getTitle(), Toast.LENGTH_SHORT).show();
47 | return true;
48 | }
49 | });
50 |
51 | // 显示在mButtom控件的周围
52 | menuView.show(mButtom);
53 | ```
54 |
55 | 或者通过代码添加:
56 |
57 | ``` java
58 | menuView.setMenuItems(Arrays.asList(
59 | new OptionMenu("复制"), new OptionMenu("转发到朋友圈"),
60 | new OptionMenu("收藏"), new OptionMenu("翻译"),
61 | new OptionMenu("删除")));
62 | ```
63 |
64 | 默认的显示方位为:上、下、左、右,即按照这种顺序测试界面是否可以显示下菜单,可以如下方式自定义:
65 |
66 | ``` java
67 | menuView.setSites(PopupView.SITE_BOTTOM, PopupView.SITE_LEFT, PopupView.SITE_TOP, PopupView.SITE_RIGHT);
68 | ```
69 |
70 | # Update Logs
71 |
72 | #### v1.0.0
73 | - 弹出菜单`PopupMenuView`
74 | - 气泡布局
75 | - 菜单控件
76 |
77 | #### v1.0.1
78 | - 移除support库引用,加载menu资源文件方法变动
79 |
80 | #### v1.0.2
81 | - 增加气泡布局资源属性offsetSize,并修改部分资源属性名称
82 |
83 | #### v1.0.3
84 | - 修改了module的名称,之前版本为`library`,后续版本为`popmenu`
85 |
86 | #### v1.0.4
87 | - 修复菜单条目变化时无法显示BUG
88 |
89 | #### v1.0.5
90 | - 增加自定义样式接口
91 |
92 | #### v1.1.0
93 | - 默认样式微调
94 | - 菜单项过多时可滚动视图
95 |
96 | # License
97 | ``` license
98 | Copyright 2016 kareluo.
99 |
100 | Licensed under the Apache License, Version 2.0 (the "License");
101 | you may not use this file except in compliance with the License.
102 | You may obtain a copy of the License at
103 |
104 | http://www.apache.org/licenses/LICENSE-2.0
105 |
106 | Unless required by applicable law or agreed to in writing, software
107 | distributed under the License is distributed on an "AS IS" BASIS,
108 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
109 | See the License for the specific language governing permissions and
110 | limitations under the License.
111 | ```
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.2"
6 |
7 | defaultConfig {
8 | applicationId "me.kareluo.popupmenuview"
9 | minSdkVersion 14
10 | targetSdkVersion 24
11 | versionCode 1
12 | versionName "1.0"
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 'com.android.support:appcompat-v7:24.2.1'
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | testCompile 'junit:junit:4.12'
26 | compile project(path: ':popmenu')
27 | }
28 |
--------------------------------------------------------------------------------
/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 /Users/felix/Workspace/Library/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/kareluo/popupmenuview/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.popupmenuview;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/me/kareluo/popupmenuview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.popupmenuview;
2 |
3 | import android.graphics.Path;
4 | import android.graphics.RectF;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.view.menu.MenuBuilder;
8 | import android.view.View;
9 | import android.widget.LinearLayout;
10 | import android.widget.RadioGroup;
11 | import android.widget.SeekBar;
12 | import android.widget.Toast;
13 |
14 | import java.util.Arrays;
15 |
16 | import me.kareluo.ui.OptionMenu;
17 | import me.kareluo.ui.OptionMenuView;
18 | import me.kareluo.ui.PopLayout;
19 | import me.kareluo.ui.PopupMenuView;
20 | import me.kareluo.ui.PopupView;
21 |
22 | public class MainActivity extends AppCompatActivity implements View.OnClickListener,
23 | RadioGroup.OnCheckedChangeListener, OptionMenuView.OnOptionMenuClickListener {
24 |
25 | private PopLayout mPopLayout;
26 |
27 | private PopLayout mImgPopLayout;
28 |
29 | private OptionMenuView mMenuView;
30 |
31 | private PopupMenuView mPopupMenuView;
32 |
33 | private PopupMenuView mCustomMenuView;
34 |
35 | @Override
36 | protected void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_main);
39 |
40 | RadioGroup radioGroup = (RadioGroup) findViewById(R.id.rg_group);
41 | radioGroup.setOnCheckedChangeListener(this);
42 |
43 | mPopLayout = (PopLayout) findViewById(R.id.pl_pop);
44 | mImgPopLayout = (PopLayout) findViewById(R.id.pl_img);
45 |
46 | SeekBar sbRadius = (SeekBar) findViewById(R.id.sb_radius);
47 | sbRadius.setProgress(mImgPopLayout.getRadiusSize());
48 | sbRadius.setOnSeekBarChangeListener(
49 | new SeekBar.OnSeekBarChangeListener() {
50 | @Override
51 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
52 | mImgPopLayout.setRadiusSize(progress);
53 | }
54 |
55 | @Override
56 | public void onStartTrackingTouch(SeekBar seekBar) {
57 |
58 | }
59 |
60 | @Override
61 | public void onStopTrackingTouch(SeekBar seekBar) {
62 |
63 | }
64 | });
65 |
66 | SeekBar sbBulge = (SeekBar) findViewById(R.id.sb_bulge);
67 | sbBulge.setProgress(mImgPopLayout.getBugleSize());
68 | sbBulge.setOnSeekBarChangeListener(
69 | new SeekBar.OnSeekBarChangeListener() {
70 | @Override
71 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
72 | mImgPopLayout.setBulgeSize(progress);
73 | }
74 |
75 | @Override
76 | public void onStartTrackingTouch(SeekBar seekBar) {
77 |
78 | }
79 |
80 | @Override
81 | public void onStopTrackingTouch(SeekBar seekBar) {
82 |
83 | }
84 | });
85 |
86 | mMenuView = (OptionMenuView) findViewById(R.id.omv_menu);
87 | mMenuView.setOnOptionMenuClickListener(this);
88 |
89 | SeekBar seekBar = (SeekBar) findViewById(R.id.sb_offset);
90 | seekBar.setProgress(mPopLayout.getOffset());
91 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
92 | @Override
93 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
94 | mPopLayout.setOffset(progress);
95 | mImgPopLayout.setOffset(progress);
96 | }
97 |
98 | @Override
99 | public void onStartTrackingTouch(SeekBar seekBar) {
100 |
101 | }
102 |
103 | @Override
104 | public void onStopTrackingTouch(SeekBar seekBar) {
105 |
106 | }
107 | });
108 |
109 | mPopupMenuView = new PopupMenuView(this, R.menu.menu_pop, new MenuBuilder(this));
110 | mPopupMenuView.setOnMenuClickListener(this);
111 |
112 | mCustomMenuView = new PopupMenuView(this, R.layout.layout_custom_menu);
113 | mCustomMenuView.inflate(R.menu.menu_pop, new MenuBuilder(this));
114 | mCustomMenuView.setOnMenuClickListener(this);
115 |
116 | mMenuView.setOptionMenus(Arrays.asList(
117 | new OptionMenu("复制"), new OptionMenu("转发到朋友圈"),
118 | new OptionMenu("收藏"), new OptionMenu("翻译"),
119 | new OptionMenu("删除")));
120 |
121 |
122 | Path triangle = new Path();
123 | triangle.lineTo(32, 0);
124 | triangle.lineTo(16, 16);
125 | triangle.close();
126 |
127 | Path path = new Path();
128 | path.addRoundRect(new RectF(0, 0, 100, 32), 16, 16, Path.Direction.CW);
129 | path.addPath(triangle, 16, 32);
130 | }
131 |
132 | @Override
133 | public void onClick(View v) {
134 | switch (v.getId()) {
135 | case R.id.btn_orientation:
136 | if (mMenuView.getOrientation() == LinearLayout.HORIZONTAL) {
137 | mMenuView.setOrientation(LinearLayout.VERTICAL);
138 | mPopupMenuView.setOrientation(LinearLayout.VERTICAL);
139 | mCustomMenuView.setOrientation(LinearLayout.VERTICAL);
140 | } else {
141 | mMenuView.setOrientation(LinearLayout.HORIZONTAL);
142 | mPopupMenuView.setOrientation(LinearLayout.HORIZONTAL);
143 | mCustomMenuView.setOrientation(LinearLayout.HORIZONTAL);
144 | }
145 | break;
146 | case R.id.btn_show:
147 | case R.id.btn_show1:
148 | case R.id.btn_show2:
149 | case R.id.btn_show3:
150 | mPopupMenuView.show(v);
151 | break;
152 | case R.id.btn_custom:
153 | mCustomMenuView.show(v);
154 | break;
155 | }
156 | }
157 |
158 | @Override
159 | public void onCheckedChanged(RadioGroup group, int checkedId) {
160 | switch (checkedId) {
161 | case R.id.radio1:
162 | mPopLayout.setSiteMode(PopLayout.SITE_LEFT);
163 | mImgPopLayout.setSiteMode(PopLayout.SITE_LEFT);
164 | mPopupMenuView.setSites(PopupView.SITE_LEFT, PopupView.SITE_TOP, PopupView.SITE_RIGHT, PopupView.SITE_BOTTOM);
165 | mCustomMenuView.setSites(PopupView.SITE_LEFT, PopupView.SITE_TOP, PopupView.SITE_RIGHT, PopupView.SITE_BOTTOM);
166 | break;
167 | case R.id.radio2:
168 | mPopLayout.setSiteMode(PopLayout.SITE_TOP);
169 | mImgPopLayout.setSiteMode(PopLayout.SITE_TOP);
170 | mPopupMenuView.setSites(PopupView.SITE_TOP, PopupView.SITE_RIGHT, PopupView.SITE_BOTTOM, PopupView.SITE_LEFT);
171 | mCustomMenuView.setSites(PopupView.SITE_TOP, PopupView.SITE_RIGHT, PopupView.SITE_BOTTOM, PopupView.SITE_LEFT);
172 | break;
173 | case R.id.radio3:
174 | mPopLayout.setSiteMode(PopLayout.SITE_RIGHT);
175 | mImgPopLayout.setSiteMode(PopLayout.SITE_RIGHT);
176 | mPopupMenuView.setSites(PopupView.SITE_RIGHT, PopupView.SITE_BOTTOM, PopupView.SITE_LEFT, PopupView.SITE_TOP);
177 | mCustomMenuView.setSites(PopupView.SITE_RIGHT, PopupView.SITE_BOTTOM, PopupView.SITE_LEFT, PopupView.SITE_TOP);
178 | break;
179 | case R.id.radio4:
180 | mPopLayout.setSiteMode(PopLayout.SITE_BOTTOM);
181 | mImgPopLayout.setSiteMode(PopLayout.SITE_BOTTOM);
182 | mPopupMenuView.setSites(PopupView.SITE_BOTTOM, PopupView.SITE_LEFT, PopupView.SITE_TOP, PopupView.SITE_RIGHT);
183 | mCustomMenuView.setSites(PopupView.SITE_BOTTOM, PopupView.SITE_LEFT, PopupView.SITE_TOP, PopupView.SITE_RIGHT);
184 | break;
185 | }
186 | }
187 |
188 | @Override
189 | public boolean onOptionMenuClick(int position, OptionMenu menu) {
190 | if (menu.getId() == R.id.menu_collect) {
191 | menu.toggle();
192 | }
193 | Toast.makeText(this, menu.getTitle(), Toast.LENGTH_SHORT).show();
194 | return true;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/app/src/main/res/drawable-hdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/app/src/main/res/drawable-hdpi/ic_location.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/app/src/main/res/drawable-hdpi/ic_more.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_red_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/app/src/main/res/drawable-hdpi/ic_red_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_custom_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/s_heart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/universe.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/app/src/main/res/drawable/universe.jpg
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
25 |
26 |
31 |
32 |
37 |
38 |
43 |
44 |
49 |
50 |
51 |
52 |
58 |
59 |
60 |
61 |
67 |
68 |
74 |
75 |
82 |
83 |
90 |
91 |
98 |
99 |
106 |
107 |
108 |
109 |
113 |
114 |
121 |
122 |
129 |
130 |
131 |
132 |
137 |
138 |
142 |
143 |
147 |
148 |
152 |
153 |
154 |
155 |
159 |
160 |
164 |
165 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
180 |
181 |
185 |
186 |
187 |
188 |
192 |
193 |
200 |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_custom_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_menus.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_pop.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PopupMenuView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/test/java/me/kareluo/popupmenuview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.popupmenuview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/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.1.3'
9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Nov 17 23:57:45 CST 2016
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-2.14.1-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 |
--------------------------------------------------------------------------------
/popmenu/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/popmenu/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.jfrog.bintray'
3 | apply plugin: 'com.github.dcendents.android-maven'
4 |
5 | version = "1.1.0"
6 |
7 | android {
8 | compileSdkVersion 24
9 | buildToolsVersion "24.0.2"
10 |
11 | resourcePrefix "kareluo_ui_"
12 |
13 | defaultConfig {
14 | minSdkVersion 14
15 | targetSdkVersion 24
16 | versionCode 110
17 | versionName version
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | compile fileTree(dir: 'libs', include: ['*.jar'])
29 | testCompile 'junit:junit:4.12'
30 | }
31 |
32 |
33 | def siteUrl = 'https://github.com/kareluo/PopupMenuView'
34 | def gitUrl = 'https://github.com/kareluo/PopupMenuView.git'
35 |
36 | group = "me.kareluo.ui"
37 |
38 | install {
39 |
40 | repositories.mavenInstaller {
41 | // This generates POM.xml with proper parameters
42 | pom {
43 | project {
44 | packaging 'aar'
45 |
46 | name 'me.kareluo.ui:popmenu'
47 | url siteUrl
48 |
49 | licenses {
50 | license {
51 | name 'The Apache Software License, Version 2.0'
52 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
53 | }
54 | }
55 |
56 | developers {
57 | developer {
58 | id 'kareluo'
59 | name 'Felix'
60 | email 'kareluo76@gmail.com'
61 | }
62 | }
63 |
64 | scm {
65 | connection gitUrl
66 | developerConnection gitUrl
67 | url siteUrl
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 | task sourcesJar(type: Jar) {
75 | from android.sourceSets.main.java.srcDirs
76 | classifier = 'sources'
77 | }
78 |
79 | task javadoc(type: Javadoc) {
80 | source = android.sourceSets.main.java.srcDirs
81 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
82 | }
83 |
84 | task javadocJar(type: Jar, dependsOn: javadoc) {
85 | classifier = 'javadoc'
86 | from javadoc.destinationDir
87 | }
88 |
89 | artifacts {
90 | archives javadocJar
91 | archives sourcesJar
92 | }
93 |
94 | Properties properties = new Properties()
95 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
96 | bintray {
97 | user = properties.getProperty("bintray.user")
98 | key = properties.getProperty("bintray.apikey")
99 | configurations = ['archives']
100 | pkg {
101 | repo = "maven"
102 | name = "me.kareluo.ui:popmenu"
103 | websiteUrl = siteUrl
104 | vcsUrl = gitUrl
105 | licenses = ["Apache-2.0"]
106 | publish = true
107 | }
108 | }
--------------------------------------------------------------------------------
/popmenu/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 /Users/felix/Workspace/Library/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 |
--------------------------------------------------------------------------------
/popmenu/src/androidTest/java/me/kareluo/ui/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/popmenu/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/OptionItemView.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.Gravity;
6 | import android.widget.CheckedTextView;
7 |
8 | /**
9 | * Created by felix on 16/11/21.
10 | */
11 | public class OptionItemView extends CheckedTextView {
12 |
13 | public OptionItemView(Context context) {
14 | this(context, null, 0);
15 | }
16 |
17 | public OptionItemView(Context context, AttributeSet attrs) {
18 | this(context, attrs, 0);
19 | }
20 |
21 | public OptionItemView(Context context, AttributeSet attrs, int defStyleAttr) {
22 | super(context, attrs, defStyleAttr);
23 | initialize(context, attrs, defStyleAttr);
24 | }
25 |
26 | public OptionItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
27 | super(context, attrs, defStyleAttr);
28 | initialize(context, attrs, defStyleAttr);
29 | }
30 |
31 | private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/OptionMenu.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.graphics.drawable.Drawable;
4 | import android.view.MenuItem;
5 | import android.view.View;
6 | import android.widget.CheckedTextView;
7 |
8 | /**
9 | * Created by felix on 16/11/19.
10 | */
11 | public class OptionMenu {
12 |
13 | private int id;
14 |
15 | private boolean enable;
16 |
17 | // 暂时不支持
18 | private boolean visible;
19 |
20 | private boolean checked;
21 |
22 | private boolean checkable;
23 |
24 | private int titleRes;
25 |
26 | private CharSequence title;
27 |
28 | private Drawable drawableLeft;
29 |
30 | private Drawable drawableTop;
31 |
32 | private Drawable drawableRight;
33 |
34 | private Drawable drawableBottom;
35 |
36 | public OptionMenu() {
37 | this.enable = true;
38 | this.visible = true;
39 | this.checked = false;
40 | this.checkable = true;
41 | }
42 |
43 | public OptionMenu(int titleRes) {
44 | this();
45 | this.titleRes = titleRes;
46 | }
47 |
48 | public OptionMenu(CharSequence title) {
49 | this();
50 | this.title = title;
51 | }
52 |
53 | public OptionMenu(MenuItem item) {
54 | this.id = item.getItemId();
55 | this.title = item.getTitle();
56 | this.enable = item.isEnabled();
57 | this.visible = item.isVisible();
58 | this.checked = item.isChecked();
59 | this.checkable = item.isCheckable();
60 | switch (item.getOrder()) {
61 | case 0:
62 | this.drawableLeft = item.getIcon();
63 | break;
64 | case 1:
65 | this.drawableTop = item.getIcon();
66 | break;
67 | case 2:
68 | this.drawableRight = item.getIcon();
69 | break;
70 | case 3:
71 | this.drawableBottom = item.getIcon();
72 | break;
73 | }
74 | }
75 |
76 | public int getId() {
77 | return id;
78 | }
79 |
80 | public void setId(int id) {
81 | this.id = id;
82 | }
83 |
84 | public boolean isEnable() {
85 | return enable;
86 | }
87 |
88 | public void setEnable(boolean enable) {
89 | this.enable = enable;
90 | }
91 |
92 | private boolean isVisible() {
93 | return visible;
94 | }
95 |
96 | // 暂时不支持
97 | private void setVisible(boolean visible) {
98 | this.visible = visible;
99 | }
100 |
101 | public boolean isChecked() {
102 | return checked;
103 | }
104 |
105 | public void setChecked(boolean checked) {
106 | if (checkable) {
107 | this.checked = checked;
108 | }
109 | }
110 |
111 | public void toggle() {
112 | setChecked(!checked);
113 | }
114 |
115 | public boolean isCheckable() {
116 | return checkable;
117 | }
118 |
119 | public void setCheckable(boolean checkable) {
120 | this.checkable = checkable;
121 | }
122 |
123 | public int getTitleRes() {
124 | return titleRes;
125 | }
126 |
127 | public void setTitleRes(int titleRes) {
128 | this.titleRes = titleRes;
129 | }
130 |
131 | public CharSequence getTitle() {
132 | return title;
133 | }
134 |
135 | public void setTitle(CharSequence title) {
136 | this.title = title;
137 | }
138 |
139 | public Drawable getDrawableLeft() {
140 | return drawableLeft;
141 | }
142 |
143 | public void setDrawableLeft(Drawable drawableLeft) {
144 | this.drawableLeft = drawableLeft;
145 | }
146 |
147 | public Drawable getDrawableTop() {
148 | return drawableTop;
149 | }
150 |
151 | public void setDrawableTop(Drawable drawableTop) {
152 | this.drawableTop = drawableTop;
153 | }
154 |
155 | public Drawable getDrawableRight() {
156 | return drawableRight;
157 | }
158 |
159 | public void setDrawableRight(Drawable drawableRight) {
160 | this.drawableRight = drawableRight;
161 | }
162 |
163 | public Drawable getDrawableBottom() {
164 | return drawableBottom;
165 | }
166 |
167 | public void setDrawableBottom(Drawable drawableBottom) {
168 | this.drawableBottom = drawableBottom;
169 | }
170 |
171 | public void validate(CheckedTextView textView) {
172 | if (titleRes > 0) {
173 | textView.setText(titleRes);
174 | } else textView.setText(title);
175 | textView.setEnabled(enable);
176 | textView.setChecked(checked);
177 | // 暂时不支持
178 | // textView.setVisibility(visible ? View.VISIBLE : View.GONE);
179 | textView.setCompoundDrawablesWithIntrinsicBounds(
180 | drawableLeft, drawableTop, drawableRight, drawableBottom);
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/OptionMenuView.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 | import android.view.Menu;
9 | import android.view.MenuInflater;
10 | import android.view.View;
11 | import android.widget.CheckedTextView;
12 | import android.widget.LinearLayout;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by felix on 16/11/19.
19 | */
20 | public class OptionMenuView extends LinearLayout implements PopLayout.OnBulgeChangeCallback, View.OnClickListener {
21 | private static final String TAG = "OptionMenuView";
22 |
23 | private int mBulgeSize = 0;
24 |
25 | private boolean mNeedLayout = false;
26 |
27 | private List mOptionMenus;
28 |
29 | private int mItemRes = R.layout.layout_menu_item;
30 |
31 | private OnOptionMenuClickListener mMenuClickListener;
32 |
33 | private int mLeftPadding, mTopPadding, mRightPadding, mBottomPadding;
34 |
35 | public OptionMenuView(Context context) {
36 | this(context, null, 0);
37 | }
38 |
39 | public OptionMenuView(Context context, int itemRes) {
40 | this(context);
41 | if (itemRes != 0) mItemRes = itemRes;
42 | }
43 |
44 | public OptionMenuView(Context context, AttributeSet attrs) {
45 | this(context, attrs, 0);
46 | }
47 |
48 | public OptionMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
49 | super(context, attrs, defStyleAttr);
50 | initialize(context, attrs, defStyleAttr);
51 | }
52 |
53 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
54 | public OptionMenuView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
55 | super(context, attrs, defStyleAttr, defStyleRes);
56 | initialize(context, attrs, defStyleAttr);
57 | }
58 |
59 | private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
60 | mOptionMenus = new ArrayList<>();
61 | }
62 |
63 | public void setOptionMenus(List optionMenus) {
64 | mOptionMenus.clear();
65 | if (optionMenus != null) {
66 | mOptionMenus.addAll(optionMenus);
67 | }
68 | notifyMenusChange();
69 | }
70 |
71 | public List getOptionMenus() {
72 | return mOptionMenus;
73 | }
74 |
75 | @Override
76 | public void setOrientation(int orientation) {
77 | mNeedLayout = getOrientation() != orientation;
78 | super.setOrientation(orientation);
79 | }
80 |
81 | @Override
82 | public void requestLayout() {
83 | if (mNeedLayout) {
84 | mNeedLayout = false;
85 | resetPadding();
86 | int width = LayoutParams.MATCH_PARENT;
87 | int height = LayoutParams.MATCH_PARENT;
88 | int leftMargin = 0, topMargin = 0;
89 | if (getOrientation() == HORIZONTAL) {
90 | width = LayoutParams.WRAP_CONTENT;
91 | leftMargin = 1;
92 | } else {
93 | height = LayoutParams.WRAP_CONTENT;
94 | topMargin = 1;
95 | }
96 | int count = getChildCount();
97 | for (int i = 0; i < count; i++) {
98 | View view = getChildAt(i);
99 | LayoutParams params = (LayoutParams) view.getLayoutParams();
100 | if (params != null) {
101 | params.width = width;
102 | params.height = height;
103 | params.topMargin = topMargin;
104 | params.leftMargin = leftMargin;
105 | }
106 | }
107 | if (count > 0) {
108 | View view = getChildAt(0);
109 | LayoutParams params = (LayoutParams) view.getLayoutParams();
110 | if (params != null) {
111 | params.leftMargin = params.topMargin = 0;
112 | }
113 | }
114 | }
115 | super.requestLayout();
116 | }
117 |
118 | public void inflate(int menuRes, Menu menu) {
119 | MenuInflater inflater = new MenuInflater(getContext());
120 | inflater.inflate(menuRes, menu);
121 | mOptionMenus.clear();
122 | int size = menu.size();
123 | for (int i = 0; i < size; i++) {
124 | mOptionMenus.add(new OptionMenu(menu.getItem(i)));
125 | }
126 | notifyMenusChange();
127 | }
128 |
129 | public void notifyMenusChange() {
130 | adjustChildCount(mOptionMenus.size());
131 | int size = Math.min(mOptionMenus.size(), getChildCount());
132 | for (int i = 0; i < size; i++) {
133 | CheckedTextView textView = (CheckedTextView) getChildAt(i);
134 | textView.setTag(String.valueOf(i));
135 | mOptionMenus.get(i).validate(textView);
136 | }
137 | }
138 |
139 | private void adjustChildCount(int targetCount) {
140 | boolean needResetPadding = false;
141 | int count = getChildCount();
142 | if (count < targetCount) {
143 | for (int i = 0; i < targetCount; i++) {
144 | if (i < count) {
145 | View view = getChildAt(i);
146 | if (view.getVisibility() != VISIBLE) {
147 | view.setVisibility(VISIBLE);
148 | needResetPadding = true;
149 | }
150 | } else {
151 | addView(newMenuItemView());
152 | needResetPadding = true;
153 | }
154 | }
155 | } else {
156 | for (int i = 0; i < count; i++) {
157 | View view = getChildAt(i);
158 | if (i < targetCount) {
159 | if (view.getVisibility() != VISIBLE) {
160 | view.setVisibility(VISIBLE);
161 | needResetPadding = true;
162 | }
163 | } else {
164 | if (view.getVisibility() != GONE) {
165 | view.setVisibility(GONE);
166 | needResetPadding = true;
167 | }
168 | }
169 | }
170 | }
171 | if (needResetPadding) {
172 | resetPadding();
173 | }
174 | }
175 |
176 | private OptionItemView newMenuItemView() {
177 | OptionItemView itemView = (OptionItemView) inflate(getContext(), mItemRes, null);
178 | LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
179 | if (getOrientation() == HORIZONTAL) {
180 | if (getChildCount() > 0) {
181 | params.leftMargin = 1;
182 | } else {
183 | mLeftPadding = itemView.getPaddingLeft();
184 | mTopPadding = itemView.getPaddingTop();
185 | mRightPadding = itemView.getPaddingRight();
186 | mBottomPadding = itemView.getPaddingBottom();
187 | }
188 | params.height = LayoutParams.MATCH_PARENT;
189 | } else {
190 | if (getChildCount() > 0) {
191 | params.topMargin = 1;
192 | } else {
193 | mLeftPadding = itemView.getPaddingLeft();
194 | mTopPadding = itemView.getPaddingTop();
195 | mRightPadding = itemView.getPaddingRight();
196 | mBottomPadding = itemView.getPaddingBottom();
197 | }
198 | params.width = LayoutParams.MATCH_PARENT;
199 | }
200 | itemView.setLayoutParams(params);
201 | itemView.setOnClickListener(this);
202 | return itemView;
203 | }
204 |
205 | private void resetPadding() {
206 | int count = Math.min(getChildCount(), mOptionMenus.size());
207 | if (count > 0) {
208 | if (count == 1) {
209 | // 只有一个时设置四周边界
210 | getChildAt(0).setPadding(mLeftPadding + mBulgeSize, mTopPadding + mBulgeSize,
211 | mRightPadding + mBulgeSize, mBottomPadding + mBulgeSize);
212 | } else {
213 | View first = getChildAt(0);
214 | View last = getChildAt(count - 1);
215 | if (getOrientation() == HORIZONTAL) {
216 | first.setPadding(mLeftPadding + mBulgeSize, mTopPadding + mBulgeSize,
217 | mRightPadding, mBottomPadding + mBulgeSize);
218 |
219 | last.setPadding(mLeftPadding, mTopPadding + mBulgeSize,
220 | mRightPadding + mBulgeSize, mBottomPadding + mBulgeSize);
221 |
222 | for (int i = 1; i < count - 1; i++) {
223 | getChildAt(i).setPadding(mLeftPadding, mTopPadding + mBulgeSize,
224 | mRightPadding, mBottomPadding + mBulgeSize);
225 | }
226 | } else {
227 | first.setPadding(mLeftPadding + mBulgeSize, mTopPadding + mBulgeSize,
228 | mRightPadding + mBulgeSize, mBottomPadding);
229 | last.setPadding(mLeftPadding + mBulgeSize, mTopPadding,
230 | mRightPadding + mBulgeSize, mBottomPadding + mBulgeSize);
231 | for (int i = 1; i < count - 1; i++) {
232 | getChildAt(i).setPadding(mLeftPadding + mBulgeSize, mTopPadding,
233 | mRightPadding + mBulgeSize, mBottomPadding);
234 | }
235 | }
236 | }
237 | }
238 | }
239 |
240 | @Override
241 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
242 | super.onMeasure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
243 | }
244 |
245 | @Override
246 | public void onBulgeChanged(int site, int size) {
247 | if (size != mBulgeSize) {
248 | mBulgeSize = size;
249 | resetPadding();
250 | }
251 | }
252 |
253 | public void setOnOptionMenuClickListener(OnOptionMenuClickListener listener) {
254 | mMenuClickListener = listener;
255 | }
256 |
257 | @Override
258 | public void onClick(View v) {
259 | if (v.getTag() != null) {
260 | try {
261 | int position = Integer.parseInt((String) v.getTag());
262 | if (mMenuClickListener != null && position >= 0 && position < mOptionMenus.size()) {
263 | mMenuClickListener.onOptionMenuClick(position, mOptionMenus.get(position));
264 | }
265 | } catch (Exception e) {
266 | Log.d(TAG, e.getMessage(), e);
267 | }
268 | }
269 | }
270 |
271 | public interface OnOptionMenuClickListener {
272 |
273 | boolean onOptionMenuClick(int position, OptionMenu menu);
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/PopHorizontalScrollView.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.HorizontalScrollView;
7 |
8 | import me.kareluo.ui.PopLayout.OnBulgeChangeCallback;
9 |
10 | /**
11 | * Created by felix on 17/1/10.
12 | */
13 | public class PopHorizontalScrollView extends HorizontalScrollView implements OnBulgeChangeCallback {
14 |
15 | public PopHorizontalScrollView(Context context) {
16 | super(context);
17 | }
18 |
19 | public PopHorizontalScrollView(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public PopHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
24 | super(context, attrs, defStyleAttr);
25 | }
26 |
27 | @Override
28 | public void onBulgeChanged(int site, int size) {
29 | if (getChildCount() > 0) {
30 | View view = getChildAt(0);
31 | if (view instanceof OnBulgeChangeCallback) {
32 | ((OnBulgeChangeCallback) view).onBulgeChanged(site, size);
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/PopLayout.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.graphics.Path;
11 | import android.graphics.PorterDuff;
12 | import android.graphics.PorterDuffXfermode;
13 | import android.graphics.RectF;
14 | import android.graphics.Xfermode;
15 | import android.os.Build;
16 | import android.util.AttributeSet;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.widget.FrameLayout;
20 |
21 | /**
22 | * Created by felix on 16/11/20.
23 | */
24 | public class PopLayout extends FrameLayout implements View.OnLayoutChangeListener {
25 | private static final String TAG = "PopLayout";
26 |
27 | private int mOffset = 0;
28 |
29 | private int mRadiusSize = DEFAULT_RADIUS;
30 |
31 | private int mBulgeSize = DEFAULT_BULGE_SIZE;
32 |
33 | private Paint mPaint;
34 |
35 | private Path mPopMaskPath;
36 |
37 | private Path mBulgePath;
38 |
39 | private Path mDestBulgePath;
40 |
41 | private Matrix mCornuteMatrix;
42 |
43 | private int mSiteMode = SITE_BOTTOM;
44 |
45 | public static final int SITE_TOP = 0;
46 |
47 | public static final int SITE_LEFT = 1;
48 |
49 | public static final int SITE_RIGHT = 2;
50 |
51 | public static final int SITE_BOTTOM = 3;
52 |
53 | private static final int DEFAULT_RADIUS = 16;
54 |
55 | private static final int DEFAULT_BULGE_SIZE = 16;
56 |
57 | private static final Xfermode MODE = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
58 |
59 | public PopLayout(Context context) {
60 | this(context, null, 0);
61 | }
62 |
63 | public PopLayout(Context context, AttributeSet attrs) {
64 | this(context, attrs, 0);
65 | }
66 |
67 | public PopLayout(Context context, AttributeSet attrs, int defStyleAttr) {
68 | super(context, attrs, defStyleAttr);
69 | initialize(context, attrs, defStyleAttr);
70 | }
71 |
72 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
73 | public PopLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
74 | super(context, attrs, defStyleAttr, defStyleRes);
75 | initialize(context, attrs, defStyleAttr);
76 | }
77 |
78 | private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
79 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PopLayout);
80 | mSiteMode = a.getInt(R.styleable.PopLayout_siteMode, SITE_BOTTOM);
81 | mRadiusSize = a.getDimensionPixelSize(R.styleable.PopLayout_radiusSize,
82 | getResources().getDimensionPixelSize(R.dimen.pop_radius));
83 | mBulgeSize = a.getDimensionPixelSize(R.styleable.PopLayout_bulgeSize,
84 | getResources().getDimensionPixelSize(R.dimen.bulge_size));
85 | mOffset = a.getDimensionPixelSize(R.styleable.PopLayout_offsetSize, 0);
86 | a.recycle();
87 |
88 | if (getBackground() == null) {
89 | // 需要设置背景,可能是因为没有背景Layout就不会去执行绘制操作
90 | setBackgroundColor(Color.TRANSPARENT);
91 | }
92 |
93 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
94 | mPaint.setXfermode(MODE);
95 |
96 | mBulgePath = new Path();
97 | mPopMaskPath = new Path();
98 | mDestBulgePath = new Path();
99 | mCornuteMatrix = new Matrix();
100 |
101 | resetBulge();
102 | resetMask();
103 | onBulgeChange();
104 | addOnLayoutChangeListener(this);
105 | }
106 |
107 | private void resetBulge() {
108 | mBulgePath.reset();
109 | mBulgePath.lineTo(mBulgeSize << 1, 0);
110 | mBulgePath.lineTo(mBulgeSize, mBulgeSize);
111 | mBulgePath.close();
112 | }
113 |
114 | @Override
115 | public void addView(View child, int index, ViewGroup.LayoutParams params) {
116 | super.addView(child, index, params);
117 | onBulgeChange();
118 | }
119 |
120 | private void onBulgeChange() {
121 | if (getChildCount() > 0) {
122 | View view = getChildAt(0);
123 | if (view instanceof OnBulgeChangeCallback) {
124 | ((OnBulgeChangeCallback) view).onBulgeChanged(mSiteMode, mBulgeSize);
125 | }
126 | }
127 | }
128 |
129 | private void resetMask() {
130 | mPopMaskPath.reset();
131 | int width = getMeasuredWidth(), height = getMeasuredHeight();
132 | if (width <= mRadiusSize || height <= mRadiusSize) return;
133 | int offset = reviseOffset(mOffset);
134 | mPopMaskPath.addRect(new RectF(0, 0, width, height), Path.Direction.CW);
135 | mPopMaskPath.addRoundRect(new RectF(mBulgeSize, mBulgeSize, width - mBulgeSize, height - mBulgeSize), mRadiusSize, mRadiusSize, Path.Direction.CCW);
136 | mPopMaskPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
137 |
138 | switch (mSiteMode) {
139 | case SITE_TOP:
140 | mCornuteMatrix.setRotate(180, mBulgeSize, 0);
141 | mCornuteMatrix.postTranslate(0, mBulgeSize);
142 | mBulgePath.transform(mCornuteMatrix, mDestBulgePath);
143 | mPopMaskPath.addPath(mDestBulgePath, offset - mBulgeSize, 0);
144 | break;
145 | case SITE_LEFT:
146 | mCornuteMatrix.setRotate(90, mBulgeSize, 0);
147 | mBulgePath.transform(mCornuteMatrix, mDestBulgePath);
148 | mPopMaskPath.addPath(mDestBulgePath, 0, offset);
149 | break;
150 | case SITE_RIGHT:
151 | mCornuteMatrix.setRotate(-90, mBulgeSize, 0);
152 | mCornuteMatrix.postTranslate(-mBulgeSize, 0);
153 | mBulgePath.transform(mCornuteMatrix, mDestBulgePath);
154 | mPopMaskPath.addPath(mDestBulgePath, width - mBulgeSize, offset);
155 | break;
156 | case SITE_BOTTOM:
157 | mCornuteMatrix.setTranslate(-mBulgeSize, 0);
158 | mBulgePath.transform(mCornuteMatrix, mDestBulgePath);
159 | mPopMaskPath.addPath(mDestBulgePath, offset, height - mBulgeSize);
160 | break;
161 | }
162 | }
163 |
164 | private int reviseOffset(int offset) {
165 | int size = 0, bulgeWidth = mBulgeSize << 1;
166 | switch (mSiteMode) {
167 | case SITE_TOP:
168 | case SITE_BOTTOM:
169 | size = getWidth();
170 | break;
171 | case SITE_LEFT:
172 | case SITE_RIGHT:
173 | size = getHeight();
174 | break;
175 | }
176 | offset = Math.max(offset, mRadiusSize + bulgeWidth);
177 | if (size > 0) {
178 | offset = Math.min(offset, size - mRadiusSize - bulgeWidth);
179 | if (mRadiusSize + bulgeWidth > offset) {
180 | offset = size >> 1;
181 | }
182 | }
183 | return offset;
184 | }
185 |
186 | public void setOffset(int offset) {
187 | if (mOffset != offset) {
188 | mOffset = offset;
189 | resetMask();
190 | postInvalidate();
191 | }
192 | }
193 |
194 | public void setRadiusSize(int radius) {
195 | if (mRadiusSize != radius) {
196 | mRadiusSize = radius;
197 | resetMask();
198 | postInvalidate();
199 | }
200 | }
201 |
202 | public void setBulgeSize(int size) {
203 | if (mBulgeSize != size) {
204 | mBulgeSize = size;
205 | resetBulge();
206 | resetMask();
207 | postInvalidate();
208 | }
209 | }
210 |
211 | public void setSiteMode(int mode) {
212 | if (mSiteMode != mode) {
213 | mSiteMode = mode;
214 | onBulgeChange();
215 | resetMask();
216 | postInvalidate();
217 | }
218 | }
219 |
220 | public int getOffset() {
221 | return mOffset;
222 | }
223 |
224 | public int getRadiusSize() {
225 | return mRadiusSize;
226 | }
227 |
228 | public int getBugleSize() {
229 | return mBulgeSize;
230 | }
231 |
232 | public int getSiteMode() {
233 | return mSiteMode;
234 | }
235 |
236 | @Override
237 | public void draw(Canvas canvas) {
238 | int layer = canvas.saveLayer(0, 0, canvas.getWidth(),
239 | canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
240 | super.draw(canvas);
241 | canvas.drawPath(mPopMaskPath, mPaint);
242 | canvas.restoreToCount(layer);
243 | }
244 |
245 | @Override
246 | public void onLayoutChange(View v, int left, int top, int right, int bottom,
247 | int oldLeft, int oldTop, int oldRight, int oldBottom) {
248 | resetMask();
249 | postInvalidate();
250 | }
251 |
252 | public interface OnBulgeChangeCallback {
253 |
254 | void onBulgeChanged(int site, int size);
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/PopVerticalScrollView.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.ScrollView;
7 |
8 | import me.kareluo.ui.PopLayout.OnBulgeChangeCallback;
9 |
10 | /**
11 | * Created by felix on 17/1/10.
12 | */
13 | public class PopVerticalScrollView extends ScrollView implements OnBulgeChangeCallback {
14 |
15 | public PopVerticalScrollView(Context context) {
16 | super(context);
17 | }
18 |
19 | public PopVerticalScrollView(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public PopVerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
24 | super(context, attrs, defStyleAttr);
25 | }
26 |
27 | @Override
28 | public void onBulgeChanged(int site, int size) {
29 | if (getChildCount() > 0) {
30 | View view = getChildAt(0);
31 | if (view instanceof OnBulgeChangeCallback) {
32 | ((OnBulgeChangeCallback) view).onBulgeChanged(site, size);
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/PopupMenuView.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.content.Context;
4 | import android.graphics.Point;
5 | import android.graphics.Rect;
6 | import android.view.Menu;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.FrameLayout;
10 | import android.widget.HorizontalScrollView;
11 | import android.widget.LinearLayout;
12 | import android.widget.ScrollView;
13 |
14 | import java.util.List;
15 |
16 | /**
17 | * Created by felix on 16/11/18.
18 | */
19 | public class PopupMenuView extends PopupView implements OptionMenuView.OnOptionMenuClickListener {
20 |
21 | private PopLayout mPopLayout;
22 |
23 | private OptionMenuView mOptionMenuView;
24 |
25 | private PopVerticalScrollView mVerticalScrollView;
26 |
27 | private PopHorizontalScrollView mHorizontalScrollView;
28 |
29 | private OptionMenuView.OnOptionMenuClickListener mOnOptionMenuClickListener;
30 |
31 | public PopupMenuView(Context context) {
32 | this(context, 0);
33 | }
34 |
35 | public PopupMenuView(Context context, int menuRes) {
36 | super(context);
37 | mOptionMenuView = new OptionMenuView(context, menuRes);
38 | mOptionMenuView.setOnOptionMenuClickListener(this);
39 | mPopLayout = new PopLayout(context);
40 | ViewGroup scrollView = getScrollView(mOptionMenuView.getOrientation());
41 | scrollView.addView(mOptionMenuView);
42 | mPopLayout.addView(scrollView);
43 | setContentView(mPopLayout);
44 | }
45 |
46 | public PopupMenuView(Context context, int menuRes, Menu menu) {
47 | this(context);
48 | inflate(menuRes, menu);
49 | }
50 |
51 | public void inflate(int menuRes, Menu menu) {
52 | mOptionMenuView.inflate(menuRes, menu);
53 | measureContentView();
54 | }
55 |
56 | public void setMenuItems(List optionMenus) {
57 | mOptionMenuView.setOptionMenus(optionMenus);
58 | measureContentView();
59 | }
60 |
61 | public List getMenuItems() {
62 | return mOptionMenuView.getOptionMenus();
63 | }
64 |
65 | public void setOrientation(int orientation) {
66 | mOptionMenuView.setOrientation(orientation);
67 | measureContentView();
68 | }
69 |
70 | public int getOrientation() {
71 | return mOptionMenuView.getOrientation();
72 | }
73 |
74 | // 暂时暴露出
75 | @Deprecated
76 | public PopLayout getPopLayout() {
77 | return mPopLayout;
78 | }
79 |
80 | // 暂时暴露出
81 | @Deprecated
82 | public OptionMenuView getMenuView() {
83 | return mOptionMenuView;
84 | }
85 |
86 | public void setOnMenuClickListener(OptionMenuView.OnOptionMenuClickListener listener) {
87 | mOnOptionMenuClickListener = listener;
88 | }
89 |
90 | @Override
91 | public void show(View anchor, Rect frame, Point origin) {
92 | mOptionMenuView.notifyMenusChange();
93 | super.show(anchor, frame, origin);
94 | }
95 |
96 | @Override
97 | public void showAtTop(View anchor, Point origin, int xOff, int yOff) {
98 | mPopLayout.setSiteMode(PopLayout.SITE_BOTTOM);
99 | mPopLayout.setOffset(origin.x - xOff);
100 | super.showAtTop(anchor, origin, xOff, yOff);
101 | }
102 |
103 | @Override
104 | public void showAtLeft(View anchor, Point origin, int xOff, int yOff) {
105 | mPopLayout.setSiteMode(PopLayout.SITE_RIGHT);
106 | mPopLayout.setOffset(-origin.y - yOff);
107 | super.showAtLeft(anchor, origin, xOff, yOff);
108 | }
109 |
110 | @Override
111 | public void showAtRight(View anchor, Point origin, int xOff, int yOff) {
112 | mPopLayout.setSiteMode(PopLayout.SITE_LEFT);
113 | mPopLayout.setOffset(-origin.y - yOff);
114 | super.showAtRight(anchor, origin, xOff, yOff);
115 | }
116 |
117 | @Override
118 | public void showAtBottom(View anchor, Point origin, int xOff, int yOff) {
119 | mPopLayout.setSiteMode(PopLayout.SITE_TOP);
120 | mPopLayout.setOffset(origin.x - xOff);
121 | super.showAtBottom(anchor, origin, xOff, yOff);
122 | }
123 |
124 | @Override
125 | public boolean onOptionMenuClick(int position, OptionMenu menu) {
126 | if (mOnOptionMenuClickListener != null) {
127 | if (mOnOptionMenuClickListener.onOptionMenuClick(position, menu)) {
128 | dismiss();
129 | return true;
130 | }
131 | }
132 | return false;
133 | }
134 |
135 | private ViewGroup getScrollView(int orientation) {
136 | if (orientation == LinearLayout.HORIZONTAL) {
137 | if (mHorizontalScrollView == null) {
138 | mHorizontalScrollView = new PopHorizontalScrollView(getContext());
139 | mHorizontalScrollView.setHorizontalScrollBarEnabled(false);
140 | mHorizontalScrollView.setVerticalScrollBarEnabled(false);
141 | }
142 | return mHorizontalScrollView;
143 | } else {
144 | if (mVerticalScrollView == null) {
145 | mVerticalScrollView = new PopVerticalScrollView(getContext());
146 | mVerticalScrollView.setHorizontalScrollBarEnabled(false);
147 | mVerticalScrollView.setVerticalScrollBarEnabled(false);
148 | }
149 | return mVerticalScrollView;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/popmenu/src/main/java/me/kareluo/ui/PopupView.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.graphics.Point;
6 | import android.graphics.Rect;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.view.View;
9 | import android.view.View.MeasureSpec;
10 | import android.view.WindowManager.LayoutParams;
11 | import android.widget.PopupWindow;
12 |
13 | /**
14 | * Created by felix on 16/11/20.
15 | */
16 | public class PopupView extends PopupWindow {
17 | private static final String TAG = "PopupView";
18 |
19 | private int mSites = 0x9C;
20 |
21 | private Context mViewContext;
22 |
23 | public static final int SITE_TOP = 0;
24 |
25 | public static final int SITE_LEFT = 1;
26 |
27 | public static final int SITE_RIGHT = 2;
28 |
29 | public static final int SITE_BOTTOM = 3;
30 |
31 | public PopupView(Context context) {
32 | super(context);
33 | mViewContext = context;
34 | setWidth(LayoutParams.WRAP_CONTENT);
35 | setHeight(LayoutParams.WRAP_CONTENT);
36 | setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
37 | setFocusable(true);
38 | setOutsideTouchable(true);
39 | }
40 |
41 | public void measureContentView() {
42 | if (getContentView() != null) {
43 | getContentView().measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
44 | }
45 | }
46 |
47 | public void setSites(int sites) {
48 | mSites = sites;
49 | }
50 |
51 | public void setSites(int... sites) {
52 | if (sites != null) {
53 | mSites = 0;
54 | for (int i = 0; i < sites.length; i++) {
55 | mSites |= sites[i] << (i << 1);
56 | }
57 | }
58 | }
59 |
60 | public int getContentWidth() {
61 | return getContentView().getMeasuredWidth();
62 | }
63 |
64 | public int getContentHeight() {
65 | return getContentView().getMeasuredHeight();
66 | }
67 |
68 | public int[] reviseFrameAndOrigin(View anchor, Rect frame, Point origin) {
69 | int[] location = new int[2];
70 | anchor.getLocationInWindow(location);
71 |
72 | if (origin.x < 0 || origin.y < 0) {
73 | origin.set(anchor.getWidth() >> 1, anchor.getHeight() >> 1);
74 | }
75 |
76 | if (frame.isEmpty() || !frame.contains(origin.x + location[0], origin.y + location[1])) {
77 | anchor.getWindowVisibleDisplayFrame(frame);
78 | }
79 |
80 | return location;
81 | }
82 |
83 | public void show(View anchor) {
84 | show(anchor, null, null);
85 | }
86 |
87 | public void show(View anchor, Point origin) {
88 | show(anchor, null, origin);
89 | }
90 |
91 | public void show(View anchor, Rect frame) {
92 | show(anchor, frame, null);
93 | }
94 |
95 | public void show(View anchor, Rect frame, Point origin) {
96 | if (origin == null) origin = new Point(-1, -1);
97 | if (frame == null) frame = new Rect();
98 |
99 | int[] location = reviseFrameAndOrigin(anchor, frame, origin);
100 |
101 | int x = location[0], y = location[1];
102 | int width = anchor.getWidth(), height = anchor.getHeight();
103 | int contentWidth = getContentWidth(), contentHeight = getContentHeight();
104 |
105 | Point offset = getOffset(frame, new Rect(x, y + height, contentWidth + x,
106 | contentHeight + y + height), x + origin.x, y + origin.y);
107 |
108 | int sites = mSites;
109 | do {
110 | int site = sites & 0x03;
111 | switch (site) {
112 | case SITE_TOP:
113 | if (y - contentHeight > frame.top) {
114 | showAtTop(anchor, origin, offset.x, -height - contentHeight);
115 | return;
116 | }
117 | break;
118 | case SITE_LEFT:
119 | if (x - contentWidth > frame.left) {
120 | showAtLeft(anchor, origin, -contentWidth, offset.y);
121 | return;
122 | }
123 | break;
124 | case SITE_RIGHT:
125 | if (x + width + contentWidth < frame.right) {
126 | showAtRight(anchor, origin, width, offset.y);
127 | return;
128 | }
129 | break;
130 | case SITE_BOTTOM:
131 | if (y + height + contentHeight < frame.bottom) {
132 | showAtBottom(anchor, origin, offset.x, 0);
133 | return;
134 | }
135 | break;
136 | }
137 | if (sites <= 0) break;
138 | sites >>= 2;
139 | } while (sites >= 0);
140 | }
141 |
142 | private Point getOffset(Rect frame, Rect rect, int x, int y) {
143 | Rect rt = new Rect(rect);
144 | rt.offset(x - rt.centerX(), y - rt.centerY());
145 | if (!frame.contains(rt)) {
146 | int offsetX = 0, offsetY = 0;
147 | if (rt.bottom > frame.bottom) {
148 | offsetY = frame.bottom - rt.bottom;
149 | }
150 |
151 | if (rt.top < frame.top) {
152 | offsetY = frame.top - rt.top;
153 | }
154 |
155 | if (rt.right > frame.right) {
156 | offsetX = frame.right - rt.right;
157 | }
158 |
159 | if (rt.left < frame.left) {
160 | offsetX = frame.left - rt.left;
161 | }
162 |
163 | rt.offset(offsetX, offsetY);
164 | }
165 | return new Point(rt.left - rect.left, rt.top - rect.top);
166 | }
167 |
168 | public void showAtTop(View anchor, Point origin, int xOff, int yOff) {
169 | showAsDropDown(anchor, xOff, yOff);
170 | }
171 |
172 | public void showAtLeft(View anchor, Point origin, int xOff, int yOff) {
173 | showAsDropDown(anchor, xOff, yOff);
174 | }
175 |
176 | public void showAtRight(View anchor, Point origin, int xOff, int yOff) {
177 | showAsDropDown(anchor, xOff, yOff);
178 | }
179 |
180 | public void showAtBottom(View anchor, Point origin, int xOff, int yOff) {
181 | showAsDropDown(anchor, xOff, yOff);
182 | }
183 |
184 | protected Context getContext() {
185 | return mViewContext;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/popmenu/src/main/res/drawable/bg_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
--------------------------------------------------------------------------------
/popmenu/src/main/res/layout/layout_menu_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/popmenu/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/popmenu/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 7dp
4 | 7dp
5 |
--------------------------------------------------------------------------------
/popmenu/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
--------------------------------------------------------------------------------
/popmenu/src/test/java/me/kareluo/ui/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.kareluo.ui;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/screenshot/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/screenshot/code.png
--------------------------------------------------------------------------------
/screenshot/device-2016-11-22-173850.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/screenshot/device-2016-11-22-173850.png
--------------------------------------------------------------------------------
/screenshot/device-2016-11-22-174011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/screenshot/device-2016-11-22-174011.png
--------------------------------------------------------------------------------
/screenshot/device-2016-11-22-174803.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/screenshot/device-2016-11-22-174803.png
--------------------------------------------------------------------------------
/screenshot/preview_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/PopupMenuView/80d6775dbdd60f691edf88d4baab207408796f31/screenshot/preview_image.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':popmenu'
2 |
--------------------------------------------------------------------------------