├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── panxiaohe │ │ └── springboard │ │ └── demo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── panxiaohe │ │ │ └── springboard │ │ │ └── demo │ │ │ ├── ImageUtil.java │ │ │ ├── MainActivity.java │ │ │ ├── MyAdapter.java │ │ │ └── MyButtonItem.java │ └── res │ │ ├── drawable-xhdpi │ │ ├── detail_icon_back_normal.png │ │ ├── detail_icon_fav_normal.png │ │ ├── detail_icon_share_normal.png │ │ ├── ugly_edittext_bg.9.png │ │ └── ugly_folder_background.png │ │ ├── drawable │ │ ├── account_bg.png │ │ ├── folder_icon.png │ │ └── new_phone.png │ │ ├── layout │ │ ├── activity_main.xml │ │ └── item.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── panxiaohe │ └── springboard │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── panxiaohe │ │ └── springboard │ │ └── library │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── panxiaohe │ │ │ └── springboard │ │ │ └── library │ │ │ ├── FavoritesItem.java │ │ │ ├── FingerFlowViewManager.java │ │ │ ├── FolderView.java │ │ │ ├── MenuView.java │ │ │ ├── SpringboardAdapter.java │ │ │ └── SpringboardView.java │ └── res │ │ ├── anim │ │ ├── cycle_7.xml │ │ ├── shake.xml │ │ └── shake_rotate.xml │ │ ├── drawable-hdpi │ │ └── icon_delete.png │ │ ├── drawable │ │ └── folder_background.xml │ │ ├── layout │ │ └── folder_layout.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── style.xml │ └── test │ └── java │ └── com │ └── panxiaohe │ └── springboardlib │ └── ExampleUnitTest.java ├── pic └── screenShot1.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | .idea/ 18 | build/ 19 | /springboard.iml 20 | 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboard View 2 | this is a view that can drag sort buttons and merge buttons to a folder. 3 | 能够拖动排序菜单,和合并文件夹,删除按钮View。 4 | 效果类似桌面和招商手机银行app最爱菜单. 5 | # Features 6 | 1:drag sort the buttons.拖动排序 7 | 2:drag button into a folder.把按钮拖进文件夹 8 | 3:drag button out of a folder.把按钮拖出文件夹 9 | 4:delete buttons in menu and folder.能够删除菜单和文件夹中的按钮 10 | 5:rename the folder.文件夹重命名. 11 | # view 12 | ![image1](https://github.com/xiaohepan/springboard/blob/master/pic/screenShot1.gif) 13 | # How to Work with the Source 14 | 1:make your data model extends com.panxiaohe.springboard.library.FavoritesItem; 15 | 16 | 2:make your adapter extends com.panxiaohe.springboard.library.SpringboardAdapter; 17 | 18 | 3:set the adapter to com.panxiaohe.springboard.library.MenuView; 19 | 20 | 4:springboardAdapter.onDataChange() will notice you data has change 21 | (when sortted change,moved in or out of folder,deleted button,changed folder name) 22 | you should save the change. 23 | 24 | 5:func setOnPageChangedListener(),setOnItemClickListener() is usefull too; 25 | 26 | 6:the attrs of the view : 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | # notice 44 | this view is not well tested. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /app.iml 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.panxiaohe.springboard" 9 | minSdkVersion 16 10 | targetSdkVersion 23 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 fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.2.1' 26 | compile project(':library') 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/panxiaohe/Library/Android/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/com/panxiaohe/springboard/demo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.demo; 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 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/panxiaohe/springboard/demo/ImageUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.panxiaohe.springboard.demo; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | 8 | import android.content.Context; 9 | import android.graphics.Bitmap; 10 | import android.graphics.Bitmap.CompressFormat; 11 | import android.graphics.Bitmap.Config; 12 | import android.graphics.BitmapFactory; 13 | import android.graphics.Canvas; 14 | import android.graphics.Paint; 15 | import android.graphics.drawable.BitmapDrawable; 16 | import android.graphics.drawable.Drawable; 17 | import android.graphics.drawable.StateListDrawable; 18 | import android.util.Base64; 19 | 20 | /** 21 | * @author panxiaohe 22 | */ 23 | public class ImageUtil 24 | { 25 | 26 | private Context context; 27 | 28 | public ImageUtil(Context context) 29 | { 30 | this.context = context; 31 | } 32 | 33 | private static Drawable createDrawable(Drawable d, Paint p) 34 | { 35 | 36 | BitmapDrawable bd = (BitmapDrawable) d; 37 | Bitmap b = bd.getBitmap(); 38 | Bitmap bitmap = Bitmap.createBitmap(bd.getIntrinsicWidth(), bd.getIntrinsicHeight(), Config.ARGB_8888); 39 | Canvas canvas = new Canvas(bitmap); 40 | canvas.drawBitmap(b, 0, 0, p); // 关键代码,使用新的Paint画原图, 41 | 42 | return new BitmapDrawable(bitmap); 43 | } 44 | 45 | /** 46 | * 设置Selector。 本次只增加点击变暗的效果,注释的代码为更多的效果 47 | */ 48 | public static StateListDrawable createSLD(Drawable drawable) 49 | { 50 | StateListDrawable bg = new StateListDrawable(); 51 | Paint p = new Paint(); 52 | p.setColor(0x40222222); // Paint ARGB色值,A = 0x40 不透明。RGB222222 暗色 53 | 54 | Drawable normal = drawable; 55 | Drawable pressed = createDrawable(drawable, p); 56 | // p = new Paint(); 57 | // p.setColor(0x8000FF00); 58 | // Drawable focused = createDrawable(drawable, p); 59 | // p = new Paint(); 60 | // p.setColor(0x800000FF); 61 | // Drawable unable = createDrawable(drawable, p); 62 | // View.PRESSED_ENABLED_STATE_SET 63 | bg.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, pressed); 64 | // View.ENABLED_FOCUSED_STATE_SET 65 | // bg.addState(new int[] { android.R.attr.state_enabled, 66 | // android.R.attr.state_focused }, focused); 67 | // View.ENABLED_STATE_SET 68 | bg.addState(new int[]{android.R.attr.state_enabled}, normal); 69 | // View.FOCUSED_STATE_SET 70 | // bg.addState(new int[] { android.R.attr.state_focused }, focused); 71 | // // View.WINDOW_FOCUSED_STATE_SET 72 | // bg.addState(new int[] { android.R.attr.state_window_focused }, 73 | // unable); 74 | // View.EMPTY_STATE_SET 75 | bg.addState(new int[]{}, normal); 76 | return bg; 77 | } 78 | 79 | // /** 80 | // * 获取客户端本地图片 81 | // * */ 82 | // public static Drawable getImageDrawable(String imageName, Context context) { 83 | // Util util = new Util(context); 84 | // int id = util.getDrawableId(imageName); 85 | // if (id != 0) { 86 | // return context.getResources().getDrawable(id); 87 | // } else { 88 | // return null; 89 | // } 90 | // } 91 | // 92 | public static Drawable getStateListDrawable(Drawable background) 93 | { 94 | // TODO Auto-generated method stub 95 | return createSLD(background); 96 | } 97 | 98 | // /** 99 | // * 获取带点击效果的图片对象 100 | // * */ 101 | // public static StateListDrawable getStateListDrawable(int imageName, Context context) { 102 | // return getStateListDrawable(imageName, context); 103 | // } 104 | 105 | /** 106 | * @param imageName drawable的名称 107 | * @param context 包名 108 | * @return 109 | */ 110 | public static StateListDrawable getStateListDrawable(int imageName, Context context) 111 | { 112 | Drawable drawable; 113 | drawable = context.getResources().getDrawable(imageName); 114 | if (drawable != null) 115 | { 116 | return createSLD(drawable); 117 | } else 118 | { 119 | return null; 120 | } 121 | } 122 | 123 | /** 124 | * 将Bitmap转换成字符串 125 | * 126 | * @param bitmap 127 | * @return 128 | * @author guo_fenghua 129 | */ 130 | public static String bitmaptoString(Bitmap bitmap) 131 | { 132 | if (bitmap == null) 133 | { 134 | return ""; 135 | } 136 | String string = null; 137 | ByteArrayOutputStream bStream = new ByteArrayOutputStream(); 138 | bitmap.compress(CompressFormat.PNG, 100, bStream); 139 | byte[] bytes = bStream.toByteArray(); 140 | string = Base64.encodeToString(bytes, Base64.DEFAULT); 141 | return string; 142 | } 143 | 144 | /** 145 | * 将字符串转换成Bitmap类型 146 | * 147 | * @param string 148 | * @return 149 | * @author guo_fenghua 150 | */ 151 | public static Bitmap stringtoBitmap(String string) 152 | { 153 | Bitmap bitmap = null; 154 | try 155 | { 156 | byte[] bitmapArray; 157 | bitmapArray = Base64.decode(string, Base64.DEFAULT); 158 | bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length); 159 | } catch (Exception e) 160 | { 161 | e.printStackTrace(); 162 | } 163 | 164 | return bitmap; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /app/src/main/java/com/panxiaohe/springboard/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.demo; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | import com.panxiaohe.springboard.library.FavoritesItem; 7 | import com.panxiaohe.springboard.library.MenuView; 8 | import com.panxiaohe.springboard.library.SpringboardView; 9 | 10 | import java.util.ArrayList; 11 | 12 | public class MainActivity extends AppCompatActivity 13 | { 14 | 15 | private MenuView springboard; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) 19 | { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | springboard = (MenuView) findViewById(R.id.springboard); 23 | 24 | ArrayList buttons = new ArrayList(); 25 | buttons.add(new MyButtonItem("0", "item0", R.drawable.detail_icon_back_normal)); 26 | buttons.add(new MyButtonItem("1", "item1", R.drawable.detail_icon_fav_normal)); 27 | buttons.add(new MyButtonItem("2", "item2", R.drawable.detail_icon_share_normal)); 28 | buttons.add(new MyButtonItem("3", "item3", R.drawable.detail_icon_back_normal)); 29 | buttons.add(new MyButtonItem("4", "item4", R.drawable.detail_icon_fav_normal)); 30 | buttons.add(new MyButtonItem("5", "item5", R.drawable.detail_icon_share_normal)); 31 | buttons.add(new MyButtonItem("6", "item6", R.drawable.detail_icon_back_normal)); 32 | buttons.add(new MyButtonItem("7", "item7", R.drawable.detail_icon_fav_normal)); 33 | buttons.add(new MyButtonItem("8", "item8", R.drawable.detail_icon_share_normal)); 34 | buttons.add(new MyButtonItem("9", "item9", R.drawable.detail_icon_back_normal)); 35 | buttons.add(new MyButtonItem("10", "item10", R.drawable.detail_icon_fav_normal)); 36 | buttons.add(new MyButtonItem("11", "item11", R.drawable.detail_icon_share_normal)); 37 | buttons.add(new MyButtonItem("12", "item12", R.drawable.detail_icon_back_normal)); 38 | buttons.add(new MyButtonItem("13", "item13", R.drawable.detail_icon_back_normal)); 39 | buttons.add(new MyButtonItem("14", "item14", R.drawable.detail_icon_back_normal)); 40 | buttons.add(new MyButtonItem("15", "item15", R.drawable.detail_icon_back_normal)); 41 | buttons.add(new MyButtonItem("16", "item16", R.drawable.detail_icon_back_normal)); 42 | buttons.add(new MyButtonItem("17", "item17", R.drawable.detail_icon_back_normal)); 43 | buttons.add(new MyButtonItem("18", "item18", R.drawable.detail_icon_back_normal)); 44 | buttons.add(new MyButtonItem("19", "item19", R.drawable.detail_icon_back_normal)); 45 | buttons.add(new MyButtonItem("20", "item20", R.drawable.detail_icon_back_normal)); 46 | buttons.add(new MyButtonItem("21", "item21", R.drawable.detail_icon_back_normal)); 47 | buttons.add(new MyButtonItem("22", "item22", R.drawable.detail_icon_back_normal)); 48 | 49 | MyAdapter myAdapter = new MyAdapter(buttons); 50 | springboard.setAdapter(myAdapter); 51 | springboard.setOnItemClickListener(new SpringboardView.OnItemClickListener() 52 | { 53 | @Override 54 | public void onItemClick(FavoritesItem item) 55 | { 56 | MyButtonItem myItem = (MyButtonItem) item; 57 | // Toast.makeText(MainActivity.this, " button : " + myItem.getData().getName() + "is clicked", Toast.LENGTH_SHORT).show(); 58 | } 59 | }); 60 | 61 | springboard.setOnPageChangedListener(new SpringboardView.OnPageChangedListener() 62 | { 63 | @Override 64 | public void onPageScroll(int from, int to) 65 | { 66 | // Toast.makeText(MainActivity.this, " springboardview scroll from page#" + from + " to page#" + to, Toast.LENGTH_SHORT).show(); 67 | } 68 | 69 | @Override 70 | public void onPageCountChange(int oldCount, int newCount) 71 | { 72 | // Toast.makeText(MainActivity.this, "springboardview page count has changed from #" + oldCount + " to #" + newCount, Toast.LENGTH_SHORT).show(); 73 | } 74 | }); 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/panxiaohe/springboard/demo/MyAdapter.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.demo; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.graphics.drawable.StateListDrawable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.FrameLayout; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import com.panxiaohe.springboard.library.SpringboardAdapter; 15 | 16 | import java.util.ArrayList; 17 | 18 | /** 19 | * Created by panxiaohe on 16/3/20. 20 | * ff 21 | */ 22 | public class MyAdapter extends SpringboardAdapter 23 | { 24 | 25 | public MyAdapter(ArrayList items) 26 | { 27 | setItems(items); 28 | } 29 | 30 | @Override 31 | public FrameLayout initItemView(int position, ViewGroup parent) 32 | { 33 | return (FrameLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false); 34 | } 35 | 36 | @Override 37 | public void configItemView(int position, FrameLayout view) 38 | { 39 | MyButtonItem item = getItem(position); 40 | ImageView menu_icon = (ImageView) view.findViewById(R.id.menu_icon); 41 | // ImageView menu_redDot = (ImageView)view.findViewById(R.id.redDot); 42 | TextView menu_name = (TextView) view.findViewById(R.id.menu_name); 43 | // ImageView menu_del = (ImageView)view.findViewById(R.id.menu_del); 44 | LinearLayout folder = (LinearLayout) view.findViewById(R.id.folder); 45 | // FrameLayout menu_container = (FrameLayout) view.findViewById(R.id.menu_container); 46 | menu_name.setText(item.getActionName()); 47 | if (item.isFolder()) 48 | { 49 | // menu_container.setBackgroundResource(R.drawable.folder_icon); 50 | menu_icon.setVisibility(View.GONE); 51 | folder.setVisibility(View.VISIBLE); 52 | ImageView[] images = new ImageView[4]; 53 | images[0] = (ImageView) folder.findViewById(R.id.folder_button1); 54 | images[1] = (ImageView) folder.findViewById(R.id.folder_button2); 55 | images[2] = (ImageView) folder.findViewById(R.id.folder_button3); 56 | images[3] = (ImageView) folder.findViewById(R.id.folder_button4); 57 | for (int i = 0; i < 4; i++) 58 | { 59 | if (item.getSubItemCount() > i) 60 | { 61 | MyButtonItem button = item.getSubItem(i); 62 | Drawable drawable = view.getResources().getDrawable(button.getIcon()); 63 | images[i].setImageDrawable(drawable); 64 | images[i].setVisibility(View.VISIBLE); 65 | } else 66 | { 67 | images[i].setVisibility(View.INVISIBLE); 68 | } 69 | } 70 | } else 71 | { 72 | // menu_container.setBackgroundResource(R.drawable.folder_icon); 73 | // menu_container.setBackgroundResource(0); 74 | menu_icon.setVisibility(View.VISIBLE); 75 | folder.setVisibility(View.GONE); 76 | StateListDrawable drawable = ImageUtil.getStateListDrawable(item.getIcon(), view.getContext()); 77 | menu_icon.setImageDrawable(drawable); 78 | } 79 | } 80 | 81 | @Override 82 | public FrameLayout initSubItemView(int folderPosition, int position, ViewGroup parent) 83 | { 84 | return (FrameLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false); 85 | } 86 | 87 | @Override 88 | public void configSubItemView(int folderPosition, int position, FrameLayout view) 89 | { 90 | MyButtonItem item = getSubItem(folderPosition, position); 91 | ImageView menu_icon = (ImageView) view.findViewById(R.id.menu_icon); 92 | // ImageView menu_redDot = (ImageView)view.findViewById(R.id.redDot); 93 | TextView menu_name = (TextView) view.findViewById(R.id.menu_name); 94 | // ImageView menu_del = (ImageView)view.findViewById(R.id.menu_del); 95 | LinearLayout folder = (LinearLayout) view.findViewById(R.id.folder); 96 | // FrameLayout menu_container = (FrameLayout) view.findViewById(R.id.menu_container); 97 | menu_name.setText(item.getActionName()); 98 | if (item.isFolder()) 99 | { 100 | // menu_container.setBackgroundResource(R.drawable.folder_icon); 101 | menu_icon.setVisibility(View.GONE); 102 | folder.setVisibility(View.VISIBLE); 103 | ImageView[] images = new ImageView[4]; 104 | images[0] = (ImageView) folder.findViewById(R.id.folder_button1); 105 | images[1] = (ImageView) folder.findViewById(R.id.folder_button2); 106 | images[2] = (ImageView) folder.findViewById(R.id.folder_button3); 107 | images[3] = (ImageView) folder.findViewById(R.id.folder_button4); 108 | for (int i = 0; i < 4; i++) 109 | { 110 | if (item.getSubItemCount() > i) 111 | { 112 | MyButtonItem button = item.getSubItem(i); 113 | Drawable drawable = view.getResources().getDrawable(button.getIcon()); 114 | images[i].setImageDrawable(drawable); 115 | images[i].setVisibility(View.VISIBLE); 116 | } else 117 | { 118 | images[i].setVisibility(View.INVISIBLE); 119 | } 120 | } 121 | } else 122 | { 123 | // menu_container.setBackgroundResource(R.drawable.folder_icon); 124 | // menu_container.setBackgroundResource(0); 125 | menu_icon.setVisibility(View.VISIBLE); 126 | folder.setVisibility(View.GONE); 127 | StateListDrawable drawable = ImageUtil.getStateListDrawable(item.getIcon(), view.getContext()); 128 | menu_icon.setImageDrawable(drawable); 129 | } 130 | } 131 | 132 | @Override 133 | public boolean ifCanMerge(int fromPosition, int toPosition) 134 | { 135 | return super.ifCanMerge(fromPosition, toPosition); 136 | } 137 | 138 | @Override 139 | public void onDataChange() 140 | { 141 | Toast.makeText(getSpringboardView().getContext(), "数据有变化", Toast.LENGTH_SHORT).show(); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/panxiaohe/springboard/demo/MyButtonItem.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.demo; 2 | 3 | import com.panxiaohe.springboard.library.FavoritesItem; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Created by panxiaohe on 16/3/20. 9 | * 继承 10 | */ 11 | public class MyButtonItem extends FavoritesItem 12 | { 13 | 14 | private String actionName; 15 | 16 | private String actionId; 17 | 18 | private int icon; 19 | 20 | private ArrayList menuList; 21 | 22 | 23 | @Override 24 | public void setActionName(String actionName) 25 | { 26 | this.actionName = actionName; 27 | } 28 | 29 | @Override 30 | public String getActionName() 31 | { 32 | return actionName; 33 | } 34 | 35 | @Override 36 | public void setActionId(String actionId) 37 | { 38 | this.actionId = actionId; 39 | } 40 | 41 | @Override 42 | public String getActionId() 43 | { 44 | return actionId; 45 | } 46 | 47 | @Override 48 | public boolean isFolder() 49 | { 50 | return menuList != null; 51 | } 52 | 53 | @Override 54 | public void addSubButton(FavoritesItem item, String defaultFolderName) 55 | { 56 | 57 | if (menuList == null || menuList.size() == 0) 58 | { 59 | menuList = new ArrayList<>(); 60 | MyButtonItem newItem = new MyButtonItem(); 61 | newItem.copy(this); 62 | menuList.add(newItem); 63 | setActionId("-1"); 64 | setActionName(defaultFolderName); 65 | } 66 | menuList.add((MyButtonItem) item); 67 | } 68 | 69 | @Override 70 | protected void addSubItem(int position, FavoritesItem item) 71 | { 72 | menuList.add(position, (MyButtonItem) item); 73 | } 74 | 75 | @Override 76 | protected MyButtonItem removeSubItem(int position) 77 | { 78 | return menuList.remove(position); 79 | } 80 | 81 | @Override 82 | public void removeSubButton(int position) 83 | { 84 | menuList.remove(position); 85 | 86 | if (menuList.size() == 1) 87 | { 88 | copy(menuList.get(0)); 89 | menuList = null; 90 | } 91 | } 92 | 93 | private void copy(MyButtonItem myButtonItem) 94 | { 95 | this.actionName = myButtonItem.actionName; 96 | this.actionId = myButtonItem.actionId; 97 | this.icon = myButtonItem.icon; 98 | } 99 | 100 | @Override 101 | public int getSubItemCount() 102 | { 103 | return menuList == null ? 0 : menuList.size(); 104 | } 105 | 106 | @Override 107 | public MyButtonItem getSubItem(int position) 108 | { 109 | if (menuList != null) 110 | { 111 | if (menuList.size() > position) 112 | { 113 | return menuList.get(position); 114 | } 115 | 116 | } 117 | throw new IllegalStateException("按钮列表是空"); 118 | } 119 | 120 | public int getIcon() 121 | { 122 | return icon; 123 | } 124 | 125 | public MyButtonItem(String actionId, String actionName, int icon) 126 | { 127 | this.actionName = actionName; 128 | this.actionId = actionId; 129 | this.icon = icon; 130 | } 131 | 132 | public MyButtonItem() 133 | { 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/detail_icon_back_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable-xhdpi/detail_icon_back_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/detail_icon_fav_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable-xhdpi/detail_icon_fav_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/detail_icon_share_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable-xhdpi/detail_icon_share_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ugly_edittext_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable-xhdpi/ugly_edittext_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ugly_folder_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable-xhdpi/ugly_folder_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/account_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable/account_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/folder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable/folder_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/new_phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/drawable/new_phone.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 22 | 23 | 31 | 32 | 38 | 39 | 44 | 45 | 51 | 52 | 59 | 60 | 61 | 62 | 67 | 68 | 74 | 75 | 82 | 83 | 84 | 85 | 86 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 117 | 118 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/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 | #99cdcdcd 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SpringboardDemo 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/panxiaohe/springboard/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard; 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:1.5.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /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/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 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.8-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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /library.iml 3 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:23.2.1' 25 | } 26 | -------------------------------------------------------------------------------- /library/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/panxiaohe/Library/Android/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 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/panxiaohe/springboard/library/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 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 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/library/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /library/src/main/java/com/panxiaohe/springboard/library/FavoritesItem.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 2 | 3 | 4 | import java.io.Serializable; 5 | 6 | /** 7 | * item model 8 | * 9 | * @author panxiaohe 10 | */ 11 | public abstract class FavoritesItem implements Serializable 12 | { 13 | 14 | public abstract boolean isFolder(); 15 | 16 | public abstract String getActionName(); 17 | 18 | public abstract void setActionName(String name); 19 | 20 | public abstract void setActionId(String id); 21 | 22 | /** 23 | * 如果this是文件夹,返回"-1" 24 | * 否则请返回该按钮的唯一识别. 25 | */ 26 | public abstract String getActionId(); 27 | 28 | /** 29 | * 从文件夹中删除按钮 30 | */ 31 | public abstract void removeSubButton(int position); 32 | 33 | public abstract void addSubButton(FavoritesItem item, String defaultFolderName); 34 | 35 | protected abstract void addSubItem(int position, FavoritesItem item); 36 | 37 | protected abstract FavoritesItem removeSubItem(int position); 38 | 39 | public abstract int getSubItemCount(); 40 | 41 | public abstract FavoritesItem getSubItem(int position); 42 | 43 | @Override 44 | public int hashCode() 45 | { 46 | final int prime = 31; 47 | int result = 1; 48 | String actionId = getActionId(); 49 | result = prime * result + ((actionId == null) ? 0 : actionId.hashCode()); 50 | return result; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object obj) 55 | { 56 | if (this == obj) 57 | return true; 58 | if (obj == null) 59 | return false; 60 | if (getClass() != obj.getClass()) 61 | return false; 62 | FavoritesItem other = (FavoritesItem) obj; 63 | String actionId = getActionId(); 64 | if (actionId == null) 65 | { 66 | if (other.getActionId() != null) 67 | return false; 68 | } else if (!actionId.equals(other.getActionId())) 69 | return false; 70 | return true; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /library/src/main/java/com/panxiaohe/springboard/library/FingerFlowViewManager.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.PixelFormat; 6 | import android.view.Gravity; 7 | import android.view.WindowManager; 8 | import android.view.ViewGroup.LayoutParams; 9 | import android.widget.ImageView; 10 | 11 | /** 12 | * Created by panxiaohe on 16/3/9. 13 | * 14 | */ 15 | public class FingerFlowViewManager 16 | { 17 | 18 | private static FingerFlowViewManager instance; 19 | 20 | private FingerFlowViewManager() 21 | { 22 | super(); 23 | } 24 | 25 | public static FingerFlowViewManager getInstance() 26 | { 27 | if (instance == null) 28 | { 29 | instance = new FingerFlowViewManager(); 30 | } 31 | return instance; 32 | } 33 | 34 | public WindowManager init(Context context) 35 | { 36 | if (figerFlowWindowManager == null) 37 | { 38 | figerFlowWindowManager = (WindowManager) context.getSystemService( 39 | Context.WINDOW_SERVICE); 40 | } 41 | return figerFlowWindowManager; 42 | } 43 | 44 | public void setUp(Context context, Bitmap bitmap, int x, int y) 45 | { 46 | 47 | WindowManager.LayoutParams windowParams = getLayoutParams(); 48 | 49 | windowParams.x = x; 50 | 51 | windowParams.y = y; 52 | 53 | getDrawImageView(context).setImageBitmap(bitmap); 54 | 55 | init(context).addView(drawImageView, windowParams); 56 | } 57 | 58 | public ImageView getDrawImageView(Context context) 59 | { 60 | 61 | if (drawImageView == null) 62 | { 63 | drawImageView = new ImageView(context); 64 | } 65 | 66 | return drawImageView; 67 | } 68 | 69 | public void updatePosition(int x, int y) 70 | { 71 | 72 | WindowManager.LayoutParams windowParams = getLayoutParams(); 73 | 74 | windowParams.x = x; 75 | 76 | windowParams.y = y; 77 | 78 | figerFlowWindowManager.updateViewLayout(drawImageView, windowParams); 79 | } 80 | 81 | public void remove() 82 | { 83 | figerFlowWindowManager.removeView(drawImageView); 84 | drawImageView = null; 85 | } 86 | 87 | 88 | private WindowManager figerFlowWindowManager; 89 | 90 | private ImageView drawImageView; 91 | 92 | private WindowManager.LayoutParams windowParams; 93 | 94 | private WindowManager.LayoutParams getLayoutParams() 95 | { 96 | if (windowParams == null) 97 | { 98 | windowParams = new WindowManager.LayoutParams(); 99 | windowParams.gravity = Gravity.TOP | Gravity.START; 100 | windowParams.height = LayoutParams.WRAP_CONTENT; 101 | windowParams.width = LayoutParams.WRAP_CONTENT; 102 | windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 103 | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 104 | | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 105 | | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 106 | windowParams.format = PixelFormat.TRANSLUCENT; 107 | windowParams.windowAnimations = 0; 108 | windowParams.alpha = 0.8f; 109 | } 110 | return windowParams; 111 | } 112 | 113 | 114 | public static void onDestory() 115 | { 116 | instance = null; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /library/src/main/java/com/panxiaohe/springboard/library/FolderView.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.KeyEvent; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.widget.FrameLayout; 9 | 10 | 11 | /** 12 | * Created by panxiaohe on 16/3/9. 13 | * 展示文件夹的View 14 | */ 15 | public class FolderView extends SpringboardView 16 | { 17 | 18 | 19 | private boolean isOutOfFolder = false; 20 | 21 | private MenuView parentLayout; 22 | 23 | private FavoritesItem dragOutItem; 24 | 25 | private int folderPosition; 26 | 27 | public FolderView(Context context) 28 | { 29 | super(context); 30 | } 31 | 32 | public FolderView(Context context, AttributeSet attrs) 33 | { 34 | super(context, attrs); 35 | } 36 | 37 | public FolderView(Context context, AttributeSet attrs, int defStyle) 38 | { 39 | super(context, attrs, defStyle); 40 | } 41 | 42 | /** 43 | * 判断是否手指移动到文件夹外边了 44 | */ 45 | public boolean moveOutFolder(int x, int y) 46 | { 47 | return x < 0 || y < 0 || x > getWidth() || y > getHeight(); 48 | } 49 | 50 | @Override 51 | public void onBeingDragging(MotionEvent event, float v) 52 | { 53 | 54 | int x = (int) event.getX(); 55 | int y = (int) event.getY(); 56 | 57 | if (!isOutOfFolder && moveOutFolder(x, y)) 58 | { 59 | // Log.e("FolderLayout", "拖动中,拖出文件夹区域了"); 60 | getParentLayout().hideFolder(); 61 | dragOutItem = getAdapter().tempRemoveItem(folderPosition, temChangPosition); 62 | isOutOfFolder = true; 63 | dragPosition = -1; 64 | } 65 | 66 | if (v < getmMinimumVelocity()) 67 | { 68 | if (isOutOfFolder) 69 | { 70 | parentLayout.dragOnChild(dragOutItem, event); 71 | } else 72 | { 73 | if (dragPosition != -1 && temChangPosition != dragPosition) 74 | { 75 | // Log.e("FolderLayout", "交换位置 " + temChangPosition + " -->" + dragPosition); 76 | onExchange(); 77 | } 78 | countPageChange(x); 79 | } 80 | 81 | } 82 | } 83 | 84 | @Override 85 | public void onDragFinished(MotionEvent event) 86 | { 87 | 88 | // int x = (int) event.getRawX(); 89 | // int y = (int) event.getRawY(); 90 | if (isOutOfFolder) 91 | { 92 | getParentLayout().onActionFolderClosed(dragOutItem, event); 93 | } else 94 | { 95 | // Log.e("FolderLayout", "拖动结束,安置在" + temChangPosition + "位置"); 96 | getChildAt(temChangPosition).setVisibility(View.VISIBLE); 97 | // setAnimationEnd(); 98 | // showDropAnimation(x, y); 99 | } 100 | } 101 | 102 | @Override 103 | public void onExchange() 104 | { 105 | getAdapter().exChangeSubItem(folderPosition, temChangPosition, dragPosition); 106 | getChildAt(dragPosition).setVisibility(View.INVISIBLE); 107 | temChangPosition = dragPosition; 108 | } 109 | 110 | @Override 111 | public boolean ifCanMove(int position) 112 | { 113 | return true; 114 | } 115 | 116 | @Override 117 | public boolean ifCanDelete(int position) 118 | { 119 | return getAdapter().ifCanDelete(folderPosition, position); 120 | } 121 | 122 | 123 | public MenuView getParentLayout() 124 | { 125 | return parentLayout; 126 | } 127 | 128 | public void setParentLayout(MenuView parentLayout) 129 | { 130 | this.parentLayout = parentLayout; 131 | } 132 | 133 | public int getFolderPosition() 134 | { 135 | return folderPosition; 136 | } 137 | 138 | public void setFolderPosition(int folderPosition) 139 | { 140 | this.folderPosition = folderPosition; 141 | } 142 | 143 | 144 | public void setAdapter(SpringboardAdapter mAdapter) 145 | { 146 | mAdapter.setFolderView(this); 147 | super.setAdapter(mAdapter); 148 | } 149 | 150 | @Override 151 | public void onItemClick(int position) 152 | { 153 | if (getAdapter().isEditting()) 154 | { 155 | getAdapter().setEditing(false); 156 | } else 157 | { 158 | if (getOnItemClickListener() != null) 159 | { 160 | getOnItemClickListener().onItemClick(getAdapter().getSubItem(folderPosition, position)); 161 | } 162 | } 163 | 164 | 165 | } 166 | 167 | @Override 168 | public int getItemCount() 169 | { 170 | return getAdapter().getSubItemCount(folderPosition); 171 | } 172 | 173 | @Override 174 | public FrameLayout initItemView(int position) 175 | { 176 | FrameLayout view = mAdapter.initSubItemView(folderPosition, position, this); 177 | mAdapter.configSubItemView(folderPosition, position, view); 178 | return view; 179 | } 180 | 181 | @Override 182 | public void onDelete(int position) 183 | { 184 | getAdapter().deleteItem(folderPosition, position); 185 | computePageCountChange(false); 186 | } 187 | 188 | @Override 189 | public boolean dispatchKeyEvent(KeyEvent event) 190 | { 191 | if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) 192 | { 193 | getParentLayout().removeFolder(); 194 | return true; 195 | } 196 | return super.dispatchKeyEvent(event); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /library/src/main/java/com/panxiaohe/springboard/library/MenuView.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.PixelFormat; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Build; 8 | import android.text.TextUtils; 9 | import android.util.AttributeSet; 10 | import android.util.TypedValue; 11 | import android.view.LayoutInflater; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.WindowManager; 15 | import android.view.KeyEvent; 16 | import android.view.animation.Animation; 17 | import android.view.animation.AnimationUtils; 18 | import android.widget.EditText; 19 | import android.widget.FrameLayout; 20 | import android.widget.ImageView; 21 | import android.widget.LinearLayout; 22 | 23 | /** 24 | * Created by panxiaohe on 16/3/9. 25 | * 使用这个布局显示按钮 26 | */ 27 | public class MenuView extends SpringboardView 28 | { 29 | 30 | // 文件夹相关 31 | 32 | private View folderView; 33 | 34 | private WindowManager windowManager; 35 | 36 | private WindowManager getWindowManager() 37 | { 38 | if (windowManager == null) 39 | { 40 | windowManager = (WindowManager) getContext().getSystemService( 41 | Context.WINDOW_SERVICE); 42 | } 43 | return windowManager; 44 | } 45 | 46 | 47 | private WindowManager.LayoutParams getWindowParams() 48 | { 49 | WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(); 50 | windowParams.height = LayoutParams.MATCH_PARENT; 51 | windowParams.width = LayoutParams.MATCH_PARENT; 52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 53 | { 54 | windowParams.flags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; 55 | } else 56 | { 57 | windowParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 58 | } 59 | windowParams.format = PixelFormat.TRANSLUCENT; 60 | windowParams.windowAnimations = 0; 61 | windowParams.x = 0; 62 | windowParams.y = 0; 63 | return windowParams; 64 | } 65 | 66 | private int folderColCount = 3; 67 | private int folderRowCount = 3; 68 | 69 | private Drawable folderDialogBackground; 70 | private Drawable folderViewBackground; 71 | private Drawable editTextBackground; 72 | private int folder_editText_textColor; 73 | private float folder_editText_textSize; 74 | 75 | public MenuView(Context context) 76 | { 77 | this(context, null); 78 | } 79 | 80 | public MenuView(Context context, AttributeSet attrs) 81 | { 82 | this(context, attrs, R.style.Springboard); 83 | } 84 | 85 | public MenuView(Context context, AttributeSet attrs, int defStyle) 86 | { 87 | super(context, attrs, defStyle); 88 | init(context, attrs, defStyle, 0); 89 | } 90 | 91 | private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 92 | { 93 | final TypedArray a = context.obtainStyledAttributes( 94 | attrs, R.styleable.Springboard, defStyleAttr, defStyleRes); 95 | folderDialogBackground = a.getDrawable(R.styleable.Springboard_folder_dialog_background); 96 | folderViewBackground = a.getDrawable(R.styleable.Springboard_folder_view_background); 97 | editTextBackground = a.getDrawable(R.styleable.Springboard_folder_edittext_background); 98 | folderColCount = a.getInt(R.styleable.Springboard_folder_column_count, 3); 99 | folderRowCount = a.getInt(R.styleable.Springboard_folder_row_count, 3); 100 | folder_editText_textColor = a.getColor(R.styleable.Springboard_folder_edittext_textcolor, 0); 101 | folder_editText_textSize = a.getDimensionPixelSize(R.styleable.Springboard_folder_edittext_textsize, 15); 102 | a.recycle(); 103 | // 点击区域外取消编辑状态 104 | this.setOnClickListener(new OnClickListener() 105 | { 106 | 107 | @Override 108 | public void onClick(View v) 109 | { 110 | getAdapter().setEditing(false); 111 | } 112 | }); 113 | } 114 | 115 | 116 | @Override 117 | public void onItemClick(int position) 118 | { 119 | if (getAdapter().getItem(position).isFolder()) 120 | { 121 | showFolder(position); 122 | } else 123 | { 124 | if (getAdapter().isEditting()) 125 | { 126 | getAdapter().setEditing(false); 127 | } else 128 | { 129 | if (getOnItemClickListener() != null) 130 | { 131 | getOnItemClickListener().onItemClick(getAdapter().getItem(position)); 132 | } 133 | } 134 | } 135 | } 136 | 137 | @Override 138 | public int getItemCount() 139 | { 140 | return getAdapter().getCount(); 141 | } 142 | 143 | @Override 144 | public FrameLayout initItemView(int position) 145 | { 146 | FrameLayout view = mAdapter.initItemView(position, this); 147 | mAdapter.configItemView(position, view); 148 | return view; 149 | } 150 | 151 | @Override 152 | public void onBeingDragging(MotionEvent event, float v) 153 | { 154 | 155 | int x = (int) event.getX(); 156 | // int y = (int)event.getY(); 157 | if (v < getmMinimumVelocity()) 158 | { 159 | // Log.e("速度",v+""); 160 | //初始位置和触摸位置不同时 161 | if (dragPosition != -1 && dragPosition != temChangPosition) 162 | { 163 | // 如果在头部或尾部区域,不进行操作 164 | if (ifCanMove(dragPosition)) 165 | { 166 | //如果被拖动的是文件夹或者不在item内部区域,交换位置 167 | if ((getAdapter().getItem(temChangPosition).isFolder()) || (!isInCenter(dragPosition, event))) 168 | { 169 | // Log.e("MenuView", "拖动中,交换位置,从" + temChangPosition + "到" + dragPosition); 170 | onExchange(); 171 | } 172 | // else 173 | // { 174 | // Log.e("MenuView", "在文件夹中dragPosition = "+dragPosition); 175 | // } 176 | } 177 | } 178 | countPageChange(x); 179 | } 180 | // else 181 | // { 182 | // Log.e("MenuView", "speed too fast v= "+v); 183 | // } 184 | } 185 | 186 | @Override 187 | protected void onLayout(boolean changed, int l, int t, int r, int b) 188 | { 189 | super.onLayout(changed, l, t, r, b); 190 | 191 | } 192 | 193 | @Override 194 | public void onDragFinished(MotionEvent event) 195 | { 196 | // int x = (int) event.getX(); 197 | // int y = (int) event.getY(); 198 | //可以合并 199 | if (getAdapter().ifCanMerge(temChangPosition, dragPosition)) 200 | { 201 | //在目标位置中心区域 202 | if (dragPosition != -1 && temChangPosition != dragPosition && isInCenter(dragPosition, event)) 203 | { 204 | onMerge(temChangPosition, dragPosition); 205 | } 206 | } 207 | // getChildAt(temChangPosition).setVisibility(View.VISIBLE); 208 | // setAnimationEnd(); 209 | // showDropAnimation(x, y); 210 | } 211 | 212 | @Override 213 | public void onExchange() 214 | { 215 | mAdapter.exchangeItem(temChangPosition, dragPosition); 216 | temChangPosition = dragPosition; 217 | // movePostionAnimation(temChangPosition, dragPosition); 218 | getChildAt(dragPosition).setVisibility(View.INVISIBLE); 219 | 220 | } 221 | 222 | @Override 223 | public boolean ifCanMove(int position) 224 | { 225 | return position >= getStableHeaderCount() && !(position == getChildCount() - 1 && isLastItemStable()); 226 | 227 | } 228 | 229 | @Override 230 | public boolean ifCanDelete(int position) 231 | { 232 | if (position < getStableHeaderCount()) 233 | { 234 | return false; 235 | } 236 | return !(position == getChildCount() - 1 && isLastItemStable()) && getAdapter().ifCanDelete(position); 237 | 238 | } 239 | 240 | protected void onActionFolderClosed(FavoritesItem dragOutItem, MotionEvent event) 241 | { 242 | int x = (int) event.getRawX(); 243 | int y = (int) event.getRawY(); 244 | removeFolder(); 245 | if (temChangPosition != -1) 246 | { 247 | getChildAt(temChangPosition).setVisibility(View.VISIBLE); 248 | } else 249 | { 250 | int[] locations = new int[2]; 251 | this.getLocationOnScreen(locations); 252 | x = x - locations[0]; 253 | y = y - locations[1]; 254 | dragPosition = pointToPosition(x, y); 255 | // Log.e("dragPosition = ",dragPosition+""); 256 | if (dragPosition != -1 && isInCenter(dragPosition, event) && ifCanMove(dragPosition)) 257 | { 258 | // Log.e("ScrollLayout", "拖动结束,合并到文件夹 position = "+dragOutItem); 259 | getAdapter().addItemToFolder(dragPosition, dragOutItem, defaultFolderName); 260 | } else 261 | { 262 | // Log.e("ScrollLayout", "拖动结束,添加按钮到最后"); 263 | int position = getChildCount(); 264 | if (isLastItemStable()) 265 | { 266 | position--; 267 | } 268 | getAdapter().addItem(position, dragOutItem); 269 | 270 | computePageCountChange(true); 271 | 272 | if (getmCurScreen() < getTotalPage() - 1) 273 | { 274 | snapToScreen(getTotalPage() - 1); 275 | } 276 | } 277 | } 278 | dragPosition = temChangPosition = -1; 279 | } 280 | 281 | /** 282 | * @param event 触摸点位置 283 | * @param dragOutItem 被拖出的按钮 284 | */ 285 | protected void dragOnChild(FavoritesItem dragOutItem, MotionEvent event) 286 | { 287 | 288 | if (!mScroller.isFinished() || mLayoutTransition.isRunning()) 289 | { 290 | return; 291 | } 292 | 293 | int x = (int) event.getRawX(); 294 | int y = (int) event.getRawY(); 295 | int[] locations = new int[2]; 296 | this.getLocationOnScreen(locations); 297 | x = x - locations[0]; 298 | y = y - locations[1]; 299 | dragPosition = pointToPosition(x, y); 300 | // Log.e("dragOnChild", "mCurScreen" + getmCurScreen() + " dragPosition = " + dragPosition + " temChangPosition = " + temChangPosition); 301 | if (dragPosition != -1) 302 | { 303 | if (temChangPosition != -1) 304 | { 305 | if (isLastItemStable() && dragPosition == getItemCount() - 1) 306 | { 307 | // Log.e("dragOnChild", "在最后区域不交换"); 308 | } else 309 | { 310 | if (temChangPosition != dragPosition && ifCanMove(dragPosition)) 311 | { 312 | if (isInCenter(dragPosition, event)) 313 | { 314 | // Log.e("dragOnChild", "删除位置是temChangPosition 的按钮"); 315 | onDelete(temChangPosition); 316 | temChangPosition = -1; 317 | } else 318 | { 319 | // Log.e("dragOnChild","交换位置 "+temChangPosition +""+dragPosition); 320 | onExchange(); 321 | } 322 | } else 323 | { 324 | // Log.e("dragOnChild","位置没变"); 325 | } 326 | } 327 | } else 328 | { 329 | if (isLastItemStable() && dragPosition == getItemCount() - 1) 330 | { 331 | // Log.e("dragOnChild","添加按钮到位置 "+dragPosition); 332 | getAdapter().addItem(dragPosition, dragOutItem); 333 | temChangPosition = dragPosition; 334 | getChildAt(temChangPosition).setVisibility(View.INVISIBLE); 335 | computePageCountChange(true); 336 | } else 337 | { 338 | if (!isInCenter(dragPosition, event) && ifCanMove(dragPosition)) 339 | { 340 | // Log.e("dragOnChild","添加按钮到位置 "+dragPosition); 341 | getAdapter().addItem(dragPosition, dragOutItem); 342 | temChangPosition = dragPosition; 343 | getChildAt(temChangPosition).setVisibility(View.INVISIBLE); 344 | computePageCountChange(true); 345 | } else 346 | { 347 | // Log.e("dragOnChild","在文件夹中或者"+dragPosition); 348 | } 349 | } 350 | } 351 | } else if (temChangPosition != -1) 352 | { 353 | // Log.e("dragOnChild","移除View区域,删除位置是"+temChangPosition + " 的按钮"); 354 | onDelete(temChangPosition); 355 | temChangPosition = -1; 356 | } 357 | countPageChange(x); 358 | } 359 | 360 | /** 361 | * 显示文件夹 362 | */ 363 | public void showFolder(int position) 364 | { 365 | if (getAdapter().isEditting()) 366 | { 367 | stopShake(); 368 | isStopShake = true; 369 | } 370 | FavoritesItem info = getAdapter().getItem(position); 371 | folderView = LayoutInflater.from(getContext()).inflate(R.layout.folder_layout, this, false); 372 | if (folderDialogBackground != null) 373 | { 374 | folderView.setBackground(folderDialogBackground); 375 | } 376 | LinearLayout linearLayout = (LinearLayout) folderView.findViewById(R.id.folder_container); 377 | 378 | if (folderViewBackground != null) 379 | { 380 | linearLayout.setBackground(folderViewBackground); 381 | } 382 | EditText editText = (EditText) folderView.findViewById(R.id.editText1); 383 | if (editTextBackground != null) 384 | { 385 | editText.setBackground(editTextBackground); 386 | } 387 | if (folder_editText_textColor != 0) 388 | { 389 | editText.setTextColor(folder_editText_textColor); 390 | } 391 | editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, folder_editText_textSize); 392 | editText.setText(info.getActionName()); 393 | 394 | FolderView layout = (FolderView) folderView.findViewById(R.id.container); 395 | 396 | layout.setParentLayout(this); 397 | layout.setColCount(folderColCount); 398 | layout.setRowCount(folderRowCount); 399 | layout.setFolderPosition(position); 400 | layout.setAdapter(getAdapter()); 401 | layout.setOnItemClickListener(getOnItemClickListener()); 402 | folderView.setOnClickListener(new OnClickListener() 403 | { 404 | 405 | @Override 406 | public void onClick(View v) 407 | { 408 | removeFolder(); 409 | } 410 | }); 411 | getWindowManager().addView(folderView, getWindowParams()); 412 | 413 | getAdapter().initFolderEditingMode(); 414 | 415 | } 416 | 417 | public void stopShake() 418 | { 419 | int count = getChildCount(); 420 | for (int i = 0; i < count; i++) 421 | { 422 | View child = getChildAt(i); 423 | ImageView imageView = (ImageView) child.findViewById(R.id.delete_button_id); 424 | FrameLayout layout = (FrameLayout) child.findViewById(R.id.shake_zone_id); 425 | ; 426 | imageView.clearAnimation(); 427 | layout.clearAnimation(); 428 | } 429 | } 430 | 431 | public void startShake() 432 | { 433 | int count = getChildCount(); 434 | for (int i = 0; i < count; i++) 435 | { 436 | View child = getChildAt(i); 437 | ImageView imageView = (ImageView) child.findViewById(R.id.delete_button_id); 438 | FrameLayout layout = (FrameLayout) child.findViewById(R.id.shake_zone_id); 439 | Animation shake; 440 | if (ifCanMove(i)) 441 | { 442 | if (imageView.getVisibility() == View.VISIBLE) 443 | { 444 | shake = AnimationUtils.loadAnimation(getContext(), R.anim.shake); 445 | imageView.startAnimation(shake); 446 | } 447 | shake = AnimationUtils.loadAnimation(getContext(), R.anim.shake_rotate); 448 | layout.startAnimation(shake); 449 | } 450 | } 451 | } 452 | 453 | /** 454 | * 隐藏View 455 | */ 456 | public void hideFolder() 457 | { 458 | // Log.e("ScrollLayout", "隐藏文件夹"); 459 | if (folderView != null) 460 | { 461 | WindowManager.LayoutParams params = getWindowParams(); 462 | params.alpha = 0; 463 | getWindowManager().updateViewLayout(folderView, params); 464 | } 465 | 466 | if (getAdapter().isEditting()) 467 | { 468 | if (isStopShake == true) 469 | { 470 | startShake(); 471 | isStopShake = false; 472 | } 473 | } 474 | } 475 | 476 | private boolean isStopShake = false; 477 | 478 | /** 479 | * 清除View 480 | */ 481 | public void removeFolder() 482 | { 483 | // Log.e("ScrollLayout", "是否在编辑状态"+getAdapter().isEditting()); 484 | if (getAdapter().isEditting()) 485 | { 486 | if (isStopShake == true) 487 | { 488 | startShake(); 489 | isStopShake = false; 490 | } 491 | } 492 | 493 | if (folderView != null) 494 | { 495 | 496 | EditText editText = (EditText) folderView.findViewById(R.id.editText1); 497 | String name = editText.getText().toString(); 498 | if (TextUtils.isEmpty(name)) 499 | { 500 | name = defaultFolderName; 501 | } 502 | getAdapter().changeFolderName(name); 503 | getWindowManager().removeView(folderView); 504 | folderView = null; 505 | getAdapter().removeFolder(); 506 | } 507 | } 508 | 509 | 510 | @Override 511 | public boolean dispatchKeyEvent(KeyEvent event) 512 | { 513 | 514 | if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && getAdapter().isEditting()) 515 | { 516 | getAdapter().setEditing(false); 517 | return true; 518 | } 519 | 520 | return super.dispatchKeyEvent(event); 521 | } 522 | 523 | public boolean isShowingFolder() 524 | { 525 | return folderView != null; 526 | } 527 | 528 | 529 | public void setAdapter(SpringboardAdapter mAdapter) 530 | { 531 | mAdapter.setSpringboardView(this); 532 | super.setAdapter(mAdapter); 533 | } 534 | 535 | @Override 536 | public void onDelete(int position) 537 | { 538 | getAdapter().deleteItem(position); 539 | computePageCountChange(true); 540 | } 541 | } -------------------------------------------------------------------------------- /library/src/main/java/com/panxiaohe/springboard/library/SpringboardAdapter.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 2 | 3 | 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.FrameLayout; 7 | 8 | import java.lang.ref.SoftReference; 9 | import java.util.ArrayList; 10 | 11 | /** 12 | * Created by panxiaohe on 16/3/19. 13 | * 适配器. 14 | */ 15 | public abstract class SpringboardAdapter 16 | { 17 | private ArrayList items; 18 | 19 | private boolean isEditing = false; 20 | 21 | private boolean isTouching = false; 22 | 23 | private SoftReference springboardView; 24 | 25 | private SoftReference folderView; 26 | 27 | /** 28 | * 首个层级数量 29 | */ 30 | public int getCount() 31 | { 32 | return items.size(); 33 | } 34 | 35 | /** 36 | * 文件夹里按钮数量 37 | */ 38 | public int getSubItemCount(int position) 39 | { 40 | return items.get(position).getSubItemCount(); 41 | } 42 | 43 | /** 44 | * 首个层级item 45 | */ 46 | public T getItem(int position) 47 | { 48 | return items.get(position); 49 | } 50 | 51 | /** 52 | * 首个层级item 53 | */ 54 | public T getSubItem(int folderPosition, int position) 55 | { 56 | return (T) items.get(folderPosition).getSubItem(position); 57 | } 58 | 59 | /** 60 | * @return 只需返回所需的view, 所有设置请在configUI里完成 61 | */ 62 | public abstract FrameLayout initItemView(int position, ViewGroup parent); 63 | 64 | public abstract void configItemView(int position, FrameLayout frameLayout); 65 | 66 | /** 67 | * @return 只需返回所需的view, 所有设置请在configUI里完成 68 | */ 69 | public abstract FrameLayout initSubItemView(int folderPosition, int position, ViewGroup parent); 70 | 71 | public abstract void configSubItemView(int folderPosition, int position, FrameLayout frameLayout); 72 | 73 | public void exchangeItem(int fromPosition, int toPosition) 74 | { 75 | // Log.e("SpringboardAdapter","exchangeItem fromPosition = "+fromPosition+" toPosition = "+toPosition); 76 | T item = items.remove(fromPosition); 77 | 78 | SpringboardView container = getSpringboardView(); 79 | 80 | FrameLayout view = (FrameLayout) container.getChildAt(fromPosition); 81 | 82 | container.removeView(view); 83 | 84 | items.add(toPosition, item); 85 | 86 | dataChange(); 87 | 88 | container.addView(view, toPosition); 89 | 90 | getSpringboardView().setEditingMode(toPosition, view, isEditing, true); 91 | 92 | } 93 | 94 | public void exChangeSubItem(int folderPosition, int fromPosition, int toPosition) 95 | { 96 | // Log.e("SpringboardAdapter","exChangeSubItem folderPosition = "+folderPosition +" fromPosition = "+fromPosition+" toPosition = "+toPosition); 97 | T folder = items.get(folderPosition); 98 | 99 | T item = (T) folder.removeSubItem(fromPosition); 100 | // (T)folder.getMenuList().remove(fromPosition); 101 | 102 | SpringboardView container = getFolderView(); 103 | 104 | FrameLayout view = (FrameLayout) container.getChildAt(fromPosition); 105 | 106 | container.removeView(view); 107 | 108 | folder.addSubItem(toPosition, item); 109 | 110 | dataChange(); 111 | 112 | container.addView(view, toPosition); 113 | 114 | configItemView(folderPosition, (FrameLayout) getSpringboardView().getChildAt(folderPosition)); 115 | 116 | getFolderView().setEditingMode(toPosition, view, isEditing, true); 117 | 118 | } 119 | 120 | public void deleteItem(int position) 121 | { 122 | // Log.e("SpringboardAdapter","deleteItem position = "+position); 123 | items.remove(position); 124 | 125 | onDataChange(); 126 | 127 | getSpringboardView().removeViewAt(position); 128 | } 129 | 130 | public void deleteItem(int folderPosition, int position) 131 | { 132 | // Log.e("SpringboardAdapter","deleteItem folderPosition = "+folderPosition+" position = "+position); 133 | T folder = items.get(folderPosition); 134 | 135 | folder.removeSubButton(position); 136 | 137 | onDataChange(); 138 | 139 | getFolderView().removeViewAt(position); 140 | 141 | FrameLayout view = (FrameLayout) getSpringboardView().getChildAt(folderPosition); 142 | 143 | configItemView(folderPosition, view); 144 | 145 | if (!folder.isFolder()) 146 | { 147 | getSpringboardView().setEditingMode(folderPosition, view, isEditing, true); 148 | getSpringboardView().removeFolder(); 149 | } else 150 | { 151 | getSpringboardView().setEditingMode(folderPosition, view, isEditing, false); 152 | } 153 | } 154 | 155 | 156 | public void mergeItem(int fromPosition, int toPosition, String defaultFolderName) 157 | { 158 | // Log.e("SpringboardAdapter","mergeItem fromPosition = "+fromPosition+" toPosition = "+toPosition); 159 | T fromItem = items.get(fromPosition); 160 | 161 | T toItem = items.get(toPosition); 162 | 163 | toItem.addSubButton(fromItem, defaultFolderName); 164 | 165 | FrameLayout view = (FrameLayout) getSpringboardView().getChildAt(toPosition); 166 | 167 | configItemView(toPosition, view); 168 | 169 | getSpringboardView().setEditingMode(toPosition, view, isEditing, true); 170 | 171 | items.remove(fromPosition); 172 | 173 | dataChange(); 174 | 175 | getSpringboardView().removeViewAt(fromPosition); 176 | } 177 | 178 | public T tempRemoveItem(int folderPosition, int position) 179 | { 180 | T folder = items.get(folderPosition); 181 | 182 | T item = (T) folder.getSubItem(position); 183 | 184 | folder.removeSubButton(position); 185 | 186 | FrameLayout view = (FrameLayout) getSpringboardView().getChildAt(folderPosition); 187 | 188 | configItemView(folderPosition, view); 189 | 190 | getSpringboardView().setEditingMode(folderPosition, view, isEditing, true); 191 | 192 | return item; 193 | } 194 | 195 | public void addItem(int position, T item) 196 | { 197 | // Log.e("SpringboardAdapter","addItem position = "+position); 198 | items.add(position, item); 199 | 200 | dataChange(); 201 | 202 | FrameLayout view = getSpringboardView().initItemView(position); 203 | getSpringboardView().configView(view); 204 | getSpringboardView().addView(view, position); 205 | 206 | getSpringboardView().setEditingMode(position, view, isEditing, true); 207 | 208 | } 209 | 210 | public void addItemToFolder(int dragPosition, T dragOutItem, String defaultName) 211 | { 212 | // Log.e("SpringboardAdapter","addItemToFolder position = "+dragPosition); 213 | T folder = items.get(dragPosition); 214 | 215 | folder.addSubButton(dragOutItem, defaultName); 216 | 217 | dataChange(); 218 | 219 | configItemView(dragPosition, (FrameLayout) getSpringboardView().getChildAt(dragPosition)); 220 | } 221 | 222 | private void dataChange() 223 | { 224 | if (!isTouching) 225 | { 226 | onDataChange(); 227 | } 228 | } 229 | 230 | 231 | public MenuView getSpringboardView() 232 | { 233 | return springboardView.get(); 234 | } 235 | 236 | public void setSpringboardView(MenuView mSpringboardView) 237 | { 238 | this.springboardView = new SoftReference<>(mSpringboardView); 239 | } 240 | 241 | public FolderView getFolderView() 242 | { 243 | if (folderView != null) 244 | { 245 | return folderView.get(); 246 | 247 | } 248 | return null; 249 | } 250 | 251 | public void setFolderView(FolderView folderView) 252 | { 253 | this.folderView = new SoftReference<>(folderView); 254 | } 255 | 256 | /** 257 | * @param position 位置 258 | * @return true 可以删除 259 | */ 260 | public boolean ifCanDelete(int position) 261 | { 262 | // Log.e("SpringboardAdapter",getItem(position).toString()); 263 | return !getItem(position).isFolder(); 264 | } 265 | 266 | /** 267 | * @param position 位置 268 | * @return true 可以删除 269 | */ 270 | public boolean ifCanDelete(int folderPosition, int position) 271 | { 272 | return !getSubItem(folderPosition, position).isFolder(); 273 | } 274 | 275 | public boolean ifCanMerge(int fromPosition, int toPosition) 276 | { 277 | return !(getItem(fromPosition).isFolder() || (!getSpringboardView().ifCanMove(toPosition))); 278 | } 279 | 280 | public void changeFolderName(String name) 281 | { 282 | int folderPosition = getFolderView().getFolderPosition(); 283 | T item = items.get(folderPosition); 284 | if (item.isFolder()) 285 | { 286 | String oldName = items.get(folderPosition).getActionName(); 287 | if (!oldName.equals(name)) 288 | { 289 | items.get(folderPosition).setActionName(name); 290 | 291 | onDataChange(); 292 | 293 | configItemView(folderPosition, (FrameLayout) getSpringboardView().getChildAt(folderPosition)); 294 | } 295 | } 296 | } 297 | 298 | public void setEditing(boolean isEditing) 299 | { 300 | if (this.isEditing != isEditing) 301 | { 302 | // Log.e("SpringboardAdapter", "编辑模式" + isEditing); 303 | this.isEditing = isEditing; 304 | FolderView folder; 305 | if (folderView != null && (folder = folderView.get()) != null) 306 | { 307 | int count = folder.getChildCount(); 308 | for (int i = 0; i < count; i++) 309 | { 310 | View child = folder.getChildAt(i); 311 | folder.setEditingMode(i, child, isEditing, true); 312 | } 313 | if (isEditting()) 314 | { 315 | folder.requestFocus(); 316 | } 317 | int count1 = getSpringboardView().getChildCount(); 318 | for (int i = 0; i < count1; i++) 319 | { 320 | View child1 = getSpringboardView().getChildAt(i); 321 | getSpringboardView().setEditingMode(i, child1, isEditing, false); 322 | } 323 | } else 324 | { 325 | if (isEditting()) 326 | { 327 | getSpringboardView().requestFocus(); 328 | } 329 | int count1 = getSpringboardView().getChildCount(); 330 | for (int i = 0; i < count1; i++) 331 | { 332 | View child1 = getSpringboardView().getChildAt(i); 333 | getSpringboardView().setEditingMode(i, child1, isEditing, true); 334 | } 335 | } 336 | } 337 | } 338 | 339 | public void initFolderEditingMode() 340 | { 341 | if (isEditing) 342 | { 343 | FolderView folder; 344 | if (folderView != null && (folder = folderView.get()) != null) 345 | { 346 | int count = folder.getChildCount(); 347 | for (int i = 0; i < count; i++) 348 | { 349 | View child = folder.getChildAt(i); 350 | folder.setEditingMode(i, child, isEditing, true); 351 | } 352 | if (isEditting()) 353 | { 354 | folder.requestFocus(); 355 | } 356 | } 357 | } 358 | } 359 | 360 | public boolean isEditting() 361 | { 362 | return isEditing; 363 | } 364 | 365 | public void removeFolder() 366 | { 367 | setFolderView(null); 368 | } 369 | 370 | public abstract void onDataChange(); 371 | 372 | public ArrayList getItems() 373 | { 374 | return items; 375 | } 376 | 377 | public void setItems(ArrayList items) 378 | { 379 | this.items = items; 380 | } 381 | 382 | 383 | protected boolean isTouching() 384 | { 385 | return isTouching; 386 | } 387 | 388 | protected void setIsTouching(boolean isTouching) 389 | { 390 | this.isTouching = isTouching; 391 | } 392 | } -------------------------------------------------------------------------------- /library/src/main/java/com/panxiaohe/springboard/library/SpringboardView.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard.library; 2 | 3 | import android.animation.LayoutTransition; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.Drawable; 12 | import android.util.AttributeSet; 13 | import android.view.Gravity; 14 | import android.view.MotionEvent; 15 | import android.view.VelocityTracker; 16 | import android.view.View; 17 | import android.view.ViewConfiguration; 18 | import android.view.ViewGroup; 19 | import android.view.animation.Animation; 20 | import android.view.animation.AnimationUtils; 21 | import android.widget.FrameLayout; 22 | import android.widget.ImageView; 23 | import android.widget.Scroller; 24 | 25 | 26 | /** 27 | * Created by panxiaohe on 16/3/9. 28 | *

