├── .gitignore ├── .gradle ├── 2.2.1 │ └── taskArtifacts │ │ ├── cache.properties │ │ ├── cache.properties.lock │ │ ├── fileHashes.bin │ │ ├── fileSnapshots.bin │ │ ├── outputFileStates.bin │ │ └── taskArtifacts.bin └── 2.4 │ └── taskArtifacts │ ├── cache.properties │ ├── cache.properties.lock │ ├── fileHashes.bin │ ├── fileSnapshots.bin │ ├── outputFileStates.bin │ └── taskArtifacts.bin ├── .idea ├── compiler.xml ├── gradle.xml ├── modules.xml ├── runConfigurations.xml ├── vcs.xml └── workspace.xml ├── .travis.yml ├── FlowingDrawer.iml ├── License ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mxn │ │ └── soul │ │ └── flowingdrawer │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mxn │ │ └── soul │ │ └── flowingdrawer │ │ ├── CircleTransformation.java │ │ ├── FeedAdapter.java │ │ ├── MainActivity.java │ │ ├── MenuListFragment.java │ │ └── SquaredFrameLayout.java │ └── res │ ├── anim │ ├── slide_in_likes_counter.xml │ └── slide_out_likes_counter.xml │ ├── drawable-xxhdpi │ ├── bg.9.png │ ├── card_background.9.png │ ├── ic_comment_outline_grey.png │ ├── ic_feed_top.png │ ├── ic_global_menu_direct.png │ ├── ic_global_menu_feed.png │ ├── ic_global_menu_likes.png │ ├── ic_global_menu_nearby.png │ ├── ic_global_menu_news.png │ ├── ic_global_menu_popular.png │ ├── ic_global_menu_search.png │ ├── ic_heart_outline_grey.png │ ├── ic_heart_small_blue.png │ ├── ic_menu_white.png │ ├── ic_more_grey.png │ ├── img_feed_bottom_1.png │ ├── img_feed_bottom_2.png │ ├── img_feed_center_1.png │ └── img_feed_center_2.png │ ├── drawable │ ├── btn_feed_action.xml │ └── img_circle_placeholder.xml │ ├── layout │ ├── activity_main.xml │ ├── fragment_menu.xml │ ├── item_feed.xml │ ├── view_feed_toolbar.xml │ └── view_global_menu_header.xml │ ├── menu │ ├── drawer_menu.xml │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── build └── intermediates │ ├── dex-cache │ └── cache.xml │ └── gradle_project_sync_data.bin ├── flowingdrawer-core ├── .gitignore ├── build.gradle ├── flowingdrawer-core.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mxn │ │ └── soul │ │ └── flowingdrawer_core │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mxn │ │ └── soul │ │ └── flowingdrawer_core │ │ ├── BuildLayerFrameLayout.java │ │ ├── ElasticDrawer.java │ │ ├── FlowingAnimationListener.java │ │ ├── FlowingDrawer.java │ │ ├── FlowingMenuLayout.java │ │ ├── NoClickThroughFrameLayout.java │ │ ├── SmoothInterpolator.java │ │ └── ViewHelper.java │ └── res │ └── values │ └── attrs.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screen.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ## Android 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | 16 | ## Gradle files 17 | #.gradle/ 18 | build/ 19 | /*/build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # IDE Files 28 | .idea/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Gradle files 34 | .gradle 35 | .gradle/ 36 | 37 | 38 | .idea/copyright/profiles_settings.xml 39 | .idea/misc.xml 40 | *.iml -------------------------------------------------------------------------------- /.gradle/2.2.1/taskArtifacts/cache.properties: -------------------------------------------------------------------------------- 1 | #Mon Oct 19 12:03:22 CST 2015 2 | -------------------------------------------------------------------------------- /.gradle/2.2.1/taskArtifacts/cache.properties.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.2.1/taskArtifacts/cache.properties.lock -------------------------------------------------------------------------------- /.gradle/2.2.1/taskArtifacts/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.2.1/taskArtifacts/fileHashes.bin -------------------------------------------------------------------------------- /.gradle/2.2.1/taskArtifacts/fileSnapshots.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.2.1/taskArtifacts/fileSnapshots.bin -------------------------------------------------------------------------------- /.gradle/2.2.1/taskArtifacts/outputFileStates.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.2.1/taskArtifacts/outputFileStates.bin -------------------------------------------------------------------------------- /.gradle/2.2.1/taskArtifacts/taskArtifacts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.2.1/taskArtifacts/taskArtifacts.bin -------------------------------------------------------------------------------- /.gradle/2.4/taskArtifacts/cache.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 15 23:41:09 CST 2015 2 | -------------------------------------------------------------------------------- /.gradle/2.4/taskArtifacts/cache.properties.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.4/taskArtifacts/cache.properties.lock -------------------------------------------------------------------------------- /.gradle/2.4/taskArtifacts/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.4/taskArtifacts/fileHashes.bin -------------------------------------------------------------------------------- /.gradle/2.4/taskArtifacts/fileSnapshots.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.4/taskArtifacts/fileSnapshots.bin -------------------------------------------------------------------------------- /.gradle/2.4/taskArtifacts/outputFileStates.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.4/taskArtifacts/outputFileStates.bin -------------------------------------------------------------------------------- /.gradle/2.4/taskArtifacts/taskArtifacts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/.gradle/2.4/taskArtifacts/taskArtifacts.bin -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | notifications: 4 | email: false 5 | 6 | sudo: false 7 | 8 | android: 9 | components: 10 | - tools 11 | - build-tools-23.0.2 12 | - android-23 13 | - extra-android-m2repository 14 | - extra-android-support 15 | 16 | before_install: 17 | - chmod +x gradlew 18 | 19 | script: 20 | - chmod +x ./gradlew 21 | 22 | before_deploy: 23 | - mv app/build/outputs/apk/app-release.apk app/build/outputs/apk/FlowingDrawer.apk 24 | 25 | deploy: 26 | provider: releases 27 | api_key: 28 | secure: 3cb2c71a8165ed3eb18990692d0383c1cc975a18 29 | file: app/build/outputs/apk/FlowingDrawer.apk 30 | skip_cleanup: true 31 | on: 32 | tags: true -------------------------------------------------------------------------------- /FlowingDrawer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlowingDrawer 2 | 3 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-FlowingDrawer-green.svg?style=flat)](https://android-arsenal.com/details/1/2658) 4 | [![travis-ic](https://travis-ci.org/mxn21/FlowingDrawer.svg?branch=master)](https://travis-ci.org/mxn21/FlowingDrawer) 5 | 6 | ![Showcase](/screen.gif) 7 | 8 | swipe right to display drawer with flowing effects. 9 | 10 | 11 | ## Download 12 | 13 | Include the following dependency in your build.gradle file. 14 | 15 | Gradle: 16 | 17 | ```Gradle 18 | repositories { 19 | jcenter() 20 | } 21 | 22 | dependencies { 23 | implementation 'com.mxn.soul:flowingdrawer-core:2.1.0' 24 | implementation 'com.nineoldandroids:library:2.4.0' 25 | } 26 | ``` 27 | 28 | ## V2.0.0 Features 29 | 30 | * The menu can be positioned along two edges:left and right . 31 | * Allows the drawer to be opened by dragging the edge or the entire screen. 32 | 33 | 34 | ## Usage 35 | 36 | *For a working implementation of this project see the `app/` folder and check out the sample app* 37 | 38 | activity_main.xml: 39 | 40 | ```xml 41 | 52 | 53 | 54 | 58 | 59 | 60 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | ``` 74 | 75 | To use a FlowingDrawer, position FlowingDrawer as the root , position your primary content view as the 76 | first child with width and height of match_parent . Add FlowingMenuLayout as child views after the main 77 | content view . FlowingMenuLayout commonly use match_parent for height and width. 78 | 79 | Don't set any background on FlowingMenuLayout or FlowingMenuLayout's children, it means their background 80 | should be transparent. 81 | 82 | Don't set FlowingMenuLayout's width with a fixed width, it's not a useful way to change it's width . 83 | 84 | You can change menu's attribute in FlowingDrawer layout node use custom attribute,like edMenuBackground,edMenuSize, 85 | edPosition. 86 | 87 | Use edPosition attribute corresponding to which side of the view you want the drawer 88 | to emerge from: left or right.Left menu : edPosition =1 ; Right menu: edPosition =2 . 89 | 90 | For more custom attribute ,you can see in attrs.xml. 91 | 92 | 93 | MainActivity: 94 | 95 | ```java 96 | mDrawer = (FlowingDrawer) findViewById(R.id.drawerlayout); 97 | mDrawer.setTouchMode(ElasticDrawer.TOUCH_MODE_BEZEL); 98 | mDrawer.setOnDrawerStateChangeListener(new ElasticDrawer.OnDrawerStateChangeListener() { 99 | @Override 100 | public void onDrawerStateChange(int oldState, int newState) { 101 | if (newState == ElasticDrawer.STATE_CLOSED) { 102 | Log.i("MainActivity", "Drawer STATE_CLOSED"); 103 | } 104 | } 105 | 106 | @Override 107 | public void onDrawerSlide(float openRatio, int offsetPixels) { 108 | Log.i("MainActivity", "openRatio=" + openRatio + " ,offsetPixels=" + offsetPixels); 109 | } 110 | }); 111 | ``` 112 | setTouchMode can allows the drawer to be opened by dragging the edge or the entire screen. 113 | setOnDrawerStateChangeListener can be used to monitor the state and motion of drawer views. 114 | Avoid performing expensive operations such as layout during animation as it can cause stuttering. 115 | ElasticDrawer.OnDrawerStateChangeListener offers default/no-op implementations of each callback method. 116 | 117 | 118 | License 119 | ======= 120 | 121 | Copyright 2015 soul.mxn 122 | 123 | Licensed under the Apache License, Version 2.0 (the "License"); 124 | you may not use this file except in compliance with the License. 125 | You may obtain a copy of the License at 126 | 127 | http://www.apache.org/licenses/LICENSE-2.0 128 | 129 | Unless required by applicable law or agreed to in writing, software 130 | distributed under the License is distributed on an "AS IS" BASIS, 131 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132 | See the License for the specific language governing permissions and 133 | limitations under the License. 134 | 135 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | <<<<<<< HEAD 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | ======= 93 | 94 | 95 | 96 | 97 | 98 | >>>>>>> origin/mxn-2.0test-master 99 | 100 | 101 | 102 | 103 | 104 | 105 | <<<<<<< HEAD 106 | 107 | 108 | 109 | 110 | 111 | ======= 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | >>>>>>> origin/mxn-2.0test-master 122 | 123 | 124 | 125 | 126 | 127 | <<<<<<< HEAD 128 | 129 | 130 | 131 | 132 | ======= 133 | 134 | 135 | >>>>>>> origin/mxn-2.0test-master 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | defaultConfig { 7 | applicationId "com.mxn.soul.flowingdrawer" 8 | minSdkVersion 11 9 | targetSdkVersion 26 10 | versionCode 120 11 | versionName "1.2.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 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:22.2.0' 24 | implementation 'com.android.support:recyclerview-v7:22.+' 25 | implementation 'com.android.support:design:22.2.1' 26 | implementation 'com.squareup.picasso:picasso:2.5.0' 27 | implementation 'com.android.support:cardview-v7:22.+' 28 | implementation 'com.nineoldandroids:library:2.4.0' 29 | implementation 'com.mxn.soul:flowingdrawer-core:2.1.0' 30 | // implementation project(':flowingdrawer-core') 31 | } 32 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/cys/Public/androidADT/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/mxn/soul/flowingdrawer/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer; 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 | 6 | 7 | 8 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/mxn/soul/flowingdrawer/CircleTransformation.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer; 2 | 3 | import com.squareup.picasso.Transformation; 4 | 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapShader; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | 11 | 12 | class CircleTransformation implements Transformation { 13 | 14 | private static final int STROKE_WIDTH = 6; 15 | 16 | @Override 17 | public Bitmap transform(Bitmap source) { 18 | int size = Math.min(source.getWidth(), source.getHeight()); 19 | 20 | int x = (source.getWidth() - size) / 2; 21 | int y = (source.getHeight() - size) / 2; 22 | 23 | Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); 24 | if (squaredBitmap != source) { 25 | source.recycle(); 26 | } 27 | 28 | Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); 29 | 30 | Canvas canvas = new Canvas(bitmap); 31 | 32 | Paint avatarPaint = new Paint(); 33 | BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); 34 | avatarPaint.setShader(shader); 35 | 36 | Paint outlinePaint = new Paint(); 37 | outlinePaint.setColor(Color.WHITE); 38 | outlinePaint.setStyle(Paint.Style.STROKE); 39 | outlinePaint.setStrokeWidth(STROKE_WIDTH); 40 | outlinePaint.setAntiAlias(true); 41 | 42 | float r = size / 2f; 43 | canvas.drawCircle(r, r, r, avatarPaint); 44 | canvas.drawCircle(r, r, r - STROKE_WIDTH / 2, outlinePaint); 45 | 46 | squaredBitmap.recycle(); 47 | return bitmap; 48 | } 49 | 50 | @Override 51 | public String key() { 52 | return "circleTransformation()"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/mxn/soul/flowingdrawer/FeedAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.FrameLayout; 9 | import android.widget.ImageButton; 10 | import android.widget.ImageView; 11 | import android.widget.TextSwitcher; 12 | 13 | 14 | class FeedAdapter extends RecyclerView.Adapter { 15 | 16 | private Context context; 17 | private int itemsCount = 0; 18 | 19 | FeedAdapter(Context context) { 20 | this.context = context; 21 | } 22 | 23 | @Override 24 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 25 | final View view = LayoutInflater.from(context).inflate(R.layout.item_feed, parent, false); 26 | 27 | return new CellFeedViewHolder(view); 28 | } 29 | 30 | 31 | @Override 32 | public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { 33 | final CellFeedViewHolder holder = (CellFeedViewHolder) viewHolder; 34 | bindDefaultFeedItem(position, holder); 35 | } 36 | 37 | private void bindDefaultFeedItem(int position, CellFeedViewHolder holder) { 38 | if (position % 2 == 0) { 39 | holder.ivFeedCenter.setImageResource(R.drawable.img_feed_center_1); 40 | holder.ivFeedBottom.setImageResource(R.drawable.img_feed_bottom_1); 41 | } else { 42 | holder.ivFeedCenter.setImageResource(R.drawable.img_feed_center_2); 43 | holder.ivFeedBottom.setImageResource(R.drawable.img_feed_bottom_2); 44 | } 45 | 46 | holder.btnComments.setTag(position); 47 | holder.btnMore.setTag(position); 48 | holder.ivFeedCenter.setTag(holder); 49 | holder.btnLike.setTag(holder); 50 | 51 | } 52 | 53 | void updateItems() { 54 | itemsCount = 10; 55 | notifyDataSetChanged(); 56 | } 57 | 58 | @Override 59 | public int getItemViewType(int position) { 60 | return 1; 61 | } 62 | 63 | @Override 64 | public int getItemCount() { 65 | return itemsCount; 66 | } 67 | 68 | private static class CellFeedViewHolder extends RecyclerView.ViewHolder { 69 | ImageView ivFeedCenter; 70 | ImageView ivFeedBottom; 71 | ImageButton btnComments; 72 | ImageButton btnLike; 73 | ImageButton btnMore; 74 | TextSwitcher tsLikesCounter; 75 | ImageView ivUserProfile; 76 | FrameLayout vImageRoot; 77 | 78 | CellFeedViewHolder(View view) { 79 | super(view); 80 | 81 | ivFeedCenter = (ImageView) view.findViewById(R.id.ivFeedCenter); 82 | ivFeedBottom = (ImageView) view.findViewById(R.id.ivFeedBottom); 83 | btnComments = (ImageButton) view.findViewById(R.id.btnComments); 84 | btnLike = (ImageButton) view.findViewById(R.id.btnLike); 85 | btnMore = (ImageButton) view.findViewById(R.id.btnMore); 86 | tsLikesCounter = (TextSwitcher) view.findViewById(R.id.tsLikesCounter); 87 | ivUserProfile = (ImageView) view.findViewById(R.id.ivUserProfile); 88 | vImageRoot = (FrameLayout) view.findViewById(R.id.vImageRoot); 89 | } 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/mxn/soul/flowingdrawer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer; 2 | 3 | import com.mxn.soul.flowingdrawer_core.ElasticDrawer; 4 | import com.mxn.soul.flowingdrawer_core.FlowingDrawer; 5 | 6 | import android.os.Bundle; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.View; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private RecyclerView rvFeed; 17 | private FlowingDrawer mDrawer; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | rvFeed = (RecyclerView) findViewById(R.id.rvFeed); 25 | mDrawer = (FlowingDrawer) findViewById(R.id.drawerlayout); 26 | 27 | rvFeed = (RecyclerView) findViewById(R.id.rvFeed); 28 | mDrawer = (FlowingDrawer) findViewById(R.id.drawerlayout); 29 | mDrawer.setTouchMode(ElasticDrawer.TOUCH_MODE_BEZEL); 30 | setupToolbar(); 31 | setupFeed(); 32 | setupMenu(); 33 | } 34 | 35 | protected void setupToolbar() { 36 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 37 | setSupportActionBar(toolbar); 38 | toolbar.setNavigationIcon(R.drawable.ic_menu_white); 39 | 40 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | mDrawer.toggleMenu(); 44 | } 45 | }); 46 | } 47 | 48 | private void setupFeed() { 49 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) { 50 | @Override 51 | protected int getExtraLayoutSpace(RecyclerView.State state) { 52 | return 300; 53 | } 54 | }; 55 | rvFeed.setLayoutManager(linearLayoutManager); 56 | FeedAdapter feedAdapter = new FeedAdapter(this); 57 | rvFeed.setAdapter(feedAdapter); 58 | feedAdapter.updateItems(); 59 | } 60 | 61 | private void setupMenu() { 62 | FragmentManager fm = getSupportFragmentManager(); 63 | MenuListFragment mMenuFragment = (MenuListFragment) fm.findFragmentById(R.id.id_container_menu); 64 | if (mMenuFragment == null) { 65 | mMenuFragment = new MenuListFragment(); 66 | fm.beginTransaction().add(R.id.id_container_menu, mMenuFragment).commit(); 67 | } 68 | 69 | // mDrawer.setOnDrawerStateChangeListener(new ElasticDrawer.OnDrawerStateChangeListener() { 70 | // @Override 71 | // public void onDrawerStateChange(int oldState, int newState) { 72 | // if (newState == ElasticDrawer.STATE_CLOSED) { 73 | // Log.i("MainActivity", "Drawer STATE_CLOSED"); 74 | // } 75 | // } 76 | // 77 | // @Override 78 | // public void onDrawerSlide(float openRatio, int offsetPixels) { 79 | // Log.i("MainActivity", "openRatio=" + openRatio + " ,offsetPixels=" + offsetPixels); 80 | // } 81 | // }); 82 | } 83 | 84 | @Override 85 | public void onBackPressed() { 86 | if (mDrawer.isMenuVisible()) { 87 | mDrawer.closeMenu(); 88 | } else { 89 | super.onBackPressed(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/mxn/soul/flowingdrawer/MenuListFragment.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mxn.soul.flowingdrawer; 3 | 4 | import com.squareup.picasso.Picasso; 5 | 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.design.widget.NavigationView; 9 | import android.support.v4.app.Fragment; 10 | import android.view.LayoutInflater; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.ImageView; 15 | import android.widget.Toast; 16 | 17 | /** 18 | * Created by mxn on 2016/12/13. 19 | * MenuListFragment 20 | */ 21 | 22 | public class MenuListFragment extends Fragment { 23 | 24 | private ImageView ivMenuUserProfilePhoto; 25 | 26 | @Override 27 | public void onCreate(@Nullable Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | } 30 | 31 | 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 34 | View view = inflater.inflate(R.layout.fragment_menu, container, 35 | false); 36 | ivMenuUserProfilePhoto = (ImageView) view.findViewById(R.id.ivMenuUserProfilePhoto); 37 | NavigationView vNavigation = (NavigationView) view.findViewById(R.id.vNavigation); 38 | vNavigation.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { 39 | @Override 40 | public boolean onNavigationItemSelected(MenuItem menuItem) { 41 | Toast.makeText(getActivity(),menuItem.getTitle(),Toast.LENGTH_SHORT).show(); 42 | return false; 43 | } 44 | }) ; 45 | setupHeader(); 46 | return view ; 47 | } 48 | 49 | private void setupHeader() { 50 | int avatarSize = getResources().getDimensionPixelSize(R.dimen.global_menu_avatar_size); 51 | String profilePhoto = getResources().getString(R.string.user_profile_photo); 52 | Picasso.with(getActivity()) 53 | .load(profilePhoto) 54 | .placeholder(R.drawable.img_circle_placeholder) 55 | .resize(avatarSize, avatarSize) 56 | .centerCrop() 57 | .transform(new CircleTransformation()) 58 | .into(ivMenuUserProfilePhoto); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/mxn/soul/flowingdrawer/SquaredFrameLayout.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.widget.FrameLayout; 8 | 9 | 10 | public class SquaredFrameLayout extends FrameLayout { 11 | public SquaredFrameLayout(Context context) { 12 | super(context); 13 | } 14 | 15 | public SquaredFrameLayout(Context context, AttributeSet attrs) { 16 | super(context, attrs); 17 | } 18 | 19 | public SquaredFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 20 | super(context, attrs, defStyleAttr); 21 | } 22 | 23 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 24 | public SquaredFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 25 | super(context, attrs, defStyleAttr, defStyleRes); 26 | } 27 | 28 | @Override 29 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 30 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_likes_counter.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_likes_counter.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/card_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/card_background.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_comment_outline_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_comment_outline_grey.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_feed_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_feed_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_direct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_direct.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_feed.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_likes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_likes.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_nearby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_nearby.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_news.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_popular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_popular.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_global_menu_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_global_menu_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_heart_outline_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_heart_outline_grey.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_heart_small_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_heart_small_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_menu_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_menu_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_more_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/ic_more_grey.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_feed_bottom_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/img_feed_bottom_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_feed_bottom_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/img_feed_bottom_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_feed_center_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/img_feed_center_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_feed_center_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/drawable-xxhdpi/img_feed_center_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_feed_action.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_circle_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 19 | 25 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_menu.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 21 | 22 | 26 | 27 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 46 | 47 | 53 | 54 | 60 | 61 | 67 | 68 | 73 | 74 | 78 | 79 | 87 | 88 | 93 | 94 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_feed_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_global_menu_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 17 | 18 | 27 | 28 | 35 | 36 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/menu/drawer_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | 19 | 23 | 27 | 28 | 29 | 32 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2d5d82 4 | #21425d 5 | #01bcd5 6 | #007787 7 | #44000000 8 | #2b5a83 9 | #dddddd 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 88dp 4 | 64dp 5 | 8dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FlowingDrawer 3 | 4 | Hello world! 5 | Settings 6 | http://baobaoloveyou.com/flowingdrawer_icon.png 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /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 | maven { 7 | url 'https://maven.google.com/' 8 | name 'Google' 9 | } 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.2.1' 13 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' 14 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 15 | 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | jcenter() 25 | maven { 26 | url 'https://maven.google.com/' 27 | name 'Google' 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /build/intermediates/dex-cache/cache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 38 | 39 | 40 | 47 | 48 | 49 | 56 | 57 | 58 | 65 | 66 | 67 | 74 | 75 | 76 | 83 | 84 | 85 | 92 | 93 | 94 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /build/intermediates/gradle_project_sync_data.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/build/intermediates/gradle_project_sync_data.bin -------------------------------------------------------------------------------- /flowingdrawer-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /flowingdrawer-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply plugin: 'com.github.dcendents.android-maven' 3 | //apply plugin: 'com.jfrog.bintray' 4 | 5 | android { 6 | compileSdkVersion 26 7 | 8 | defaultConfig { 9 | minSdkVersion 11 10 | targetSdkVersion 26 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 | compileOptions { 21 | encoding "UTF-8" 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | compileOnly 'com.nineoldandroids:library:2.4.0' 28 | } 29 | 30 | /** 31 | // 根节点添加 32 | version = "2.1.0" 33 | 34 | def siteUrl = 'https://github.com/mxn21/FlowingDrawer' // project homepage 35 | def gitUrl = 'https://github.com/mxn21/FlowingDrawer.git' // project git 36 | 37 | group = "com.mxn.soul" 38 | 39 | tasks.withType(JavaCompile) { 40 | options.encoding = "UTF-8" 41 | } 42 | 43 | install { 44 | repositories.mavenInstaller { 45 | // This generates POM.xml with proper parameters 46 | pom { 47 | project { 48 | packaging 'aar' 49 | name 'flowingdrawer' 50 | url siteUrl 51 | licenses { 52 | license { 53 | name 'The Apache Software License, Version 2.0' 54 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 55 | } 56 | } 57 | developers { 58 | developer { 59 | id 'mxn21' 60 | name 'mxn' 61 | email 'mxny21@gmail.com' 62 | } 63 | } 64 | scm { 65 | connection gitUrl 66 | developerConnection gitUrl 67 | url siteUrl 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | 75 | task sourcesJar(type: Jar) { 76 | from android.sourceSets.main.java.srcDirs 77 | classifier = 'sources' 78 | } 79 | 80 | task javadoc(type: Javadoc) { 81 | source = android.sourceSets.main.java.srcDirs 82 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 83 | } 84 | 85 | task javadocJar(type: Jar, dependsOn: javadoc) { 86 | classifier = 'javadoc' 87 | from javadoc.destinationDir 88 | } 89 | 90 | 91 | artifacts { 92 | // archives javadocJar 93 | archives sourcesJar 94 | } 95 | 96 | Properties properties = new Properties() 97 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 98 | 99 | bintray { 100 | user = properties.getProperty("bintray.user") 101 | key = properties.getProperty("bintray.apikey") 102 | configurations = ['archives'] 103 | pkg { 104 | repo = "maven" 105 | name = "flowingdrawer" // #CONFIG# project name in 106 | // jcenter 107 | websiteUrl = siteUrl 108 | vcsUrl = gitUrl 109 | licenses = ["Apache-2.0"] 110 | publish = true 111 | } 112 | } 113 | **/ 114 | -------------------------------------------------------------------------------- /flowingdrawer-core/flowingdrawer-core.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | <<<<<<< HEAD 71 | ======= 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | <<<<<<< HEAD 80 | 81 | >>>>>>> origin/mxn-2.0test-master 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | ======= 103 | 104 | 105 | >>>>>>> master 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /flowingdrawer-core/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/cys/Public/androidADT/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 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/androidTest/java/com/mxn/soul/flowingdrawer_core/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer_core; 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 | } -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/BuildLayerFrameLayout.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mxn.soul.flowingdrawer_core; 3 | 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.PaintFlagsDrawFilter; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.widget.FrameLayout; 11 | 12 | /** 13 | * Created by mxn on 2016/10/17. 14 | * BuildLayerFrameLayout 15 | */ 16 | public class BuildLayerFrameLayout extends FrameLayout { 17 | 18 | private boolean mChanged; 19 | 20 | private boolean mHardwareLayersEnabled = true; 21 | 22 | private boolean mAttached; 23 | 24 | private boolean mFirst = true; 25 | 26 | public BuildLayerFrameLayout(Context context) { 27 | super(context); 28 | setClipChildren(false); 29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 30 | setLayerType(LAYER_TYPE_HARDWARE, null); 31 | } 32 | } 33 | 34 | public BuildLayerFrameLayout(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 37 | setLayerType(LAYER_TYPE_HARDWARE, null); 38 | } 39 | } 40 | 41 | public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) { 42 | super(context, attrs, defStyle); 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 44 | setLayerType(LAYER_TYPE_HARDWARE, null); 45 | } 46 | } 47 | 48 | void setHardwareLayersEnabled(boolean enabled) { 49 | mHardwareLayersEnabled = enabled; 50 | } 51 | 52 | @Override 53 | protected void onAttachedToWindow() { 54 | super.onAttachedToWindow(); 55 | mAttached = true; 56 | } 57 | 58 | @Override 59 | protected void onDetachedFromWindow() { 60 | super.onDetachedFromWindow(); 61 | mAttached = false; 62 | } 63 | 64 | @Override 65 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 66 | super.onSizeChanged(w, h, oldw, oldh); 67 | 68 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && mHardwareLayersEnabled) { 69 | post(new Runnable() { 70 | @Override 71 | public void run() { 72 | mChanged = true; 73 | invalidate(); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | @Override 80 | protected void dispatchDraw(Canvas canvas) { 81 | super.dispatchDraw(canvas); 82 | if (mChanged && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 83 | post(new Runnable() { 84 | @Override 85 | public void run() { 86 | if (mAttached && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 87 | final int layerType = getLayerType(); 88 | // If it's already a hardware layer, it'll be built anyway. 89 | if (layerType != LAYER_TYPE_HARDWARE || mFirst) { 90 | mFirst = false; 91 | setLayerType(LAYER_TYPE_HARDWARE, null); 92 | buildLayer(); 93 | setLayerType(LAYER_TYPE_NONE, null); 94 | } 95 | } 96 | } 97 | }); 98 | mChanged = false; 99 | } 100 | PaintFlagsDrawFilter pfd = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint 101 | .FILTER_BITMAP_FLAG); 102 | canvas.setDrawFilter(pfd); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/ElasticDrawer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mxn.soul.flowingdrawer_core; 3 | 4 | import com.nineoldandroids.animation.Animator; 5 | import com.nineoldandroids.animation.ValueAnimator; 6 | 7 | import android.annotation.SuppressLint; 8 | import android.content.Context; 9 | import android.content.res.TypedArray; 10 | import android.graphics.Canvas; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.Parcel; 14 | import android.os.Parcelable; 15 | import android.util.AttributeSet; 16 | import android.util.Log; 17 | import android.view.VelocityTracker; 18 | import android.view.View; 19 | import android.view.ViewConfiguration; 20 | import android.view.ViewGroup; 21 | import android.view.animation.DecelerateInterpolator; 22 | import android.view.animation.Interpolator; 23 | import android.view.animation.OvershootInterpolator; 24 | import android.widget.Scroller; 25 | 26 | /** 27 | * Created by mxn on 2016/10/15. 28 | * ElasticDrawer 29 | */ 30 | public abstract class ElasticDrawer extends ViewGroup { 31 | 32 | /** 33 | * Tag used when logging. 34 | */ 35 | private static final String TAG = "ElasticDrawer"; 36 | 37 | /** 38 | * Indicates whether debug code should be enabled. 39 | */ 40 | private static final boolean DEBUG = false; 41 | /** 42 | * The time between each frame when animating the drawer. 43 | */ 44 | protected static final int ANIMATION_DELAY = 1000 / 60; 45 | 46 | /** 47 | * Indicates whether the menu is currently visible. 48 | */ 49 | protected boolean mMenuVisible; 50 | /** 51 | * The size of the menu (width or height depending on the gravity). 52 | */ 53 | protected int mMenuSize; 54 | 55 | /** 56 | * The touch bezel size of the drawer in px. 57 | */ 58 | protected int mTouchBezelSize; 59 | 60 | /** 61 | * Interpolator used when animating the drawer open/closed. 62 | */ 63 | protected static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator(); 64 | 65 | /** 66 | * Slop before starting a drag. 67 | */ 68 | protected int mTouchSlop; 69 | /** 70 | * Maximum velocity allowed when animating the drawer open/closed. 71 | */ 72 | protected int mMaxVelocity; 73 | /** 74 | * Scroller used when animating the drawer open/closed. 75 | */ 76 | private Scroller mScroller; 77 | /** 78 | * Indicates whether the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}. 79 | */ 80 | protected boolean mLayerTypeHardware; 81 | /** 82 | * Indicates whether to use {@link View#LAYER_TYPE_HARDWARE} when animating the drawer. 83 | */ 84 | protected boolean mHardwareLayersEnabled = false; 85 | 86 | /** 87 | * The initial X position of a drag. 88 | */ 89 | protected float mInitialMotionX; 90 | 91 | /** 92 | * The initial Y position of a drag. 93 | */ 94 | protected float mInitialMotionY; 95 | 96 | /** 97 | * The last X position of a drag. 98 | */ 99 | protected float mLastMotionX = -1; 100 | 101 | /** 102 | * The last Y position of a drag. 103 | */ 104 | protected float mLastMotionY = -1; 105 | 106 | /** 107 | * Velocity tracker used when animating the drawer open/closed after a drag. 108 | */ 109 | protected VelocityTracker mVelocityTracker; 110 | 111 | /** 112 | * Distance in px from closed position from where the drawer is considered closed with regards to touch events. 113 | */ 114 | protected int mCloseEnough; 115 | 116 | /** 117 | * The position of the drawer. 118 | */ 119 | private int mPosition; 120 | 121 | private int mResolvedPosition; 122 | /** 123 | * Touch mode for the Drawer. 124 | * Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN} 125 | * Default: {@link #TOUCH_MODE_BEZEL} 126 | */ 127 | protected int mTouchMode = TOUCH_MODE_BEZEL; 128 | /** 129 | * The touch area size of the drawer in px. 130 | */ 131 | protected int mTouchSize; 132 | 133 | /** 134 | * The parent of the menu view. 135 | */ 136 | protected BuildLayerFrameLayout mMenuContainer; 137 | 138 | /** 139 | * The parent of the content view. 140 | */ 141 | protected BuildLayerFrameLayout mContentContainer; 142 | /** 143 | * The custom menu view set by the user. 144 | */ 145 | private FlowingMenuLayout mMenuView; 146 | 147 | /** 148 | * The color of the menu. 149 | */ 150 | protected int mMenuBackground; 151 | /** 152 | * Current offset. 153 | */ 154 | protected float mOffsetPixels; 155 | 156 | /** 157 | * The default touch bezel size of the drawer in dp. 158 | */ 159 | private static final int DEFAULT_DRAG_BEZEL_DP = 32; 160 | /** 161 | * Distance in dp from closed position from where the drawer is considered closed with regards to touch events. 162 | */ 163 | private static final int CLOSE_ENOUGH = 3; 164 | /** 165 | * Disallow opening the drawer by dragging the screen. 166 | */ 167 | public static final int TOUCH_MODE_NONE = 0; 168 | /** 169 | * Allow opening drawer only by dragging on the edge of the screen. 170 | */ 171 | public static final int TOUCH_MODE_BEZEL = 1; 172 | 173 | /** 174 | * Allow opening drawer by dragging anywhere on the screen. 175 | */ 176 | public static final int TOUCH_MODE_FULLSCREEN = 2; 177 | 178 | /** 179 | * Listener used to dispatch state change events. 180 | */ 181 | private OnDrawerStateChangeListener mOnDrawerStateChangeListener; 182 | /** 183 | * Callback that lets the listener override intercepting of touch events. 184 | */ 185 | protected OnInterceptMoveEventListener mOnInterceptMoveEventListener; 186 | 187 | /** 188 | * The maximum animation duration. 189 | */ 190 | private static final int DEFAULT_ANIMATION_DURATION = 600; 191 | /** 192 | * The maximum duration of open/close animations. 193 | */ 194 | protected int mMaxAnimationDuration = DEFAULT_ANIMATION_DURATION; 195 | /** 196 | * Indicates that the drawer is currently closed. 197 | */ 198 | public static final int STATE_CLOSED = 0; 199 | 200 | /** 201 | * Indicates that the drawer is currently closing. 202 | */ 203 | public static final int STATE_CLOSING = 1; 204 | 205 | /** 206 | * Indicates that the drawer is currently being dragged by the user. 207 | */ 208 | public static final int STATE_DRAGGING_OPEN = 2; 209 | 210 | /** 211 | * Indicates that the drawer is currently being dragged by the user. 212 | */ 213 | public static final int STATE_DRAGGING_CLOSE = 4; 214 | 215 | /** 216 | * Indicates that the drawer is currently opening. 217 | */ 218 | public static final int STATE_OPENING = 6; 219 | 220 | /** 221 | * Indicates that the drawer is currently open. 222 | */ 223 | public static final int STATE_OPEN = 8; 224 | 225 | /** 226 | * The current drawer state. 227 | * 228 | * @see #STATE_CLOSED 229 | * @see #STATE_CLOSING 230 | * @see #STATE_DRAGGING_OPEN 231 | * @see #STATE_DRAGGING_CLOSE 232 | * @see #STATE_OPENING 233 | * @see #STATE_OPEN 234 | */ 235 | protected int mDrawerState = STATE_CLOSED; 236 | 237 | /** 238 | * Bundle used to hold the drawers state. 239 | */ 240 | protected Bundle mState; 241 | 242 | /** 243 | * Key used when saving menu visibility state. 244 | */ 245 | private static final String STATE_MENU_VISIBLE = "ElasticDrawer.menuVisible"; 246 | /** 247 | * Indicates whether the drawer is currently being dragged. 248 | */ 249 | protected boolean mIsDragging; 250 | /** 251 | * The current pointer id. 252 | */ 253 | protected int mActivePointerId = INVALID_POINTER; 254 | 255 | public static final int INVALID_POINTER = -1; 256 | 257 | private float eventY; 258 | 259 | protected boolean isFirstPointUp; 260 | 261 | /** 262 | * Runnable used when animating the drawer open/closed. 263 | */ 264 | private final Runnable mDragRunnable = new Runnable() { 265 | @Override 266 | public void run() { 267 | postAnimationInvalidate(); 268 | } 269 | }; 270 | 271 | public ElasticDrawer(Context context) { 272 | super(context); 273 | } 274 | 275 | public ElasticDrawer(Context context, AttributeSet attrs) { 276 | this(context, attrs, R.attr.elasticDrawerStyle); 277 | } 278 | 279 | public ElasticDrawer(Context context, AttributeSet attrs, int defStyle) { 280 | super(context, attrs, defStyle); 281 | initDrawer(context, attrs, defStyle); 282 | } 283 | 284 | @SuppressLint("NewApi") 285 | protected void initDrawer(Context context, AttributeSet attrs, int defStyle) { 286 | setWillNotDraw(false); 287 | setFocusable(false); 288 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ElasticDrawer); 289 | 290 | mMenuSize = a.getDimensionPixelSize(R.styleable.ElasticDrawer_edMenuSize, dpToPx(240)); 291 | 292 | mMenuBackground = a.getColor(R.styleable.ElasticDrawer_edMenuBackground, 0xFFdddddd); 293 | 294 | mTouchBezelSize = a.getDimensionPixelSize(R.styleable.ElasticDrawer_edTouchBezelSize, 295 | dpToPx(DEFAULT_DRAG_BEZEL_DP)); 296 | 297 | mMaxAnimationDuration = a.getInt(R.styleable.ElasticDrawer_edMaxAnimationDuration, DEFAULT_ANIMATION_DURATION); 298 | 299 | final int position = a.getInt(R.styleable.ElasticDrawer_edPosition, 0); 300 | setPosition(position); 301 | a.recycle(); 302 | 303 | mMenuContainer = new NoClickThroughFrameLayout(context); 304 | mMenuContainer.setBackgroundColor(getResources().getColor(android.R.color.transparent)); 305 | 306 | mContentContainer = new NoClickThroughFrameLayout(context); 307 | 308 | final ViewConfiguration configuration = ViewConfiguration.get(context); 309 | mTouchSlop = configuration.getScaledTouchSlop(); 310 | mMaxVelocity = configuration.getScaledMaximumFlingVelocity(); 311 | 312 | mScroller = new Scroller(context, SMOOTH_INTERPOLATOR); 313 | mCloseEnough = dpToPx(CLOSE_ENOUGH); 314 | 315 | mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null); 316 | mContentContainer.setHardwareLayersEnabled(false); 317 | } 318 | 319 | protected int dpToPx(int dp) { 320 | return (int) (getResources().getDisplayMetrics().density * dp + 0.5f); 321 | } 322 | 323 | /** 324 | * Callback interface for changing state of the drawer. 325 | */ 326 | public interface OnDrawerStateChangeListener { 327 | 328 | /** 329 | * Called when the drawer state changes. 330 | * 331 | * @param oldState The old drawer state. 332 | * @param newState The new drawer state. 333 | */ 334 | void onDrawerStateChange(int oldState, int newState); 335 | 336 | /** 337 | * Called when the drawer slides. 338 | * 339 | * @param openRatio Ratio for how open the menu is. 340 | * @param offsetPixels Current offset of the menu in pixels. 341 | */ 342 | void onDrawerSlide(float openRatio, int offsetPixels); 343 | } 344 | 345 | /** 346 | * Callback that is invoked when the drawer is in the process of deciding whether it should intercept the touch 347 | * event. This lets the listener decide if the pointer is on a view that would disallow dragging of the drawer. 348 | * This is only called when the touch mode is {@link #TOUCH_MODE_FULLSCREEN}. 349 | */ 350 | public interface OnInterceptMoveEventListener { 351 | 352 | /** 353 | * Called for each child the pointer i on when the drawer is deciding whether to intercept the touch event. 354 | * 355 | * @param v View to test for draggability 356 | * @param delta Delta drag in pixels 357 | * @param x X coordinate of the active touch point 358 | * @param y Y coordinate of the active touch point 359 | * 360 | * @return true if view is draggable by delta dx. 361 | */ 362 | boolean isViewDraggable(View v, int delta, int x, int y); 363 | } 364 | 365 | class Position { 366 | // Positions the drawer to the left of the content. 367 | static final int LEFT = 1; 368 | // Positions the drawer to the right of the content. 369 | static final int RIGHT = 2; 370 | /** 371 | * Position the drawer at the start edge. This will position the drawer to the {@link #LEFT} with LTR 372 | * languages and 373 | * {@link #RIGHT} with RTL languages. 374 | */ 375 | static final int START = 3; 376 | /** 377 | * Position the drawer at the end edge. This will position the drawer to the {@link #RIGHT} with LTR 378 | * languages and 379 | * {@link #LEFT} with RTL languages. 380 | */ 381 | static final int END = 4; 382 | } 383 | 384 | protected void updateTouchAreaSize() { 385 | if (mTouchMode == TOUCH_MODE_BEZEL) { 386 | mTouchSize = mTouchBezelSize; 387 | } else if (mTouchMode == TOUCH_MODE_FULLSCREEN) { 388 | mTouchSize = getMeasuredWidth(); 389 | } else { 390 | mTouchSize = 0; 391 | } 392 | } 393 | 394 | @Override 395 | protected void onFinishInflate() { 396 | super.onFinishInflate(); 397 | 398 | if (getChildCount() != 2) { 399 | throw new IllegalStateException( 400 | "child count isn't equal to 2 , content and Menu view must be added in xml ."); 401 | } 402 | View content = getChildAt(0); 403 | if (content != null) { 404 | removeView(content); 405 | mContentContainer.removeAllViews(); 406 | mContentContainer 407 | .addView(content, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 408 | } else { 409 | throw new IllegalStateException( 410 | "content view must be added in xml ."); 411 | } 412 | View menu = getChildAt(0); 413 | if (menu != null) { 414 | removeView(menu); 415 | mMenuView = (FlowingMenuLayout) menu; 416 | mMenuView.setBackgroundColor(getResources().getColor(android.R.color.transparent)); 417 | mMenuView.setPaintColor(mMenuBackground); 418 | mMenuView.setMenuPosition(getPosition()); 419 | mMenuContainer.removeAllViews(); 420 | mMenuContainer.addView(menu, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 421 | } else { 422 | throw new IllegalStateException( 423 | "menu view must be added in xml ."); 424 | } 425 | addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 426 | addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 427 | } 428 | 429 | /** 430 | * Called when the number of pixels the content should be offset by has changed. 431 | * 432 | * @param offsetPixels The number of pixels to offset the content by. 433 | */ 434 | protected abstract void onOffsetPixelsChanged(int offsetPixels); 435 | 436 | /** 437 | * Toggles the menu open and close with animation. 438 | */ 439 | public void toggleMenu() { 440 | toggleMenu(true); 441 | } 442 | 443 | /** 444 | * Toggles the menu open and close. 445 | * 446 | * @param animate Whether open/close should be animated. 447 | */ 448 | public void toggleMenu(boolean animate) { 449 | if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) { 450 | closeMenu(animate); 451 | } else if (mDrawerState == STATE_CLOSED || mDrawerState == STATE_CLOSING) { 452 | openMenu(animate); 453 | } 454 | } 455 | 456 | /** 457 | * Animates the menu open. 458 | */ 459 | @SuppressWarnings("unused") 460 | public void openMenu() { 461 | openMenu(true); 462 | } 463 | 464 | /** 465 | * Opens the menu. 466 | * 467 | * @param animate Whether open/close should be animated. 468 | */ 469 | public abstract void openMenu(boolean animate); 470 | 471 | public abstract void openMenu(boolean animate, float y); 472 | 473 | /** 474 | * Animates the menu closed. 475 | */ 476 | @SuppressWarnings("unused") 477 | public void closeMenu() { 478 | closeMenu(true); 479 | } 480 | 481 | /** 482 | * Closes the menu. 483 | * 484 | * @param animate Whether open/close should be animated. 485 | */ 486 | public abstract void closeMenu(boolean animate); 487 | 488 | public abstract void closeMenu(boolean animate, float y); 489 | 490 | /** 491 | * Indicates whether the menu is currently visible. 492 | * 493 | * @return True if the menu is open, false otherwise. 494 | */ 495 | public boolean isMenuVisible() { 496 | return mMenuVisible; 497 | } 498 | 499 | /** 500 | * Set the size of the menu drawer when open. 501 | * 502 | * @param size The size of the menu. 503 | */ 504 | @SuppressWarnings("unused") 505 | public void setMenuSize(final int size) { 506 | mMenuSize = size; 507 | if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) { 508 | setOffsetPixels(mMenuSize, 0, FlowingMenuLayout.TYPE_NONE); 509 | } 510 | requestLayout(); 511 | invalidate(); 512 | } 513 | 514 | protected void smoothClose(final int eventY) { 515 | endDrag(); 516 | setDrawerState(STATE_CLOSING); 517 | 518 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(mOffsetPixels, 0); 519 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 520 | @Override 521 | public void onAnimationUpdate(ValueAnimator animation) { 522 | setOffsetPixels((Float) animation.getAnimatedValue(), eventY, 523 | FlowingMenuLayout.TYPE_DOWN_SMOOTH); 524 | } 525 | }); 526 | valueAnimator.addListener(new FlowingAnimationListener() { 527 | @Override 528 | public void onAnimationEnd(Animator animation) { 529 | mMenuVisible = false; 530 | setOffsetPixels(0, 0, FlowingMenuLayout.TYPE_NONE); 531 | setDrawerState(STATE_CLOSED); 532 | stopLayerTranslation(); 533 | } 534 | }); 535 | valueAnimator.setDuration(500); 536 | valueAnimator.setInterpolator(new DecelerateInterpolator(4f)); 537 | valueAnimator.start(); 538 | } 539 | 540 | /** 541 | * Moves the drawer to the position passed. 542 | * 543 | * @param position The position the content is moved to. 544 | * @param velocity Optional velocity if called by releasing a drag event. 545 | * @param animate Whether the move is animated. 546 | */ 547 | protected void animateOffsetTo(int position, int velocity, boolean animate, float eventY) { 548 | endDrag(); 549 | final int startX = (int) mOffsetPixels; 550 | final int dx = position - startX; 551 | if (dx == 0 || !animate) { 552 | setOffsetPixels(position, 0, FlowingMenuLayout.TYPE_NONE); 553 | setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN); 554 | stopLayerTranslation(); 555 | return; 556 | } 557 | int duration; 558 | velocity = Math.abs(velocity); 559 | if (velocity > 0) { 560 | duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity)); 561 | } else { 562 | duration = (int) (600.f * Math.abs((float) dx / mMenuSize)); 563 | } 564 | duration = Math.min(duration, mMaxAnimationDuration); 565 | animateOffsetTo(position, duration, eventY); 566 | } 567 | 568 | protected void animateOffsetTo(int position, int duration, float eventY) { 569 | final int startX = (int) mOffsetPixels; 570 | final int dx = position - startX; 571 | if (getPosition() == Position.LEFT) { 572 | if (dx > 0) { 573 | setDrawerState(STATE_OPENING); 574 | } else { 575 | setDrawerState(STATE_CLOSING); 576 | } 577 | } else { 578 | if (dx > 0) { 579 | setDrawerState(STATE_CLOSING); 580 | } else { 581 | setDrawerState(STATE_OPENING); 582 | } 583 | } 584 | mScroller.startScroll(startX, 0, dx, 0, duration); 585 | this.eventY = eventY; 586 | startLayerTranslation(); 587 | postAnimationInvalidate(); 588 | } 589 | 590 | /** 591 | * Sets the number of pixels the content should be offset. 592 | * 593 | * @param offsetPixels The number of pixels to offset the content by. 594 | */ 595 | protected void setOffsetPixels(float offsetPixels, float eventY, int type) { 596 | final int oldOffset = (int) mOffsetPixels; 597 | final int newOffset = (int) offsetPixels; 598 | 599 | mOffsetPixels = offsetPixels; 600 | mMenuView.setClipOffsetPixels(mOffsetPixels, eventY, type); 601 | if (newOffset != oldOffset) { 602 | onOffsetPixelsChanged(newOffset); 603 | mMenuVisible = newOffset != 0; 604 | 605 | // Notify any attached listeners of the current open ratio 606 | final float openRatio = ((float) Math.abs(newOffset)) / mMenuSize; 607 | dispatchOnDrawerSlide(openRatio, newOffset); 608 | } 609 | } 610 | 611 | @Override 612 | public void onRtlPropertiesChanged(int layoutDirection) { 613 | super.onRtlPropertiesChanged(layoutDirection); 614 | 615 | if (getPosition() != mResolvedPosition) { 616 | mResolvedPosition = getPosition(); 617 | setOffsetPixels(mOffsetPixels * -1, 0, FlowingMenuLayout.TYPE_NONE); 618 | } 619 | 620 | requestLayout(); 621 | invalidate(); 622 | } 623 | 624 | private void setPosition(int position) { 625 | mPosition = position; 626 | mResolvedPosition = getPosition(); 627 | } 628 | 629 | protected int getPosition() { 630 | final int layoutDirection = ViewHelper.getLayoutDirection(this); 631 | switch (mPosition) { 632 | case Position.START: 633 | if (layoutDirection == LAYOUT_DIRECTION_RTL) { 634 | return Position.RIGHT; 635 | } else { 636 | return Position.LEFT; 637 | } 638 | case Position.END: 639 | if (layoutDirection == LAYOUT_DIRECTION_RTL) { 640 | return Position.LEFT; 641 | } else { 642 | return Position.RIGHT; 643 | } 644 | } 645 | return mPosition; 646 | } 647 | 648 | /** 649 | * Register a callback to be invoked when the drawer state changes. 650 | * 651 | * @param listener The callback that will run. 652 | */ 653 | 654 | public void setOnDrawerStateChangeListener(OnDrawerStateChangeListener listener) { 655 | mOnDrawerStateChangeListener = listener; 656 | } 657 | 658 | /** 659 | * Register a callback that will be invoked when the drawer is about to intercept touch events. 660 | * 661 | * @param listener The callback that will be invoked. 662 | */ 663 | @SuppressWarnings("unused") 664 | public void setOnInterceptMoveEventListener(OnInterceptMoveEventListener listener) { 665 | mOnInterceptMoveEventListener = listener; 666 | } 667 | 668 | /** 669 | * Sets the maximum duration of open/close animations. 670 | * 671 | * @param duration The maximum duration in milliseconds. 672 | */ 673 | @SuppressWarnings("unused") 674 | public void setMaxAnimationDuration(int duration) { 675 | mMaxAnimationDuration = duration; 676 | } 677 | 678 | @SuppressWarnings("unused") 679 | public ViewGroup getMenuContainer() { 680 | return mMenuContainer; 681 | } 682 | 683 | /** 684 | * Returns the ViewGroup used as a parent for the content view. 685 | * 686 | * @return The content view's parent. 687 | */ 688 | @SuppressWarnings("unused") 689 | public ViewGroup getContentContainer() { 690 | return mContentContainer; 691 | } 692 | 693 | /** 694 | * Get the current state of the drawer. 695 | * 696 | * @return The state of the drawer. 697 | */ 698 | @SuppressWarnings("unused") 699 | public int getDrawerState() { 700 | return mDrawerState; 701 | } 702 | 703 | protected void setDrawerState(int state) { 704 | if (state != mDrawerState) { 705 | final int oldState = mDrawerState; 706 | mDrawerState = state; 707 | if (mOnDrawerStateChangeListener != null) { 708 | mOnDrawerStateChangeListener.onDrawerStateChange(oldState, state); 709 | } 710 | if (DEBUG) { 711 | logDrawerState(state); 712 | } 713 | } 714 | } 715 | 716 | protected void logDrawerState(int state) { 717 | switch (state) { 718 | case STATE_CLOSED: 719 | Log.d(TAG, "[DrawerState] STATE_CLOSED"); 720 | break; 721 | 722 | case STATE_CLOSING: 723 | Log.d(TAG, "[DrawerState] STATE_CLOSING"); 724 | break; 725 | 726 | case STATE_DRAGGING_CLOSE: 727 | Log.d(TAG, "[DrawerState] STATE_DRAGGING_CLOSE"); 728 | break; 729 | case STATE_DRAGGING_OPEN: 730 | Log.d(TAG, "[DrawerState] STATE_DRAGGING_OPEN"); 731 | break; 732 | 733 | case STATE_OPENING: 734 | Log.d(TAG, "[DrawerState] STATE_OPENING"); 735 | break; 736 | 737 | case STATE_OPEN: 738 | Log.d(TAG, "[DrawerState] STATE_OPEN"); 739 | break; 740 | default: 741 | Log.d(TAG, "[DrawerState] Unknown: " + state); 742 | } 743 | } 744 | 745 | /** 746 | * Sets the drawer touch mode. Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or 747 | * {@link #TOUCH_MODE_FULLSCREEN}. 748 | * 749 | * @param mode The touch mode. 750 | */ 751 | public void setTouchMode(int mode) { 752 | if (mTouchMode != mode) { 753 | mTouchMode = mode; 754 | updateTouchAreaSize(); 755 | } 756 | } 757 | 758 | @Override 759 | public void postOnAnimation(Runnable action) { 760 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 761 | super.postOnAnimation(action); 762 | } else { 763 | postDelayed(action, ANIMATION_DELAY); 764 | } 765 | } 766 | 767 | protected void dispatchOnDrawerSlide(float openRatio, int offsetPixels) { 768 | if (mOnDrawerStateChangeListener != null) { 769 | mOnDrawerStateChangeListener.onDrawerSlide(openRatio, offsetPixels); 770 | } 771 | } 772 | 773 | @Override 774 | protected void dispatchDraw(Canvas canvas) { 775 | super.dispatchDraw(canvas); 776 | } 777 | 778 | void saveState(Bundle state) { 779 | final boolean menuVisible = mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING; 780 | state.putBoolean(STATE_MENU_VISIBLE, menuVisible); 781 | } 782 | 783 | /** 784 | * Restores the state of the drawer. 785 | * 786 | * @param in A parcelable containing the drawer state. 787 | */ 788 | public void restoreState(Parcelable in) { 789 | mState = (Bundle) in; 790 | final boolean menuOpen = mState.getBoolean(STATE_MENU_VISIBLE); 791 | if (menuOpen) { 792 | openMenu(false); 793 | } else { 794 | setOffsetPixels(0, 0, FlowingMenuLayout.TYPE_NONE); 795 | } 796 | mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED; 797 | } 798 | 799 | @Override 800 | protected Parcelable onSaveInstanceState() { 801 | Parcelable superState = super.onSaveInstanceState(); 802 | SavedState state = new SavedState(superState); 803 | 804 | if (mState == null) { 805 | mState = new Bundle(); 806 | } 807 | saveState(mState); 808 | 809 | state.mState = mState; 810 | return state; 811 | } 812 | 813 | @Override 814 | protected void onRestoreInstanceState(Parcelable state) { 815 | SavedState savedState = (SavedState) state; 816 | super.onRestoreInstanceState(savedState.getSuperState()); 817 | 818 | restoreState(savedState.mState); 819 | } 820 | 821 | static class SavedState extends BaseSavedState { 822 | 823 | Bundle mState; 824 | 825 | SavedState(Parcelable superState) { 826 | super(superState); 827 | } 828 | 829 | @SuppressLint("ParcelClassLoader") 830 | SavedState(Parcel in) { 831 | super(in); 832 | mState = in.readBundle(); 833 | } 834 | 835 | @Override 836 | public void writeToParcel(Parcel dest, int flags) { 837 | super.writeToParcel(dest, flags); 838 | dest.writeBundle(mState); 839 | } 840 | 841 | @SuppressWarnings("UnusedDeclaration") 842 | public static final Creator CREATOR = new Creator() { 843 | @Override 844 | public SavedState createFromParcel(Parcel in) { 845 | return new SavedState(in); 846 | } 847 | 848 | @Override 849 | public SavedState[] newArray(int size) { 850 | return new SavedState[size]; 851 | } 852 | }; 853 | } 854 | 855 | protected float getXVelocity(VelocityTracker velocityTracker) { 856 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { 857 | return velocityTracker.getXVelocity(mActivePointerId); 858 | } 859 | 860 | return velocityTracker.getXVelocity(); 861 | } 862 | 863 | protected boolean canChildrenScroll(int dx, int x, int y) { 864 | boolean canScroll = false; 865 | 866 | switch (getPosition()) { 867 | case Position.LEFT: 868 | case Position.RIGHT: 869 | if (!mMenuVisible) { 870 | canScroll = canChildScrollHorizontally(mContentContainer, false, dx, 871 | x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer)); 872 | } else { 873 | canScroll = canChildScrollHorizontally(mMenuContainer, false, dx, 874 | x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer)); 875 | } 876 | break; 877 | } 878 | 879 | return canScroll; 880 | } 881 | 882 | /** 883 | * Tests scrollability within child views of v given a delta of dx. 884 | * 885 | * @param v View to test for horizontal scrollability 886 | * @param checkV Whether the view should be checked for draggability 887 | * @param dx Delta scrolled in pixels 888 | * @param x X coordinate of the active touch point 889 | * @param y Y coordinate of the active touch point 890 | * 891 | * @return true if child views of v can be scrolled by delta of dx. 892 | */ 893 | protected boolean canChildScrollHorizontally(View v, boolean checkV, int dx, int x, int y) { 894 | if (v instanceof ViewGroup) { 895 | final ViewGroup group = (ViewGroup) v; 896 | 897 | final int count = group.getChildCount(); 898 | // Count backwards - let topmost views consume scroll distance first. 899 | for (int i = count - 1; i >= 0; i--) { 900 | final View child = group.getChildAt(i); 901 | 902 | final int childLeft = child.getLeft() + supportGetTranslationX(child); 903 | final int childRight = child.getRight() + supportGetTranslationX(child); 904 | final int childTop = child.getTop() + supportGetTranslationY(child); 905 | final int childBottom = child.getBottom() + supportGetTranslationY(child); 906 | 907 | if (x >= childLeft && x < childRight && y >= childTop && y < childBottom 908 | && canChildScrollHorizontally(child, true, dx, x - childLeft, y - childTop)) { 909 | return true; 910 | } 911 | } 912 | } 913 | 914 | return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y); 915 | } 916 | 917 | private int supportGetTranslationY(View v) { 918 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 919 | return (int) v.getTranslationY(); 920 | } 921 | 922 | return 0; 923 | } 924 | 925 | private int supportGetTranslationX(View v) { 926 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 927 | return (int) v.getTranslationX(); 928 | } 929 | 930 | return 0; 931 | } 932 | 933 | protected boolean isCloseEnough() { 934 | return Math.abs(mOffsetPixels) <= mCloseEnough; 935 | } 936 | 937 | /** 938 | * Callback when each frame in the drawer animation should be drawn. 939 | */ 940 | private void postAnimationInvalidate() { 941 | if (mScroller.computeScrollOffset()) { 942 | final int oldX = (int) mOffsetPixels; 943 | final int x = mScroller.getCurrX(); 944 | 945 | if (x != oldX) { 946 | if (mDrawerState == STATE_OPENING) { 947 | setOffsetPixels(x, eventY, FlowingMenuLayout.TYPE_UP_AUTO); 948 | } else if (mDrawerState == STATE_CLOSING) { 949 | setOffsetPixels(x, eventY, FlowingMenuLayout.TYPE_DOWN_AUTO); 950 | } 951 | } 952 | if (x != mScroller.getFinalX()) { 953 | postOnAnimation(mDragRunnable); 954 | return; 955 | } 956 | } 957 | if (mDrawerState == STATE_OPENING) { 958 | completeAnimation(); 959 | } else if (mDrawerState == STATE_CLOSING) { 960 | mScroller.abortAnimation(); 961 | final int finalX = mScroller.getFinalX(); 962 | mMenuVisible = finalX != 0; 963 | setOffsetPixels(finalX, 0, FlowingMenuLayout.TYPE_NONE); 964 | setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN); 965 | stopLayerTranslation(); 966 | } 967 | 968 | } 969 | 970 | /** 971 | * Called when a drawer animation has successfully completed. 972 | */ 973 | private void completeAnimation() { 974 | mScroller.abortAnimation(); 975 | final int finalX = mScroller.getFinalX(); 976 | flowDown(finalX); 977 | } 978 | 979 | private void flowDown(final int finalX) { 980 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); 981 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 982 | @Override 983 | public void onAnimationUpdate(ValueAnimator animation) { 984 | mMenuView.setUpDownFraction(animation.getAnimatedFraction()); 985 | } 986 | }); 987 | valueAnimator.addListener(new FlowingAnimationListener() { 988 | @Override 989 | public void onAnimationEnd(Animator animation) { 990 | if (mDrawerState == STATE_OPENING) { 991 | mMenuVisible = finalX != 0; 992 | setOffsetPixels(finalX, 0, FlowingMenuLayout.TYPE_NONE); 993 | setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN); 994 | stopLayerTranslation(); 995 | } 996 | } 997 | }); 998 | valueAnimator.setDuration(300); 999 | valueAnimator.setInterpolator(new OvershootInterpolator(4f)); 1000 | valueAnimator.start(); 1001 | } 1002 | 1003 | /** 1004 | * Called when a drag has been ended. 1005 | */ 1006 | protected void endDrag() { 1007 | mIsDragging = false; 1008 | if (mVelocityTracker != null) { 1009 | mVelocityTracker.recycle(); 1010 | mVelocityTracker = null; 1011 | } 1012 | } 1013 | 1014 | /** 1015 | * Stops ongoing animation of the drawer. 1016 | */ 1017 | protected void stopAnimation() { 1018 | removeCallbacks(mDragRunnable); 1019 | mScroller.abortAnimation(); 1020 | stopLayerTranslation(); 1021 | } 1022 | 1023 | /** 1024 | * If possible, set the layer type to {@link android.view.View#LAYER_TYPE_HARDWARE}. 1025 | */ 1026 | @SuppressLint("NewApi") 1027 | protected void startLayerTranslation() { 1028 | if (mHardwareLayersEnabled && !mLayerTypeHardware) { 1029 | mLayerTypeHardware = true; 1030 | mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1031 | mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1032 | } 1033 | } 1034 | 1035 | /** 1036 | * If the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}, this will set it to 1037 | * {@link View#LAYER_TYPE_NONE}. 1038 | */ 1039 | @SuppressLint("NewApi") 1040 | protected void stopLayerTranslation() { 1041 | if (mLayerTypeHardware) { 1042 | mLayerTypeHardware = false; 1043 | mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null); 1044 | mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null); 1045 | } 1046 | } 1047 | 1048 | @SuppressWarnings("unused") 1049 | public void setTouchBezelSize(int size) { 1050 | mTouchBezelSize = size; 1051 | } 1052 | 1053 | @SuppressWarnings("unused") 1054 | public int getTouchBezelSize() { 1055 | return mTouchBezelSize; 1056 | } 1057 | 1058 | @SuppressWarnings("unused") 1059 | public void setHardwareLayerEnabled(boolean enabled) { 1060 | if (enabled != mHardwareLayersEnabled) { 1061 | mHardwareLayersEnabled = enabled; 1062 | mMenuContainer.setHardwareLayersEnabled(enabled); 1063 | mContentContainer.setHardwareLayersEnabled(enabled); 1064 | stopLayerTranslation(); 1065 | } 1066 | } 1067 | 1068 | } 1069 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/FlowingAnimationListener.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer_core; 2 | 3 | 4 | import com.nineoldandroids.animation.Animator; 5 | 6 | class FlowingAnimationListener implements Animator.AnimatorListener { 7 | 8 | public void onAnimationStart(Animator animation) { 9 | 10 | } 11 | 12 | @Override 13 | public void onAnimationEnd(Animator animation) { 14 | 15 | } 16 | 17 | @Override 18 | public void onAnimationCancel(Animator animation) { 19 | 20 | } 21 | 22 | @Override 23 | public void onAnimationRepeat(Animator animation) { 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/FlowingDrawer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mxn.soul.flowingdrawer_core; 3 | 4 | import android.annotation.SuppressLint; 5 | import android.content.Context; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.VelocityTracker; 9 | import android.view.View; 10 | 11 | /** 12 | * Created by mxn on 2016/10/17. 13 | * FlowingDrawer 14 | */ 15 | public class FlowingDrawer extends ElasticDrawer { 16 | 17 | public FlowingDrawer(Context context) { 18 | super(context); 19 | } 20 | 21 | public FlowingDrawer(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public FlowingDrawer(Context context, AttributeSet attrs, int defStyle) { 26 | super(context, attrs, defStyle); 27 | } 28 | 29 | @SuppressLint("NewApi") 30 | @Override 31 | protected void initDrawer(Context context, AttributeSet attrs, int defStyle) { 32 | super.initDrawer(context, attrs, defStyle); 33 | } 34 | 35 | @Override 36 | public void openMenu(boolean animate) { 37 | openMenu(animate, getHeight() / 2); 38 | } 39 | 40 | @Override 41 | public void openMenu(boolean animate, float y) { 42 | int animateTo = 0; 43 | switch (getPosition()) { 44 | case Position.LEFT: 45 | animateTo = mMenuSize; 46 | break; 47 | case Position.RIGHT: 48 | animateTo = -mMenuSize; 49 | break; 50 | } 51 | animateOffsetTo(animateTo, 0, animate, y); 52 | } 53 | 54 | @Override 55 | public void closeMenu(boolean animate) { 56 | closeMenu(animate, getHeight() / 2); 57 | } 58 | 59 | @Override 60 | public void closeMenu(boolean animate, float y) { 61 | animateOffsetTo(0, 0, animate, y); 62 | } 63 | 64 | @SuppressLint("NewApi") 65 | @Override 66 | protected void onOffsetPixelsChanged(int offsetPixels) { 67 | switch (getPosition()) { 68 | case Position.LEFT: 69 | mMenuContainer.setTranslationX(offsetPixels - mMenuSize); 70 | break; 71 | case Position.RIGHT: 72 | mMenuContainer.setTranslationX(offsetPixels + mMenuSize); 73 | break; 74 | } 75 | invalidate(); 76 | } 77 | 78 | @Override 79 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 80 | super.onSizeChanged(w, h, oldw, oldh); 81 | onOffsetPixelsChanged((int) mOffsetPixels); 82 | } 83 | 84 | @SuppressLint("NewApi") 85 | @Override 86 | protected void startLayerTranslation() { 87 | if (mHardwareLayersEnabled && !mLayerTypeHardware) { 88 | mLayerTypeHardware = true; 89 | mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); 90 | } 91 | } 92 | 93 | @SuppressLint("NewApi") 94 | @Override 95 | protected void stopLayerTranslation() { 96 | if (mLayerTypeHardware) { 97 | mLayerTypeHardware = false; 98 | mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null); 99 | } 100 | } 101 | 102 | @Override 103 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 104 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 105 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 106 | 107 | if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) { 108 | throw new IllegalStateException("Must measure with an exact size"); 109 | } 110 | 111 | final int width = MeasureSpec.getSize(widthMeasureSpec); 112 | final int height = MeasureSpec.getSize(heightMeasureSpec); 113 | 114 | if (mOffsetPixels == -1) { 115 | openMenu(false); 116 | } 117 | 118 | int menuWidthMeasureSpec; 119 | int menuHeightMeasureSpec; 120 | menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize); 121 | menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height); 122 | mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec); 123 | 124 | final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width); 125 | final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height); 126 | mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec); 127 | 128 | setMeasuredDimension(width, height); 129 | 130 | updateTouchAreaSize(); 131 | } 132 | 133 | @Override 134 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 135 | final int width = r - l; 136 | final int height = b - t; 137 | 138 | mContentContainer.layout(0, 0, width, height); 139 | 140 | switch (getPosition()) { 141 | case Position.LEFT: 142 | mMenuContainer.layout(0, 0, mMenuSize, height); 143 | break; 144 | 145 | case Position.RIGHT: 146 | mMenuContainer.layout(width - mMenuSize, 0, width, height); 147 | break; 148 | } 149 | } 150 | 151 | private boolean isContentTouch(int x) { 152 | boolean contentTouch = false; 153 | 154 | switch (getPosition()) { 155 | case Position.LEFT: 156 | contentTouch = ViewHelper.getRight(mMenuContainer) < x; 157 | break; 158 | case Position.RIGHT: 159 | contentTouch = ViewHelper.getLeft(mMenuContainer) > x; 160 | break; 161 | } 162 | return contentTouch; 163 | } 164 | 165 | private boolean willCloseEnough() { 166 | boolean closeEnough = false; 167 | 168 | switch (getPosition()) { 169 | case Position.LEFT: 170 | closeEnough = mOffsetPixels <= mMenuSize/ 2; 171 | break; 172 | case Position.RIGHT: 173 | closeEnough = -mOffsetPixels <= mMenuSize / 2; 174 | break; 175 | } 176 | return closeEnough; 177 | } 178 | 179 | protected boolean onDownAllowDrag() { 180 | switch (getPosition()) { 181 | case Position.LEFT: 182 | return (!mMenuVisible && mInitialMotionX <= mTouchSize) 183 | || (mMenuVisible && mInitialMotionX <= mOffsetPixels); 184 | 185 | case Position.RIGHT: 186 | final int width = getWidth(); 187 | final int initialMotionX = (int) mInitialMotionX; 188 | 189 | return (!mMenuVisible && initialMotionX >= width - mTouchSize) 190 | || (mMenuVisible && initialMotionX >= width + mOffsetPixels); 191 | } 192 | 193 | return false; 194 | } 195 | 196 | protected boolean onMoveAllowDrag(int x, float dx) { 197 | if (mMenuVisible && mTouchMode == TOUCH_MODE_FULLSCREEN) { 198 | return true; 199 | } 200 | 201 | switch (getPosition()) { 202 | case Position.LEFT: 203 | return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0)) // Drawer closed 204 | || (mMenuVisible && x <= mOffsetPixels);// Drawer open 205 | case Position.RIGHT: 206 | final int width = getWidth(); 207 | return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0)) 208 | || (mMenuVisible && x >= width + mOffsetPixels); 209 | 210 | } 211 | 212 | return false; 213 | } 214 | 215 | protected void onMoveEvent(float dx, float y, int type) { 216 | switch (getPosition()) { 217 | case Position.LEFT: 218 | setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize), y, type); 219 | break; 220 | 221 | case Position.RIGHT: 222 | setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize), y, type); 223 | break; 224 | } 225 | } 226 | 227 | protected void onUpEvent(int x, int y) { 228 | switch (getPosition()) { 229 | case Position.LEFT: { 230 | if (mIsDragging) { 231 | if (mDrawerState == STATE_DRAGGING_CLOSE) { 232 | closeMenu(true, y); 233 | return; 234 | } 235 | if (mDrawerState == STATE_DRAGGING_OPEN && willCloseEnough()) { 236 | smoothClose(y); 237 | return; 238 | } 239 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 240 | final int initialVelocity = (int) getXVelocity(mVelocityTracker); 241 | mLastMotionX = x; 242 | animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true, y); 243 | } else if (isFirstPointUp) { 244 | isFirstPointUp = false; 245 | return; 246 | } 247 | // Close the menu when content is clicked while the menu is visible. 248 | else if (mMenuVisible) { 249 | closeMenu(true, y); 250 | } 251 | break; 252 | } 253 | case Position.RIGHT: { 254 | if (mIsDragging) { 255 | if (mDrawerState == STATE_DRAGGING_CLOSE) { 256 | closeMenu(true, y); 257 | return; 258 | } 259 | if (mDrawerState == STATE_DRAGGING_OPEN && willCloseEnough()) { 260 | smoothClose(y); 261 | return; 262 | } 263 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 264 | final int initialVelocity = (int) getXVelocity(mVelocityTracker); 265 | mLastMotionX = x; 266 | animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true, y); 267 | } else if (isFirstPointUp) { 268 | isFirstPointUp = false; 269 | return; 270 | } 271 | // Close the menu when content is clicked while the menu is visible. 272 | else if (mMenuVisible) { 273 | closeMenu(true, y); 274 | } 275 | break; 276 | } 277 | } 278 | } 279 | 280 | protected boolean checkTouchSlop(float dx, float dy) { 281 | return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy); 282 | } 283 | 284 | @Override 285 | protected void stopAnimation() { 286 | super.stopAnimation(); 287 | } 288 | 289 | @Override 290 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 291 | super.requestDisallowInterceptTouchEvent(disallowIntercept); 292 | } 293 | 294 | private void onPointerUp(MotionEvent ev) { 295 | final int pointerIndex = ev.getActionIndex(); 296 | final int pointerId = ev.getPointerId(pointerIndex); 297 | if (pointerId == mActivePointerId) { 298 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 299 | mLastMotionX = ev.getX(newPointerIndex); 300 | mActivePointerId = ev.getPointerId(newPointerIndex); 301 | if (mVelocityTracker != null) { 302 | mVelocityTracker.clear(); 303 | } 304 | } 305 | } 306 | 307 | public boolean onInterceptTouchEvent(MotionEvent ev) { 308 | final int action = ev.getAction() & MotionEvent.ACTION_MASK; 309 | if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 310 | mActivePointerId = INVALID_POINTER; 311 | mIsDragging = false; 312 | if (mVelocityTracker != null) { 313 | mVelocityTracker.recycle(); 314 | mVelocityTracker = null; 315 | } 316 | if (Math.abs(mOffsetPixels) > mMenuSize / 2) { 317 | openMenu(true, ev.getY()); 318 | } else { 319 | closeMenu(true, ev.getY()); 320 | } 321 | return false; 322 | } 323 | if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) { 324 | setOffsetPixels(0, 0, FlowingMenuLayout.TYPE_NONE); 325 | stopAnimation(); 326 | setDrawerState(STATE_CLOSED); 327 | mIsDragging = false; 328 | } 329 | // Always intercept events over the content while menu is visible. 330 | if (mMenuVisible) { 331 | int index = 0; 332 | if (mActivePointerId != INVALID_POINTER) { 333 | index = ev.findPointerIndex(mActivePointerId); 334 | index = index == -1 ? 0 : index; 335 | } 336 | final int x = (int) ev.getX(index); 337 | if (isContentTouch(x)) { 338 | return true; 339 | } 340 | } 341 | 342 | if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) { 343 | return false; 344 | } 345 | 346 | if (action != MotionEvent.ACTION_DOWN && mIsDragging) { 347 | return true; 348 | } 349 | 350 | switch (action) { 351 | case MotionEvent.ACTION_DOWN: { 352 | mLastMotionX = mInitialMotionX = ev.getX(); 353 | mLastMotionY = mInitialMotionY = ev.getY(); 354 | final boolean allowDrag = onDownAllowDrag(); 355 | mActivePointerId = ev.getPointerId(0); 356 | 357 | if (allowDrag) { 358 | setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED); 359 | stopAnimation(); 360 | mIsDragging = false; 361 | } 362 | break; 363 | } 364 | 365 | case MotionEvent.ACTION_MOVE: { 366 | final int activePointerId = mActivePointerId; 367 | if (activePointerId == INVALID_POINTER) { 368 | // If we don't have a valid id, the touch down wasn't on content. 369 | break; 370 | } 371 | 372 | final int pointerIndex = ev.findPointerIndex(activePointerId); 373 | if (pointerIndex == -1) { 374 | mIsDragging = false; 375 | mActivePointerId = INVALID_POINTER; 376 | endDrag(); 377 | closeMenu(true, ev.getY()); 378 | return false; 379 | } 380 | 381 | final float x = ev.getX(pointerIndex); 382 | final float dx = x - mLastMotionX; 383 | final float y = ev.getY(pointerIndex); 384 | final float dy = y - mLastMotionY; 385 | 386 | if (checkTouchSlop(dx, dy)) { 387 | if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible) 388 | && canChildrenScroll((int) dx, (int) x, (int) y)) { 389 | endDrag(); 390 | // Release the velocity tracker 391 | requestDisallowInterceptTouchEvent(true); 392 | return false; 393 | } 394 | final boolean allowDrag = onMoveAllowDrag((int) x, dx); 395 | if (allowDrag) { 396 | stopAnimation(); 397 | if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) { 398 | setDrawerState(STATE_DRAGGING_CLOSE); 399 | } else { 400 | setDrawerState(STATE_DRAGGING_OPEN); 401 | } 402 | mIsDragging = true; 403 | mLastMotionX = x; 404 | mLastMotionY = y; 405 | } 406 | } 407 | break; 408 | } 409 | case MotionEvent.ACTION_POINTER_UP: 410 | onPointerUp(ev); 411 | mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId)); 412 | mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId)); 413 | break; 414 | } 415 | if (mVelocityTracker == null) { 416 | mVelocityTracker = VelocityTracker.obtain(); 417 | } 418 | mVelocityTracker.addMovement(ev); 419 | return mIsDragging; 420 | } 421 | 422 | @Override 423 | public boolean onTouchEvent(MotionEvent ev) { 424 | if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) { 425 | return false; 426 | } 427 | final int action = ev.getAction() & MotionEvent.ACTION_MASK; 428 | if (mVelocityTracker == null) { 429 | mVelocityTracker = VelocityTracker.obtain(); 430 | } 431 | mVelocityTracker.addMovement(ev); 432 | switch (action) { 433 | case MotionEvent.ACTION_DOWN: { 434 | mLastMotionX = mInitialMotionX = ev.getX(); 435 | mLastMotionY = mInitialMotionY = ev.getY(); 436 | final boolean allowDrag = onDownAllowDrag(); 437 | mActivePointerId = ev.getPointerId(0); 438 | if (allowDrag) { 439 | stopAnimation(); 440 | startLayerTranslation(); 441 | } 442 | break; 443 | } 444 | case MotionEvent.ACTION_MOVE: { 445 | final int pointerIndex = ev.findPointerIndex(mActivePointerId); 446 | if (pointerIndex == -1) { 447 | mIsDragging = false; 448 | mActivePointerId = INVALID_POINTER; 449 | endDrag(); 450 | closeMenu(true, ev.getY()); 451 | return false; 452 | } 453 | if (mIsDragging) { 454 | startLayerTranslation(); 455 | final float x = ev.getX(pointerIndex); 456 | final float dx = x - mLastMotionX; 457 | final float y = ev.getY(pointerIndex); 458 | mLastMotionX = x; 459 | mLastMotionY = y; 460 | if (mDrawerState == STATE_DRAGGING_OPEN) { 461 | if (getPosition() == ElasticDrawer.Position.LEFT) { 462 | if (mOffsetPixels + dx < mMenuSize / 2) { 463 | onMoveEvent(dx, y, FlowingMenuLayout.TYPE_UP_MANUAL); 464 | } else { 465 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 466 | final int initialVelocity = (int) getXVelocity(mVelocityTracker); 467 | mLastMotionX = x; 468 | animateOffsetTo(mMenuSize, initialVelocity, true, y); 469 | isFirstPointUp = true; 470 | endDrag(); 471 | } 472 | } else { 473 | if (mOffsetPixels + dx > - mMenuSize / 2) { 474 | onMoveEvent(dx, y, FlowingMenuLayout.TYPE_UP_MANUAL); 475 | } else { 476 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 477 | final int initialVelocity = (int) getXVelocity(mVelocityTracker); 478 | mLastMotionX = x; 479 | animateOffsetTo(-mMenuSize, initialVelocity, true, y); 480 | isFirstPointUp = true; 481 | endDrag(); 482 | } 483 | } 484 | } else if (mDrawerState == STATE_DRAGGING_CLOSE) { 485 | onMoveEvent(dx, y, FlowingMenuLayout.TYPE_DOWN_MANUAL); 486 | } 487 | } 488 | break; 489 | } 490 | case MotionEvent.ACTION_CANCEL: 491 | case MotionEvent.ACTION_UP: { 492 | int index = ev.findPointerIndex(mActivePointerId); 493 | index = index == -1 ? 0 : index; 494 | final int x = (int) ev.getX(index); 495 | final int y = (int) ev.getY(index); 496 | onUpEvent(x, y); 497 | mActivePointerId = INVALID_POINTER; 498 | mIsDragging = false; 499 | break; 500 | } 501 | case MotionEvent.ACTION_POINTER_DOWN: 502 | final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 503 | >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; 504 | mLastMotionX = ev.getX(index); 505 | mLastMotionY = ev.getY(index); 506 | mActivePointerId = ev.getPointerId(index); 507 | break; 508 | case MotionEvent.ACTION_POINTER_UP: 509 | onPointerUp(ev); 510 | mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId)); 511 | mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId)); 512 | break; 513 | } 514 | return true; 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/FlowingMenuLayout.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer_core; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.Region; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | import android.widget.FrameLayout; 12 | 13 | /** 14 | * Created by mxn on 2016/12/13. 15 | * menu layout 16 | */ 17 | 18 | @SuppressWarnings("FieldCanBeLocal") 19 | public class FlowingMenuLayout extends FrameLayout { 20 | 21 | private Path mClipPath; 22 | private float mClipOffsetPixels = 0; 23 | 24 | public final static int TYPE_NONE = 0; 25 | public final static int TYPE_UP_MANUAL = 1; 26 | public final static int TYPE_UP_AUTO = 2; 27 | public final static int TYPE_UP_DOWN = 3; 28 | public final static int TYPE_DOWN_AUTO = 4; 29 | public final static int TYPE_DOWN_MANUAL = 5; 30 | public final static int TYPE_DOWN_SMOOTH = 6; 31 | 32 | private int currentType = TYPE_NONE; 33 | private float eventY = 0; 34 | private int topControlY; 35 | private int bottomControlY; 36 | private int topY; 37 | private int bottomY; 38 | private int width; 39 | private int height; 40 | private double verticalOffsetRatio; 41 | private double ratio1; 42 | private double ratio2; 43 | private float fraction; 44 | private float fractionUpDown; 45 | private float fractionEdge; 46 | private float fractionCenter; 47 | private float fractionCenterDown; 48 | 49 | private int centerXOffset; 50 | private int edgeXOffset; 51 | 52 | private Paint mPaint; 53 | private int position; 54 | 55 | public FlowingMenuLayout(Context context) { 56 | this(context, null); 57 | } 58 | 59 | public FlowingMenuLayout(Context context, AttributeSet attrs) { 60 | this(context, attrs, 0); 61 | } 62 | 63 | public FlowingMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | mClipPath = new Path(); 66 | mPaint = new Paint(); 67 | mPaint.setAntiAlias(true); 68 | mPaint.setStyle(Paint.Style.FILL); 69 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 70 | && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 71 | setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint); 72 | } 73 | } 74 | 75 | public void setPaintColor(int color) { 76 | mPaint.setColor(color); 77 | } 78 | 79 | public void setMenuPosition(int position) { 80 | this.position = position; 81 | } 82 | 83 | public void setClipOffsetPixels(float clipOffsetPixels, float eventY, int type) { 84 | mClipOffsetPixels = clipOffsetPixels; 85 | currentType = type; 86 | this.eventY = eventY; 87 | invalidate(); 88 | } 89 | 90 | public void setUpDownFraction(float fraction) { 91 | fractionUpDown = fraction; 92 | currentType = TYPE_UP_DOWN; 93 | invalidate(); 94 | } 95 | 96 | @Override 97 | protected void dispatchDraw(Canvas canvas) { 98 | 99 | width = getWidth(); 100 | height = getHeight(); 101 | mClipPath.reset(); 102 | if (position == ElasticDrawer.Position.LEFT) { 103 | drawLeftMenu(); 104 | } else { 105 | drawRightMenu(); 106 | } 107 | canvas.save(); 108 | canvas.drawPath(mClipPath, mPaint); 109 | canvas.clipPath(mClipPath, Region.Op.INTERSECT); 110 | super.dispatchDraw(canvas); 111 | canvas.restore(); 112 | } 113 | 114 | private void drawLeftMenu() { 115 | switch (currentType) { 116 | case TYPE_NONE: 117 | /** 118 | * 空状态 119 | * mClipOffsetPixels =0 or mClipOffsetPixels = width 120 | */ 121 | mClipPath.moveTo(0, 0); 122 | mClipPath.lineTo(width, 0); 123 | mClipPath.lineTo(width, height); 124 | mClipPath.lineTo(0, height); 125 | mClipPath.lineTo(0, 0); 126 | break; 127 | case TYPE_UP_MANUAL: 128 | /** 129 | * 手动打开状态 130 | verticalOffsetRatio = 0 when currentPointY = 0.5 * height ; 131 | verticalOffsetRatio = 1 when currentPointY = height or currentPointY = 0; 132 | bottomY,topY由两部分组成 133 | 第一部分是初始位置,由ratio1和currentPointY决定 134 | 第二部分由currentPointX移动位置决定 135 | 两部分系数分别是ratio1,ratio2 136 | ratio1,ratio2表示 (currentPointY - topY)/ (bottomY - currentPointY) 137 | 第一部分bottomY - topY的初始值为height的0.7 倍 138 | 第二部分bottomY - topY变化的总长为currentPointX变化总长的6倍 139 | */ 140 | verticalOffsetRatio = Math.abs((double) (2 * eventY - height) / height); 141 | ratio1 = verticalOffsetRatio * 3 + 1; 142 | ratio2 = verticalOffsetRatio * 5 + 1; 143 | if (eventY - height / 2 >= 0) { 144 | bottomY = (int) (eventY + 0.7 * height / (ratio1 + 1) + mClipOffsetPixels * 6 / (ratio2 + 1)); 145 | topY = (int) (eventY - 0.7 * height / (1 + 1 / ratio1) - mClipOffsetPixels * 6 / (1 / ratio2 + 1)); 146 | topControlY = (int) (-bottomY / 4 + 5 * eventY / 4); 147 | bottomControlY = (int) (bottomY / 4 + 3 * eventY / 4); 148 | } else { 149 | bottomY = 150 | (int) (eventY + 0.7 * height / (1 / ratio1 + 1) + mClipOffsetPixels * 6 / (1 / ratio2 + 1)); 151 | topY = (int) (eventY - 0.7 * height / (1 + ratio1) - mClipOffsetPixels * 6 / (ratio2 + 1)); 152 | topControlY = (int) (topY / 4 + 3 * eventY / 4); 153 | bottomControlY = (int) (-topY / 4 + 5 * eventY / 4); 154 | } 155 | mClipPath.moveTo(width - mClipOffsetPixels, topY); 156 | mClipPath.cubicTo(width - mClipOffsetPixels, topControlY, width, 157 | topControlY, width, eventY); 158 | mClipPath.cubicTo(width, bottomControlY, width - mClipOffsetPixels, 159 | bottomControlY, width - mClipOffsetPixels, bottomY); 160 | mClipPath.lineTo(width - mClipOffsetPixels, topY); 161 | break; 162 | case TYPE_UP_AUTO: 163 | /** 164 | * 自动打开状态 165 | fraction变化范围是0-1 166 | 0-0.5时fractionCenter变化慢(根号函数),fractionEdge变化快(指数函数) 167 | 0.5-1时fractionCenter变化快(指数函数),fractionEdge变化慢(根号函数) 168 | centerXOffset初始值width / 2, 变化到width + 150 169 | edgeXOffset初始值width * 0.75 ,变化到width + 100 170 | */ 171 | fraction = (mClipOffsetPixels - width / 2) / (width / 2); 172 | if (fraction <= 0.5) { 173 | fractionCenter = (float) (2 * Math.pow(fraction, 2)); 174 | fractionEdge = (float) ((1 / Math.sqrt(2)) * Math.sqrt(fraction)); 175 | } else { 176 | fractionCenter = 177 | (float) (1 / (2 - Math.sqrt(2)) * Math.sqrt(fraction) + 1 - 1 / (2 - Math.sqrt(2))); 178 | fractionEdge = (float) (2 * Math.pow(fraction, 2) / 3 + (float) 1 / 3); 179 | } 180 | centerXOffset = (int) (width / 2 + fractionCenter * (width / 2 + 150)); 181 | edgeXOffset = (int) (width * 0.75 + fractionEdge * (width / 4 + 100)); 182 | mClipPath.moveTo(width - mClipOffsetPixels, 0); 183 | mClipPath.lineTo(edgeXOffset, 0); 184 | mClipPath.quadTo(centerXOffset, eventY, edgeXOffset, height); 185 | mClipPath.lineTo(width - mClipOffsetPixels, height); 186 | mClipPath.lineTo(width - mClipOffsetPixels, 0); 187 | break; 188 | case TYPE_UP_DOWN: 189 | /** 190 | * 打开后回弹状态 191 | centerXOffset初始值width + 150,变化到width 192 | edgeXOffset初始值width + 100 ,变化到width 193 | */ 194 | centerXOffset = (int) (width + 150 - 150 * fractionUpDown); 195 | edgeXOffset = (int) (width + 100 - 100 * fractionUpDown); 196 | mClipPath.moveTo(width - mClipOffsetPixels, 0); 197 | mClipPath.lineTo(edgeXOffset, 0); 198 | mClipPath.quadTo(centerXOffset, eventY, edgeXOffset, height); 199 | mClipPath.lineTo(width - mClipOffsetPixels, height); 200 | mClipPath.lineTo(width - mClipOffsetPixels, 0); 201 | break; 202 | case TYPE_DOWN_AUTO: 203 | /** 204 | * 自动关闭状态 205 | edgeXOffset值width 206 | centerXOffset 比edgeXOffset多移动0.5 * width 207 | */ 208 | fractionCenterDown = 1 - mClipOffsetPixels / width; 209 | centerXOffset = (int) (width - 0.5 * width * fractionCenterDown); 210 | mClipPath.moveTo(width - mClipOffsetPixels, 0); 211 | mClipPath.lineTo(width, 0); 212 | mClipPath.quadTo(centerXOffset, eventY, width, height); 213 | mClipPath.lineTo(width - mClipOffsetPixels, height); 214 | mClipPath.lineTo(width - mClipOffsetPixels, 0); 215 | break; 216 | case TYPE_DOWN_MANUAL: 217 | /** 218 | * 手动关闭状态 219 | edgeXOffset值width 220 | centerXOffset 比edgeXOffset多移动0.5 * width 221 | */ 222 | fractionCenterDown = 1 - mClipOffsetPixels / width; 223 | centerXOffset = (int) (width - 0.5 * width * fractionCenterDown); 224 | mClipPath.moveTo(width - mClipOffsetPixels, 0); 225 | mClipPath.lineTo(width, 0); 226 | mClipPath.quadTo(centerXOffset, eventY, width, height); 227 | mClipPath.lineTo(width - mClipOffsetPixels, height); 228 | mClipPath.lineTo(width - mClipOffsetPixels, 0); 229 | break; 230 | case TYPE_DOWN_SMOOTH: 231 | /** 232 | * 手动打开不到一半,松手后恢复到初始状态 233 | 每次绘制两边纵坐标增加10 234 | */ 235 | bottomY = bottomY + 10; 236 | topY = topY - 10; 237 | if (eventY - height / 2 >= 0) { 238 | topControlY = (int) (-bottomY / 4 + 5 * eventY / 4); 239 | bottomControlY = (int) (bottomY / 4 + 3 * eventY / 4); 240 | } else { 241 | topControlY = (int) (topY / 4 + 3 * eventY / 4); 242 | bottomControlY = (int) (-topY / 4 + 5 * eventY / 4); 243 | } 244 | mClipPath.moveTo(width - mClipOffsetPixels, topY); 245 | mClipPath.cubicTo(width - mClipOffsetPixels, topControlY, width, 246 | topControlY, width, eventY); 247 | mClipPath.cubicTo(width, bottomControlY, width - mClipOffsetPixels, 248 | bottomControlY, width - mClipOffsetPixels, bottomY); 249 | mClipPath.lineTo(width - mClipOffsetPixels, topY); 250 | break; 251 | default: 252 | break; 253 | } 254 | } 255 | 256 | private void drawRightMenu() { 257 | switch (currentType) { 258 | case TYPE_NONE: 259 | /** 260 | * 空状态 261 | * mClipOffsetPixels =0 or mClipOffsetPixels = width 262 | */ 263 | mClipPath.moveTo(width, 0); 264 | mClipPath.lineTo(0, 0); 265 | mClipPath.lineTo(0, height); 266 | mClipPath.lineTo(width, height); 267 | mClipPath.lineTo(width, 0); 268 | break; 269 | case TYPE_UP_MANUAL: 270 | /** 271 | * 手动打开状态 272 | verticalOffsetRatio = 0 when currentPointY = 0.5 * height ; 273 | verticalOffsetRatio = 1 when currentPointY = height or currentPointY = 0; 274 | bottomY,topY由两部分组成 275 | 第一部分是初始位置,由ratio1和currentPointY决定 276 | 第二部分由currentPointX移动位置决定 277 | 两部分系数分别是ratio1,ratio2 278 | ratio1,ratio2表示 (currentPointY - topY)/ (bottomY - currentPointY) 279 | 第一部分bottomY - topY的初始值为height的0.7 倍 280 | 第二部分bottomY - topY变化的总长为currentPointX变化总长的6倍 281 | */ 282 | verticalOffsetRatio = Math.abs((double) (2 * eventY - height) / height); 283 | ratio1 = verticalOffsetRatio * 3 + 1; 284 | ratio2 = verticalOffsetRatio * 5 + 1; 285 | if (eventY - height / 2 >= 0) { 286 | bottomY = (int) (eventY + 0.7 * height / (ratio1 + 1) - mClipOffsetPixels * 6 / (ratio2 + 1)); 287 | topY = (int) (eventY - 0.7 * height / (1 + 1 / ratio1) + mClipOffsetPixels * 6 / (1 / ratio2 + 1)); 288 | topControlY = (int) (-bottomY / 4 + 5 * eventY / 4); 289 | bottomControlY = (int) (bottomY / 4 + 3 * eventY / 4); 290 | } else { 291 | bottomY = 292 | (int) (eventY + 0.7 * height / (1 / ratio1 + 1) - mClipOffsetPixels * 6 / (1 / ratio2 + 293 | 1)); 294 | topY = (int) (eventY - 0.7 * height / (1 + ratio1) + mClipOffsetPixels * 6 / (ratio2 + 1)); 295 | topControlY = (int) (topY / 4 + 3 * eventY / 4); 296 | bottomControlY = (int) (-topY / 4 + 5 * eventY / 4); 297 | } 298 | mClipPath.moveTo(-mClipOffsetPixels, topY); 299 | mClipPath.cubicTo(-mClipOffsetPixels, topControlY, 0, 300 | topControlY, 0, eventY); 301 | mClipPath.cubicTo(0, bottomControlY, -mClipOffsetPixels, 302 | bottomControlY, -mClipOffsetPixels, bottomY); 303 | mClipPath.lineTo(-mClipOffsetPixels, topY); 304 | break; 305 | case TYPE_UP_AUTO: 306 | /** 307 | * 自动打开状态 308 | fraction变化范围是0-1 309 | 0-0.5时fractionCenter变化慢(根号函数),fractionEdge变化快(指数函数) 310 | 0.5-1时fractionCenter变化快(指数函数),fractionEdge变化慢(根号函数) 311 | centerXOffset初始值width / 2, 变化到width + 150 312 | edgeXOffset初始值width * 0.75 ,变化到width + 100 313 | */ 314 | fraction = (-mClipOffsetPixels - width / 2) / (width / 2); 315 | if (fraction <= 0.5) { 316 | fractionCenter = (float) (2 * Math.pow(fraction, 2)); 317 | fractionEdge = (float) ((1 / Math.sqrt(2)) * Math.sqrt(fraction)); 318 | } else { 319 | fractionCenter = 320 | (float) (1 / (2 - Math.sqrt(2)) * Math.sqrt(fraction) + 1 - 1 / (2 - Math.sqrt(2))); 321 | fractionEdge = (float) (2 * Math.pow(fraction, 2) / 3 + (float) 1 / 3); 322 | } 323 | centerXOffset = (int) (width / 2 + fractionCenter * (width / 2 + 150)); 324 | edgeXOffset = (int) (width * 0.75 + fractionEdge * (width / 4 + 100)); 325 | mClipPath.moveTo(-mClipOffsetPixels, 0); 326 | mClipPath.lineTo(width - edgeXOffset, 0); 327 | mClipPath.quadTo(width - centerXOffset, eventY, width - edgeXOffset, height); 328 | mClipPath.lineTo(-mClipOffsetPixels, height); 329 | mClipPath.lineTo(-mClipOffsetPixels, 0); 330 | break; 331 | case TYPE_UP_DOWN: 332 | /** 333 | * 打开后回弹状态 334 | centerXOffset初始值width + 150,变化到width 335 | edgeXOffset初始值width + 100 ,变化到width 336 | */ 337 | centerXOffset = (int) (width + 150 - 150 * fractionUpDown); 338 | edgeXOffset = (int) (width + 100 - 100 * fractionUpDown); 339 | mClipPath.moveTo(-mClipOffsetPixels, 0); 340 | mClipPath.lineTo(width - edgeXOffset, 0); 341 | mClipPath.quadTo(width - centerXOffset, eventY, width - edgeXOffset, height); 342 | mClipPath.lineTo(-mClipOffsetPixels, height); 343 | mClipPath.lineTo(-mClipOffsetPixels, 0); 344 | break; 345 | case TYPE_DOWN_AUTO: 346 | /** 347 | * 自动关闭状态 348 | edgeXOffset值width 349 | centerXOffset 比edgeXOffset多移动0.5 * width 350 | */ 351 | fractionCenterDown = 1 + mClipOffsetPixels / width; 352 | centerXOffset = (int) (width - 0.5 * width * fractionCenterDown); 353 | mClipPath.moveTo(-mClipOffsetPixels, 0); 354 | mClipPath.lineTo(0, 0); 355 | mClipPath.quadTo(width - centerXOffset, eventY, 0, height); 356 | mClipPath.lineTo(-mClipOffsetPixels, height); 357 | mClipPath.lineTo(-mClipOffsetPixels, 0); 358 | break; 359 | case TYPE_DOWN_MANUAL: 360 | /** 361 | * 手动关闭状态 362 | edgeXOffset值width 363 | centerXOffset 比edgeXOffset多移动0.5 * width 364 | */ 365 | fractionCenterDown = 1 + mClipOffsetPixels / width; 366 | centerXOffset = (int) (width - 0.5 * width * fractionCenterDown); 367 | mClipPath.moveTo(-mClipOffsetPixels, 0); 368 | mClipPath.lineTo(0, 0); 369 | mClipPath.quadTo(width - centerXOffset, eventY, 0, height); 370 | mClipPath.lineTo(-mClipOffsetPixels, height); 371 | mClipPath.lineTo(-mClipOffsetPixels, 0); 372 | break; 373 | case TYPE_DOWN_SMOOTH: 374 | /** 375 | * 手动打开不到一半,松手后恢复到初始状态 376 | 每次绘制两边纵坐标增加10 377 | */ 378 | bottomY = bottomY + 10; 379 | topY = topY - 10; 380 | if (eventY - height / 2 >= 0) { 381 | topControlY = (int) (-bottomY / 4 + 5 * eventY / 4); 382 | bottomControlY = (int) (bottomY / 4 + 3 * eventY / 4); 383 | } else { 384 | topControlY = (int) (topY / 4 + 3 * eventY / 4); 385 | bottomControlY = (int) (-topY / 4 + 5 * eventY / 4); 386 | } 387 | mClipPath.moveTo(-mClipOffsetPixels, topY); 388 | mClipPath.cubicTo(-mClipOffsetPixels, topControlY, 0, 389 | topControlY, 0, eventY); 390 | mClipPath.cubicTo(0, bottomControlY, -mClipOffsetPixels, 391 | bottomControlY, -mClipOffsetPixels, bottomY); 392 | mClipPath.lineTo(-mClipOffsetPixels, topY); 393 | break; 394 | default: 395 | break; 396 | } 397 | } 398 | 399 | } 400 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/NoClickThroughFrameLayout.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mxn.soul.flowingdrawer_core; 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * Created by mxn on 2016/10/18. 10 | * BuildLayerFrameLayout 11 | */ 12 | public class NoClickThroughFrameLayout extends BuildLayerFrameLayout { 13 | 14 | public NoClickThroughFrameLayout(Context context) { 15 | super(context); 16 | } 17 | 18 | public NoClickThroughFrameLayout(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | } 21 | 22 | public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) { 23 | super(context, attrs, defStyle); 24 | } 25 | 26 | @Override 27 | public boolean onTouchEvent(MotionEvent event) { 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/SmoothInterpolator.java: -------------------------------------------------------------------------------- 1 | 2 | package com.mxn.soul.flowingdrawer_core; 3 | 4 | import android.view.animation.Interpolator; 5 | 6 | /** 7 | * Created by mxn on 2016/10/17. 8 | * SmoothInterpolator 9 | */ 10 | class SmoothInterpolator implements Interpolator { 11 | 12 | @Override 13 | public float getInterpolation(float t) { 14 | t -= 1.0f; 15 | return t * t * t * t * t + 1.0f; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /flowingdrawer-core/src/main/java/com/mxn/soul/flowingdrawer_core/ViewHelper.java: -------------------------------------------------------------------------------- 1 | package com.mxn.soul.flowingdrawer_core; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Build; 5 | import android.view.View; 6 | 7 | /** 8 | * Created by mxn on 2016/12/9. 9 | * ViewHelper 10 | */ 11 | 12 | final class ViewHelper { 13 | 14 | private ViewHelper() { 15 | } 16 | 17 | @SuppressLint("NewApi") 18 | static int getLeft(View v) { 19 | return (int) (v.getLeft() + v.getTranslationX()); 20 | } 21 | 22 | @SuppressLint("NewApi") 23 | static int getTop(View v) { 24 | return (int) (v.getTop() + v.getTranslationY()); 25 | } 26 | 27 | @SuppressLint("NewApi") 28 | static int getRight(View v) { 29 | return (int) (v.getRight() + v.getTranslationX()); 30 | } 31 | 32 | @SuppressLint("NewApi") 33 | static int getLayoutDirection(View v) { 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 35 | return v.getLayoutDirection(); 36 | } 37 | 38 | return View.LAYOUT_DIRECTION_LTR; 39 | } 40 | } -------------------------------------------------------------------------------- /flowingdrawer-core/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 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /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/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 17 11:03:47 CST 2019 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-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mxn21/FlowingDrawer/5a8112c530fee29510bf927fe966635c34c0ab1b/screen.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':flowingdrawer-core' 2 | --------------------------------------------------------------------------------