├── .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 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 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 | 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 | ![预览图片](/screenshot/preview_image.png) 11 | 12 | # Demo 13 | 安装 [apk](https://www.pgyer.com/menu) 文件预览效果,或者通过下面二维码去下载安装: 14 | 15 | ![DEMO下载二维码](/screenshot/code.png) 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 |