29 | * 这个类提供布局相关,各种监听的设置和基本事件的处理 30 | *

31 | * 具体的各种触摸事件处理请使用子类 32 | */ 33 | public abstract class SpringboardView extends ViewGroup 34 | { 35 | 36 | // #页面相关 37 | /** 38 | * callback onPageCountChange() at totalPage count change 39 | * callback onPageScroll() at mCurScreen change 40 | * 在页面数量和当面页面发生变化时回调 41 | */ 42 | private OnPageChangedListener onPageChangedListener; 43 | /** 44 | * 适配器 45 | */ 46 | protected SpringboardAdapter mAdapter; 47 | /** 48 | * 总页面数量 49 | * will count totalPage at setAdapter,merge into Folder,delete item,add item,remove out Folder 50 | */ 51 | private int totalPage = 0; 52 | /** 53 | * 现在页面 54 | */ 55 | private int mCurScreen = 0; 56 | 57 | 58 | // #触摸相关 59 | /** 60 | * 开始时的x坐标 61 | */ 62 | private int startX = 0; 63 | /** 64 | * 速度计算 65 | */ 66 | private VelocityTracker mVelocityTracker; 67 | /** 68 | * 最后的触摸位置X 69 | */ 70 | private float mLastMotionX; 71 | /** 72 | * 最后的触摸位置Y 73 | */ 74 | private float mLastMotionY; 75 | /** 76 | * 触摸点距离View左边的距离 77 | */ 78 | private int dragPointX; 79 | /** 80 | * 触摸点距离View顶部的距离 81 | */ 82 | private int dragPointY; 83 | /** 84 | * view左边距离屏幕的距离 85 | */ 86 | private int dragOffsetX; 87 | /** 88 | * view上边距离屏幕的距离 89 | */ 90 | private int dragOffsetY; 91 | /** 92 | * 当前手指触摸的位置 93 | */ 94 | protected int dragPosition = -1; 95 | /** 96 | * 拖动开始时和顺序交换之后被拖动View的位置 97 | */ 98 | protected int temChangPosition = -1; 99 | protected Scroller mScroller; 100 | private int mTouchSlop; 101 | private int mMinimumVelocity; 102 | private int mMaximumVelocity; 103 | private MODE mode = MODE.FREE; 104 | 105 | enum MODE 106 | { 107 | //普通时,按钮被长按之后,进入这个模式,滚动模式 108 | FREE, DRAGGING, SCROLL 109 | } 110 | 111 | 112 | // #各种属性 113 | /** 114 | * 删除按钮 115 | */ 116 | private Drawable deleteIcon; 117 | /** 118 | * 几行 119 | */ 120 | private int rowCount; 121 | /** 122 | * 几列 123 | */ 124 | private int colCount; 125 | /** 126 | * 合并按钮,产生文件夹时的默认按钮 127 | */ 128 | protected String defaultFolderName; 129 | protected int dividerWidth; 130 | protected int dividerColor; 131 | private boolean isLastItemStable; 132 | private int stableHeaderCount; 133 | protected int page_divider_width; 134 | 135 | 136 | private Paint mPaint; 137 | protected LayoutTransition mLayoutTransition; 138 | // private int halfBitmapWidth; 139 | // private int halfBitmapHeight; 140 | 141 | public SpringboardView(Context context) 142 | { 143 | this(context, null); 144 | } 145 | 146 | public SpringboardView(Context context, AttributeSet attrs) 147 | { 148 | this(context, attrs, R.style.Springboard); 149 | } 150 | 151 | public SpringboardView(Context context, AttributeSet attrs, int defStyle) 152 | { 153 | super(context, attrs, defStyle); 154 | initSpringboardView(context, attrs, defStyle, 0); 155 | } 156 | 157 | private void initSpringboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 158 | { 159 | final TypedArray a = context.obtainStyledAttributes( 160 | attrs, R.styleable.Springboard, defStyleAttr, defStyleRes); 161 | deleteIcon = a.getDrawable(R.styleable.Springboard_delete_icon); 162 | rowCount = a.getInt(R.styleable.Springboard_row_count, 3); 163 | colCount = a.getInt(R.styleable.Springboard_column_count, 3); 164 | defaultFolderName = a.getString(R.styleable.Springboard_default_folder_name); 165 | dividerWidth = a.getDimensionPixelOffset(R.styleable.Springboard_divider_width, 0); 166 | dividerColor = a.getColor(R.styleable.Springboard_divider_color, 0); 167 | isLastItemStable = a.getBoolean(R.styleable.Springboard_is_last_item_stable, false); 168 | stableHeaderCount = a.getInt(R.styleable.Springboard_stable_header_count, 0); 169 | page_divider_width = a.getDimensionPixelOffset(R.styleable.Springboard_page_divider_width, 0); 170 | 171 | if (dividerWidth != 0 && dividerColor != 0) 172 | { 173 | setWillNotDraw(false); 174 | } 175 | a.recycle(); 176 | setFocusable(true); 177 | setFocusableInTouchMode(true); 178 | this.mScroller = new Scroller(getContext()); 179 | FingerFlowViewManager.getInstance().init(getContext()); 180 | setOverScrollMode(OVER_SCROLL_ALWAYS); 181 | final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 182 | mTouchSlop = configuration.getScaledTouchSlop(); 183 | mMinimumVelocity = 700; 184 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 185 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 186 | mLayoutTransition = new LayoutTransition(); 187 | mLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, null); 188 | mLayoutTransition.setAnimator(LayoutTransition.APPEARING, null); 189 | setLayoutTransition(mLayoutTransition); 190 | } 191 | 192 | @Override 193 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 194 | { 195 | 196 | int childHeight; 197 | int measuredHeight; 198 | //父给出的宽度,不管什么模式用完 199 | int width = MeasureSpec.getSize(widthMeasureSpec); 200 | // int widthMode = MeasureSpec.getMode(widthMeasureSpec); 201 | 202 | //可以使用的宽度 203 | int usedWidth = width - getPaddingLeft() - getPaddingRight() - (colCount + 1) 204 | * dividerWidth; 205 | int childWidth = usedWidth / colCount; 206 | int height = MeasureSpec.getSize(heightMeasureSpec); 207 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 208 | //如果有准确高度,按准确高度计算. 209 | if (heightMode == MeasureSpec.EXACTLY) 210 | { 211 | int usedHeight = height - getPaddingTop() - getPaddingBottom() - (rowCount + 1) 212 | * dividerWidth; 213 | 214 | childHeight = usedHeight / rowCount; 215 | 216 | measuredHeight = height; 217 | 218 | } else 219 | { 220 | childHeight = usedWidth / colCount; 221 | 222 | measuredHeight = childHeight * rowCount + (rowCount + 1) * dividerWidth + getPaddingTop() + getPaddingBottom(); 223 | 224 | if (heightMode == MeasureSpec.AT_MOST) 225 | { 226 | if (measuredHeight > height) 227 | { 228 | measuredHeight = height; 229 | } 230 | } 231 | } 232 | 233 | int childWidthSpec = getChildMeasureSpec( 234 | MeasureSpec 235 | .makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 236 | 0, childWidth); 237 | 238 | int childHeightSpec = getChildMeasureSpec( 239 | MeasureSpec 240 | .makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 241 | 0, childHeight); 242 | final int count = getChildCount(); 243 | for (int i = 0; i < count; i++) 244 | { 245 | View child = getChildAt(i); 246 | child.measure(childWidthSpec, childHeightSpec); 247 | 248 | } 249 | setMeasuredDimension(width, measuredHeight); 250 | } 251 | 252 | @Override 253 | protected void onLayout(boolean changed, int l, int t, int r, int b) 254 | { 255 | final int childCount = getChildCount(); 256 | 257 | int measuredWidth = getMeasuredWidth(); 258 | 259 | for (int i = 0; i < childCount; i++) 260 | { 261 | final View childView = getChildAt(i); 262 | if (childView.getVisibility() != View.GONE) 263 | { 264 | 265 | int page = i / rowCount / colCount; 266 | 267 | int row = i / colCount % rowCount; 268 | 269 | int col = i % colCount; 270 | 271 | int left = getPaddingLeft() + page * (measuredWidth + page_divider_width) + col 272 | * (dividerWidth + childView.getMeasuredWidth()) + dividerWidth; 273 | 274 | int top = getPaddingTop() + dividerWidth + row * (dividerWidth + childView.getMeasuredHeight()); 275 | 276 | childView.layout(left, top, left + childView.getMeasuredWidth(), 277 | top + childView.getMeasuredHeight()); 278 | 279 | } 280 | } 281 | } 282 | 283 | @Override 284 | protected void onDraw(Canvas canvas) 285 | { 286 | super.onDraw(canvas); 287 | mPaint.setColor(dividerColor); 288 | int childHeight = getChildHeight(); 289 | int childWidth = getChildWidth(); 290 | int rowCount = getRowCount() + 1; 291 | 292 | int pageCount = getTotalPage(); 293 | 294 | for (int j = 0; j < pageCount; j++) 295 | { 296 | for (int i = 0; i < rowCount; i++) 297 | { 298 | int count = (childHeight + dividerWidth) * i; 299 | 300 | canvas.drawRect(j * (getWidth() + page_divider_width), count, (j + 1) * getWidth() + j * page_divider_width, count + dividerWidth, mPaint); 301 | } 302 | 303 | int colCount = getColCount() + 1; 304 | for (int i = 0; i < colCount; i++) 305 | { 306 | int count = (childWidth + dividerWidth) * i + j * (getWidth() + page_divider_width); 307 | canvas.drawRect(count, 0, count + dividerWidth, getHeight(), mPaint); 308 | } 309 | } 310 | } 311 | 312 | 313 | @Override 314 | public boolean onInterceptTouchEvent(MotionEvent ev) 315 | { 316 | // Log.e("onInterceptTouchEvent", ev.toString()); 317 | int action = ev.getAction(); 318 | 319 | if ((action == MotionEvent.ACTION_MOVE) && (mode == MODE.DRAGGING)) 320 | { 321 | initVelocityTrackerIfNotExists(); 322 | mVelocityTracker.addMovement(ev); 323 | return true; 324 | } 325 | 326 | float x = ev.getX(); 327 | float y = ev.getY(); 328 | 329 | switch (action) 330 | { 331 | case MotionEvent.ACTION_DOWN: 332 | startX = (int) x; 333 | dragOffsetX = (int) (ev.getRawX() - x); 334 | dragOffsetY = (int) (ev.getRawY() - y); 335 | mLastMotionX = x; 336 | mLastMotionY = y; 337 | initOrResetVelocityTracker(); 338 | mVelocityTracker.addMovement(ev); 339 | stopScroll(); 340 | if (mAdapter != null) 341 | { 342 | mAdapter.setIsTouching(true); 343 | } 344 | break; 345 | case MotionEvent.ACTION_MOVE: 346 | initVelocityTrackerIfNotExists(); 347 | mVelocityTracker.addMovement(ev); 348 | int deltaX = (int) (mLastMotionX - x); 349 | if (ifCanScroll(deltaX)) 350 | { 351 | mode = MODE.SCROLL; 352 | getParent().requestDisallowInterceptTouchEvent(true); 353 | recycleVelocityTracker(); 354 | return true; 355 | } 356 | break; 357 | case MotionEvent.ACTION_CANCEL: 358 | getAdapter().setEditing(false); 359 | case MotionEvent.ACTION_UP: 360 | if (mAdapter != null) 361 | { 362 | mAdapter.setIsTouching(false); 363 | } 364 | if (mode == MODE.DRAGGING) 365 | { 366 | dragPosition = pointToPosition((int) x, (int) y); 367 | onDragFinished(ev); 368 | endDrag(); 369 | } else if (mode == MODE.SCROLL) 370 | { 371 | float distance = ev.getRawX() - startX; 372 | endScroll(distance); 373 | } 374 | if (mode != MODE.FREE) 375 | { 376 | getParent().requestDisallowInterceptTouchEvent(false); 377 | } 378 | startX = 0; 379 | mode = MODE.FREE; 380 | recycleVelocityTracker(); 381 | break; 382 | } 383 | return false; 384 | } 385 | 386 | @Override 387 | public boolean onTouchEvent(MotionEvent event) 388 | { 389 | // Log.e("onTouchEvent", event.toString()); 390 | int action = event.getAction(); 391 | int x = (int) event.getX(); 392 | int y = (int) event.getY(); 393 | 394 | switch (action) 395 | { 396 | case MotionEvent.ACTION_MOVE: 397 | switch (mode) 398 | { 399 | case DRAGGING: 400 | int viewX = x - dragPointX + dragOffsetX; 401 | int viewY = y - dragPointY + dragOffsetY; 402 | onFling(viewX, viewY); 403 | mVelocityTracker.addMovement(event); 404 | mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 405 | // 判断速度如果速度大于highSpeed,不处理移动 406 | dragPosition = pointToPosition(x, y); 407 | if (mScroller.isFinished() && (!mLayoutTransition.isRunning())) 408 | { 409 | float v = calculateV(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); 410 | onBeingDragging(event, v); 411 | } 412 | break; 413 | case SCROLL: 414 | int deltaX = (int) (mLastMotionX - x); 415 | mLastMotionX = x; 416 | scrollBy(deltaX, 0); 417 | break; 418 | case FREE: 419 | int deltaX1 = (int) (mLastMotionX - x); 420 | if (ifCanScroll(deltaX1)) 421 | { 422 | getParent().requestDisallowInterceptTouchEvent(true); 423 | mode = MODE.SCROLL; 424 | } 425 | break; 426 | } 427 | break; 428 | case MotionEvent.ACTION_CANCEL: 429 | getAdapter().setEditing(false); 430 | case MotionEvent.ACTION_UP: 431 | if (mAdapter != null) 432 | { 433 | mAdapter.setIsTouching(false); 434 | } 435 | if (mode == MODE.DRAGGING) 436 | { 437 | dragPosition = pointToPosition(x, y); 438 | onDragFinished(event); 439 | endDrag(); 440 | } else if (mode == MODE.SCROLL) 441 | { 442 | float distance = event.getRawX() - startX; 443 | endScroll(distance); 444 | } else 445 | { 446 | getAdapter().setEditing(false); 447 | } 448 | if (mode != MODE.FREE) 449 | { 450 | getParent().requestDisallowInterceptTouchEvent(false); 451 | } 452 | startX = 0; 453 | mode = MODE.FREE; 454 | recycleVelocityTracker(); 455 | break; 456 | } 457 | return true; 458 | } 459 | 460 | private float calculateV(float xVelocity, float yVelocity) 461 | { 462 | return (float) Math.sqrt(xVelocity * xVelocity + yVelocity * yVelocity); 463 | } 464 | 465 | /** 466 | * 拖动时处理 467 | */ 468 | public abstract void onBeingDragging(MotionEvent event, float v); 469 | 470 | public abstract void onDragFinished(MotionEvent event); 471 | 472 | public abstract void onExchange(); 473 | 474 | /** 475 | * 拖动开始时的初始化 476 | */ 477 | public void onStartFling(View itemView, int x, int y) 478 | { 479 | itemView.destroyDrawingCache(); 480 | itemView.setDrawingCacheEnabled(true); 481 | Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache()); 482 | itemView.setVisibility(View.INVISIBLE); 483 | dragPointX = x - itemView.getLeft() + getmCurScreen() * getMeasuredWidth(); 484 | dragPointY = y - itemView.getTop(); 485 | int viewX = x - dragPointX + dragOffsetX; 486 | int viewy = y - dragPointY + dragOffsetY; 487 | FingerFlowViewManager.getInstance().setUp(getContext(), bm, viewX, viewy); 488 | } 489 | 490 | @Override 491 | protected void onDetachedFromWindow() 492 | { 493 | super.onDetachedFromWindow(); 494 | // Log.e("SpringboardView", "onDetachedFromWindow"); 495 | FingerFlowViewManager.onDestory(); 496 | } 497 | 498 | /** 499 | * 开始拖动 500 | */ 501 | private void startDrag(View itemView) 502 | { 503 | mode = MODE.DRAGGING; 504 | getParent().requestDisallowInterceptTouchEvent(true); 505 | // halfBitmapWidth = itemView.getWidth() / 2; 506 | // halfBitmapHeight = itemView.getHeight() / 2; 507 | temChangPosition = dragPosition = getChildIndex(itemView); 508 | // Log.e("初始拖动位置", "temChangPosition = " + temChangPosition); 509 | initOrResetVelocityTracker(); 510 | } 511 | 512 | public void endDrag() 513 | { 514 | mode = MODE.FREE; 515 | if (temChangPosition != -1) 516 | { 517 | getChildAt(temChangPosition).setVisibility(View.VISIBLE); 518 | } 519 | // showDropAnimation(x, y); 520 | getParent().requestDisallowInterceptTouchEvent(false); 521 | dragPosition = temChangPosition = -1; 522 | FingerFlowViewManager.getInstance().remove(); 523 | } 524 | 525 | private void recycleVelocityTracker() 526 | { 527 | if (mVelocityTracker != null) 528 | { 529 | mVelocityTracker.recycle(); 530 | mVelocityTracker = null; 531 | } 532 | } 533 | 534 | private void initOrResetVelocityTracker() 535 | { 536 | if (mVelocityTracker == null) 537 | { 538 | mVelocityTracker = VelocityTracker.obtain(); 539 | } else 540 | { 541 | mVelocityTracker.clear(); 542 | } 543 | } 544 | 545 | private void initVelocityTrackerIfNotExists() 546 | { 547 | if (mVelocityTracker == null) 548 | { 549 | mVelocityTracker = VelocityTracker.obtain(); 550 | } 551 | } 552 | 553 | /** 554 | * 更新拖动的坐标,让图标跟随手一起动 555 | * 556 | * @param x view左上角坐标X 557 | * @param y view左上角坐标Y 558 | */ 559 | public void onFling(int x, int y) 560 | { 561 | FingerFlowViewManager.getInstance().updatePosition(x, y); 562 | } 563 | 564 | 565 | /** 566 | * 如果正在进行滑动动画,就停止动画 567 | */ 568 | protected void stopScroll() 569 | { 570 | if (!mScroller.isFinished()) 571 | { 572 | // 中止移动动画 573 | mScroller.abortAnimation(); 574 | } 575 | } 576 | 577 | /** 578 | * 判断能否进行滑动 579 | */ 580 | protected boolean ifCanScroll(int deltaX) 581 | { 582 | 583 | if (Math.abs(deltaX) > mTouchSlop) 584 | { 585 | if (deltaX < 0) 586 | { 587 | if (getScrollX() > 0) 588 | { 589 | return true; 590 | } 591 | } else 592 | { 593 | if (getScrollX() < (getTotalPage() - 1) * getMeasuredWidth()) 594 | { 595 | return true; 596 | } 597 | } 598 | } 599 | return false; 600 | } 601 | 602 | protected void countPageChange(int x) 603 | { 604 | // Log.e("countPageChange"," x = " +x ); 605 | if (x > getWidth() - 40 606 | && mCurScreen < getTotalPage() - 1 && mScroller.isFinished() 607 | && x > startX + 10) 608 | { 609 | snapToScreen(mCurScreen + 1); 610 | } else if (x < 40 611 | && mCurScreen > 0 && mScroller.isFinished() && x < startX - 10) 612 | { 613 | snapToScreen(mCurScreen - 1); 614 | } 615 | } 616 | 617 | 618 | public void endScroll(float distance) 619 | { 620 | if (distance > getMeasuredWidth() / 6 && getmCurScreen() > 0) 621 | { 622 | snapToScreen(getmCurScreen() - 1); 623 | } else if (distance < -getMeasuredWidth() / 6 624 | && getmCurScreen() < getTotalPage() - 1) 625 | { 626 | snapToScreen(getmCurScreen() + 1); 627 | } else 628 | { 629 | final int screenWidth = getWidth(); 630 | final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth; 631 | if (destScreen >= 0 && destScreen < getTotalPage()) 632 | { 633 | snapToScreen(destScreen); 634 | } 635 | } 636 | } 637 | 638 | 639 | public void snapToScreen(int whichScreen) 640 | { 641 | // get the valid layout page 642 | // Log.e("snapToScreen","whichScreen" + whichScreen ); 643 | whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 644 | if (getScrollX() != (whichScreen * getWidth())) 645 | { 646 | final int delta = whichScreen * (getWidth() + page_divider_width) - getScrollX(); 647 | 648 | if (whichScreen != mCurScreen && onPageChangedListener != null) 649 | { 650 | onPageChangedListener.onPageScroll(mCurScreen, whichScreen); 651 | } 652 | mScroller.startScroll(getScrollX(), 0, delta, 0, 800); 653 | mCurScreen = whichScreen; 654 | postInvalidate();// Redraw the layout 655 | } 656 | } 657 | 658 | 659 | @Override 660 | public void computeScroll() 661 | { 662 | if (mScroller.computeScrollOffset()) 663 | { 664 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 665 | postInvalidate(); 666 | } 667 | } 668 | 669 | /** 670 | * 判断是否在文件夹内 671 | */ 672 | public boolean isInCenter(int position, MotionEvent event) 673 | { 674 | 675 | FrameLayout child = (FrameLayout) getChildAt(position); 676 | View view = child.findViewById(R.id.shake_zone_id).findViewById(R.id.center_zone_id); 677 | 678 | int[] location = new int[2]; 679 | // 获取控件在屏幕中的位置,返回的数组分别为控件左顶点的 x、y 的值 680 | view.getLocationOnScreen(location); 681 | 682 | float x = event.getRawX(); // 获取相对于屏幕左上角的 x 坐标值 683 | float y = event.getRawY(); 684 | RectF rect = new RectF(location[0], location[1], location[0] + view.getWidth(), 685 | location[1] + view.getHeight()); 686 | 687 | return rect.contains(x, y); 688 | } 689 | 690 | 691 | protected void setEditingMode(int position, View child, boolean isEditing, boolean ifShake) 692 | { 693 | ImageView imageView = (ImageView) child.findViewById(R.id.delete_button_id); 694 | FrameLayout layout = (FrameLayout) child.findViewById(R.id.shake_zone_id); 695 | 696 | if (isEditing) 697 | { 698 | if (ifCanDelete(position)) 699 | { 700 | imageView.setVisibility(View.VISIBLE); 701 | // Log.e("setEditingMode","position = canDelete" + position); 702 | } else 703 | { 704 | // Log.e("setEditingMode", "position = cannotDelete" + position); 705 | imageView.clearAnimation(); 706 | imageView.setVisibility(View.GONE); 707 | } 708 | 709 | if (ifCanMove(position) && ifShake) 710 | { 711 | Animation shake; 712 | if (imageView.getVisibility() == View.VISIBLE) 713 | { 714 | shake = AnimationUtils.loadAnimation(getContext(), R.anim.shake); 715 | imageView.startAnimation(shake); 716 | } 717 | shake = AnimationUtils.loadAnimation(getContext(), R.anim.shake_rotate); 718 | layout.startAnimation(shake); 719 | } else 720 | { 721 | if (imageView.getVisibility() == View.VISIBLE) 722 | { 723 | imageView.clearAnimation(); 724 | } 725 | layout.clearAnimation(); 726 | } 727 | } else 728 | { 729 | imageView.setVisibility(View.GONE); 730 | imageView.clearAnimation(); 731 | layout.clearAnimation(); 732 | } 733 | 734 | } 735 | 736 | public abstract boolean ifCanMove(int position); 737 | 738 | public abstract boolean ifCanDelete(int position); 739 | 740 | /** 741 | * 获得View在父容器中的位置 742 | */ 743 | public int getChildIndex(View view) 744 | { 745 | if (view != null) 746 | { 747 | final int childCount = ((ViewGroup) view.getParent()) 748 | .getChildCount(); 749 | for (int i = 0; i < childCount; i++) 750 | { 751 | if (view == ((ViewGroup) view.getParent()).getChildAt(i)) 752 | { 753 | return i; 754 | } 755 | } 756 | } 757 | return -1; 758 | } 759 | 760 | /** 761 | * @param x,y 触摸点所在的位置 762 | * 如果不在位置上返回-1,在的话返回位置 763 | */ 764 | public int pointToPosition(int x, int y) 765 | { 766 | int locX = x + mCurScreen * getWidth(); 767 | Rect frame = new Rect(); 768 | final int count = getChildCount(); 769 | for (int i = count - 1; i >= 0; i--) 770 | { 771 | final View child = getChildAt(i); 772 | child.getHitRect(frame); 773 | if (frame.contains(locX, y)) 774 | { 775 | return i; 776 | } 777 | } 778 | return -1; 779 | } 780 | 781 | /** 782 | * 当长按时开启编辑模式 783 | * 并开始滑动 784 | */ 785 | protected boolean onItemLongClick(View v) 786 | { 787 | if (mode != MODE.DRAGGING) 788 | { 789 | int index = getChildIndex(v); 790 | // Log.e("SpringboardView", "onItemLongClick 长按 位置是 " + index); 791 | getAdapter().setEditing(true); 792 | if (ifCanMove(index)) 793 | { 794 | startDrag(v); 795 | onStartFling(v, (int) mLastMotionX, (int) mLastMotionY); 796 | } 797 | return true; 798 | } 799 | return false; 800 | } 801 | 802 | public abstract void onItemClick(int position); 803 | 804 | /** 805 | * 计算有几页 806 | */ 807 | protected void computePageCountChange(boolean ifCallback) 808 | { 809 | int pages = (int) Math.ceil(getChildCount() * 1.0 / getPageItemCount()); 810 | 811 | if (pages == 0) 812 | { 813 | pages++; 814 | } 815 | 816 | if (pages != totalPage) 817 | { 818 | if (this.onPageChangedListener != null && ifCallback) 819 | { 820 | onPageChangedListener.onPageCountChange(totalPage, pages); 821 | } 822 | totalPage = pages; 823 | } 824 | } 825 | 826 | protected void onMerge(int fromPosition, int toPosition) 827 | { 828 | getAdapter().mergeItem(fromPosition, toPosition, defaultFolderName); 829 | temChangPosition = -1; 830 | computePageCountChange(true); 831 | } 832 | 833 | public void configView(FrameLayout view) 834 | { 835 | view.setOnLongClickListener(new View.OnLongClickListener() 836 | { 837 | @Override 838 | public boolean onLongClick(View v) 839 | { 840 | return onItemLongClick(v); 841 | } 842 | }); 843 | 844 | view.setOnClickListener(new View.OnClickListener() 845 | { 846 | @Override 847 | public void onClick(View arg0) 848 | { 849 | int position = indexOfChild(arg0); 850 | onItemClick(position); 851 | } 852 | }); 853 | ImageView imageView = new ImageView(getContext()); 854 | imageView.setImageDrawable(deleteIcon); 855 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 856 | params.gravity = Gravity.TOP | Gravity.END; 857 | params.setMargins(0, 4, 4, 0); 858 | imageView.setId(R.id.delete_button_id); 859 | view.addView(imageView, params); 860 | imageView.setVisibility(View.GONE); 861 | imageView.setOnClickListener(new OnClickListener() 862 | { 863 | @Override 864 | public void onClick(View v) 865 | { 866 | int index = getChildIndex((ViewGroup) v.getParent()); 867 | delete(index); 868 | } 869 | }); 870 | } 871 | 872 | 873 | public void setAdapter(SpringboardAdapter mAdapter) 874 | { 875 | this.mAdapter = mAdapter; 876 | refreshView(); 877 | computePageCountChange(false); 878 | } 879 | 880 | /** 881 | * 刷新页面 882 | */ 883 | private void refreshView() 884 | { 885 | removeAllViews(); 886 | int count = getItemCount(); 887 | 888 | for (int i = 0; i < count; i++) 889 | { 890 | FrameLayout view = initItemView(i); 891 | configView(view); 892 | this.addView(view); 893 | } 894 | } 895 | 896 | public abstract int getItemCount(); 897 | 898 | public abstract FrameLayout initItemView(int position); 899 | 900 | public void delete(int position) 901 | { 902 | onDelete(position); 903 | if (totalPage <= mCurScreen) 904 | { 905 | snapToScreen(totalPage - 1); 906 | } 907 | } 908 | 909 | public abstract void onDelete(int position); 910 | 911 | 912 | // /** 913 | // * 往下掉的动画 914 | // */ 915 | // protected void showDropAnimation(int x, int y) { 916 | // ViewGroup moveView = (ViewGroup) getChildAt(temChangPosition); 917 | // TranslateAnimation animation = new TranslateAnimation(x 918 | // - halfBitmapWidth - moveView.getLeft(), 0, y - halfBitmapHeight 919 | // - moveView.getTop(), 0); 920 | // animation.setFillAfter(false); 921 | // animation.setDuration(300); 922 | // moveView.setAnimation(animation); 923 | // for (int i = 0; i < getChildCount(); i++) { 924 | // getChildAt(i).clearAnimation(); 925 | // } 926 | // } 927 | 928 | // public PointF positionToPoint2(int position) { 929 | // PointF point = new PointF(); 930 | // 931 | // int page = position / getColCount() / getRowCount(); 932 | // 933 | // int row = position / getColCount() % getRowCount(); 934 | // 935 | // int col = (position - (page * getPageItemCount())) % getColCount(); 936 | // 937 | // 938 | // int left = getPaddingLeft() + (getWidth() + page_divider_width) * page + col * (dividerWidth + getChildWidth()) + dividerWidth; 939 | // 940 | // int top = getPaddingTop() + row * (dividerWidth + getChildHeight()); 941 | // 942 | // point.x = left; 943 | // 944 | // point.y = top; 945 | // 946 | // return point; 947 | // } 948 | 949 | public int getmMinimumVelocity() 950 | { 951 | return mMinimumVelocity; 952 | } 953 | 954 | public int getColCount() 955 | { 956 | return colCount; 957 | } 958 | 959 | public int getPageItemCount() 960 | { 961 | return rowCount * colCount; 962 | } 963 | 964 | public void setColCount(int colCount) 965 | { 966 | this.colCount = colCount; 967 | } 968 | 969 | public int getmCurScreen() 970 | { 971 | return mCurScreen; 972 | } 973 | 974 | public int getRowCount() 975 | { 976 | return rowCount; 977 | } 978 | 979 | public void setRowCount(int rowCount) 980 | { 981 | this.rowCount = rowCount; 982 | } 983 | 984 | public int getChildHeight() 985 | { 986 | return getChildAt(0).getWidth(); 987 | } 988 | 989 | public int getChildWidth() 990 | { 991 | return getChildAt(0).getHeight(); 992 | } 993 | 994 | public int getTotalPage() 995 | { 996 | return totalPage; 997 | } 998 | 999 | public SpringboardAdapter getAdapter() 1000 | { 1001 | return mAdapter; 1002 | } 1003 | 1004 | public OnPageChangedListener getOnPageChangedListener() 1005 | { 1006 | return onPageChangedListener; 1007 | } 1008 | 1009 | public void setOnPageChangedListener(OnPageChangedListener onPageChangedListener) 1010 | { 1011 | this.onPageChangedListener = onPageChangedListener; 1012 | } 1013 | 1014 | public interface OnPageChangedListener 1015 | { 1016 | void onPageScroll(int from, int to); 1017 | 1018 | void onPageCountChange(int oldCount, int newCount); 1019 | } 1020 | 1021 | public interface OnItemClickListener 1022 | { 1023 | void onItemClick(FavoritesItem item); 1024 | } 1025 | 1026 | private OnItemClickListener onItemClickListener; 1027 | 1028 | public OnItemClickListener getOnItemClickListener() 1029 | { 1030 | return onItemClickListener; 1031 | } 1032 | 1033 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) 1034 | { 1035 | this.onItemClickListener = onItemClickListener; 1036 | } 1037 | 1038 | public int getStableHeaderCount() 1039 | { 1040 | return stableHeaderCount; 1041 | } 1042 | 1043 | public void setStableHeaderCount(int stableHeaderCount) 1044 | { 1045 | this.stableHeaderCount = stableHeaderCount; 1046 | } 1047 | 1048 | public boolean isLastItemStable() 1049 | { 1050 | return isLastItemStable; 1051 | } 1052 | 1053 | public void setIsLastItemStable(boolean isLastItemStable) 1054 | { 1055 | this.isLastItemStable = isLastItemStable; 1056 | } 1057 | 1058 | } 1059 | -------------------------------------------------------------------------------- /library/src/main/res/anim/cycle_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /library/src/main/res/anim/shake.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 23 | -------------------------------------------------------------------------------- /library/src/main/res/anim/shake_rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/icon_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/library/src/main/res/drawable-hdpi/icon_delete.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/folder_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /library/src/main/res/layout/folder_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 23 | 24 | 25 | 40 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /library/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 文件夹 3 | SpringBoard 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/res/values/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /library/src/test/java/com/panxiaohe/springboardlib/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.panxiaohe.springboard; 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 | } -------------------------------------------------------------------------------- /pic/screenShot1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohepan/springboardView/e78fc77bd1dd81a84c20b59591853b154b349f96/pic/screenShot1.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------