├── .gitignore ├── LICENSE.txt ├── Library ├── build.gradle ├── gradle.properties ├── libs │ └── android-support-v4.jar ├── pom.xml └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── wunderlist │ │ └── slidinglayer │ │ ├── LayerTransformer.java │ │ ├── SlidingLayer.java │ │ ├── transformer │ │ ├── AlphaTransformer.java │ │ ├── RotationTransformer.java │ │ └── SlideJoyTransformer.java │ │ └── utils │ │ └── Transitions.java │ └── res │ └── values │ └── slidinglayer-attrs.xml ├── README.md ├── SlidingLayerSample ├── art │ ├── device-2015-09-13-131637.png │ ├── device-2015-09-13-131714.png │ ├── device-2015-09-13-131735.png │ ├── device-2015-09-13-131825.png │ ├── device-2015-09-13-131907.png │ └── device-2015-09-13-131931.png ├── build.gradle ├── ic_launcher-web.png ├── pom.xml └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── slidinglayersample │ │ ├── InitSelectionActivity.java │ │ └── MainActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-ldpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ ├── button_red.9.png │ ├── button_red_pressed.9.png │ ├── container_rocket.png │ ├── container_rocket_left.png │ ├── container_rocket_right.png │ ├── ic_launcher.png │ └── main_screen_rocket.png │ ├── drawable │ ├── button_red_selector.xml │ └── sidebar_shadow.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_settings.xml │ └── toolbar.xml │ ├── values │ ├── colors.xml │ ├── dimensions.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── pref_general_activity.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | target/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Eclipse project files 20 | .classpath 21 | .project 22 | # IntelliJ project files 23 | *.iml 24 | *.ipr 25 | *.iws 26 | .idea/ 27 | 28 | # Package Files # 29 | *.war 30 | *.ear 31 | *.apk 32 | bin/ 33 | bin 34 | gen/ 35 | gen 36 | .metadata/ 37 | .metadata 38 | 39 | # Gradle files 40 | .gradle/ 41 | build/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /Library/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.1.0' 8 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' 10 | } 11 | } 12 | 13 | apply plugin: 'com.android.library' 14 | apply plugin: 'com.github.dcendents.android-maven' 15 | 16 | dependencies { 17 | compile 'com.android.support:support-v4:23.0.1' 18 | } 19 | 20 | ext { 21 | bintrayRepo = 'maven' 22 | bintrayName = 'wunderlist-sliding-layer' 23 | 24 | publishedGroupId = 'com.wunderlist' 25 | libraryName = 'wunderlist-sliding-layer' 26 | artifact = 'sliding-layer' 27 | 28 | libraryDescription = 'A fully customizable layer that shows your content in another layer. This layer can be ' + 29 | 'opened swiping from left/right/top/bottom and dismissed swiping too to the other direction.' 30 | 31 | siteUrl = 'https://github.com/wunderlist/android-sliding-layer-lib' 32 | gitUrl = 'https://github.com/wunderlist/android-sliding-layer-lib.git' 33 | 34 | libraryVersion = '1.2.5' 35 | 36 | developerId = '6Wunderkinder Gmbh' 37 | developerName = '6Wunderkinder Gmbh' 38 | developerEmail = 'android@6wunderkinder.com' 39 | 40 | licenseName = 'The Apache Software License, Version 2.0' 41 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 42 | allLicenses = ["Apache-2.0"] 43 | } 44 | 45 | android { 46 | compileSdkVersion 23 47 | buildToolsVersion "23.0.1" 48 | 49 | defaultConfig { 50 | minSdkVersion 8 51 | targetSdkVersion 23 52 | versionCode 2 53 | versionName "1.2.5" 54 | } 55 | 56 | lintOptions { 57 | abortOnError false 58 | } 59 | } 60 | 61 | apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' 62 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 63 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' 64 | 65 | -------------------------------------------------------------------------------- /Library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=wunderlist-sliding-layer 2 | POM_ARTIFACT_ID=sliding-layer 3 | POM_PACKAGING=aar 4 | 5 | VERSION_NAME=1.2.5 6 | VERSION_CODE=1 7 | GROUP=com.wunderlist 8 | 9 | POM_DESCRIPTION=A fully customizable layer that shows your content in another layer. This layer can be opened \ 10 | swiping from left/right/top/bottom and dismissed swiping too to the other direction. 11 | POM_URL=https://github.com/wunderlist/android-sliding-layer-lib 12 | POM_SCM_URL=https://github.com/wunderlist/android-sliding-layer-lib 13 | POM_SCM_CONNECTION=scm:git@github.com:wunderlist/android-sliding-layer-lib.git 14 | POM_SCM_DEV_CONNECTION=scm:git@github.com:wunderlist/android-sliding-layer-lib.git 15 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 16 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 17 | POM_LICENCE_DIST=repo 18 | POM_DEVELOPER_ID=6Wunderkinder Gmbh 19 | POM_DEVELOPER_NAME=6Wunderkinder Gmbh -------------------------------------------------------------------------------- /Library/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/Library/libs/android-support-v4.jar -------------------------------------------------------------------------------- /Library/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 4.0.0 23 | 24 | Library 25 | SlidingLayer (Library) 26 | apklib 27 | 28 | 29 | com.wunderlist 30 | sliding-layer 31 | 1.2.5 32 | ../pom.xml 33 | 34 | 35 | 36 | 37 | android 38 | android 39 | ${android.version} 40 | provided 41 | 42 | 43 | android.support 44 | compatibility-v4 45 | ${android-support.version} 46 | provided 47 | 48 | 49 | 50 | 51 | src 52 | 53 | 54 | 55 | com.jayway.maven.plugins.android.generation2 56 | android-maven-plugin 57 | true 58 | 59 | ignored 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-javadoc-plugin 66 | 67 | true 68 | 69 | 70 | 71 | 72 | org.codehaus.mojo 73 | build-helper-maven-plugin 74 | 1.7 75 | 76 | 77 | package 78 | 79 | attach-artifact 80 | 81 | 82 | 83 | 84 | jar 85 | ${project.build.directory}/${project.build.finalName}.jar 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Library/src/main/java/com/wunderlist/slidinglayer/LayerTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SlidingLayer.java 3 | * 4 | * Copyright (C) 2015 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @version 1.2.0 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | package com.wunderlist.slidinglayer; 25 | 26 | import android.view.View; 27 | 28 | /** 29 | * Created by joseluisugia on 16/03/15. 30 | */ 31 | public abstract class LayerTransformer { 32 | 33 | /** 34 | * This method is executed when the layer finish measurement. Use this method to set member values inside of 35 | * the transformer so that they do not need to be calculated on every iteration of the animation; 36 | * 37 | * @param layerView A reference to the layer itself. 38 | * @param screenSide Side of the screen where the layer is stuck to 39 | */ 40 | protected void onMeasure(View layerView, int screenSide) { 41 | } 42 | 43 | /** 44 | * Internal method to expose necessary properties for internal transformers to operate. Note that custom 45 | * transformers will modify views based on their circumstances. For example, 46 | * a default transformer if the view is stuck to the top or bottom, or if the preview is enabled, 47 | * whereas a custom implementation has that information already. 48 | * 49 | * @param layerView A reference to the layer itself. 50 | * @param previewProgress The progress of the layer relative to the preview mode [0 - 1]. 0 fixed if no preview 51 | * @param layerProgress The progress of the layer relative to its total size [0 - 1] 52 | * @param screenSide Side of the screen where the layer is stuck to 53 | */ 54 | protected void internalTransform(View layerView, float previewProgress, float layerProgress, int screenSide) { 55 | transform(layerView, previewProgress, layerProgress); 56 | } 57 | 58 | /** 59 | * Apply a property transformation to layer based on its scrolling state for the total size of the layer 60 | * and preview mode. 61 | * 62 | * @param layerView A reference to the layer itself. 63 | * @param previewProgress The progress of the layer relative to the preview mode [0 - 1]. 0 fixed if no preview 64 | * @param layerProgress The progress of the layer relative to its total size [0 - 1] 65 | */ 66 | public abstract void transform(View layerView, float previewProgress, float layerProgress); 67 | } 68 | -------------------------------------------------------------------------------- /Library/src/main/java/com/wunderlist/slidinglayer/SlidingLayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SlidingLayer.java 3 | * 4 | * Copyright (C) 2015 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @version 1.2.0 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | package com.wunderlist.slidinglayer; 25 | 26 | import java.util.Random; 27 | 28 | import android.content.Context; 29 | import android.content.res.TypedArray; 30 | import android.graphics.Canvas; 31 | import android.graphics.drawable.Drawable; 32 | import android.os.Build; 33 | import android.os.Bundle; 34 | import android.os.Parcel; 35 | import android.os.Parcelable; 36 | import android.support.v4.view.MotionEventCompat; 37 | import android.support.v4.view.VelocityTrackerCompat; 38 | import android.support.v4.view.ViewCompat; 39 | import android.support.v4.view.ViewConfigurationCompat; 40 | import android.util.AttributeSet; 41 | import android.view.Gravity; 42 | import android.view.MotionEvent; 43 | import android.view.VelocityTracker; 44 | import android.view.View; 45 | import android.view.ViewConfiguration; 46 | import android.view.ViewGroup; 47 | import android.view.animation.Interpolator; 48 | import android.widget.FrameLayout; 49 | import android.widget.RelativeLayout; 50 | import android.widget.Scroller; 51 | 52 | 53 | public class SlidingLayer extends FrameLayout { 54 | 55 | private static final String STATE_KEY = "state"; 56 | 57 | /** 58 | * Special value for the position of the layer. STICK_TO_RIGHT means that 59 | * the view shall be attached to the right side of the screen, and come from 60 | * there into the viewable area. 61 | */ 62 | public static final int STICK_TO_RIGHT = -1; 63 | 64 | /** 65 | * Special value for the position of the layer. STICK_TO_LEFT means that the 66 | * view shall be attached to the left side of the screen, and come from 67 | * there into the viewable area. 68 | */ 69 | public static final int STICK_TO_LEFT = -2; 70 | 71 | /** 72 | * Special value for the position of the layer. STICK_TO_TOP means that the view will stay attached to the top 73 | * part of the screen, and come from there into the viewable area. 74 | */ 75 | public static final int STICK_TO_TOP = -3; 76 | 77 | /** 78 | * Special value for the position of the layer. STICK_TO_BOTTOM means that the view will stay attached to the 79 | * bottom part of the screen, and come from there into the viewable area. 80 | */ 81 | public static final int STICK_TO_BOTTOM = -4; 82 | 83 | private static final int HORIZONTAL = 0; 84 | private static final int VERTICAL = 1; 85 | 86 | private static final int HIGH_VELOCITY = 9000; 87 | private static final int MAX_SCROLLING_DURATION = 600; // in ms 88 | private static final int MIN_DISTANCE_FOR_FLING = 10; // in dip 89 | private static final Interpolator sMenuInterpolator = new Interpolator() { 90 | @Override 91 | public float getInterpolation(float t) { 92 | t -= 1.0f; 93 | return (float) Math.pow(t, 5) + 1.0f; 94 | } 95 | }; 96 | 97 | /** 98 | * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 99 | */ 100 | private static final int INVALID_VALUE = -1; 101 | protected int mActivePointerId = INVALID_VALUE; 102 | protected VelocityTracker mVelocityTracker; 103 | protected int mMaximumVelocity; 104 | 105 | private Random mRandom; 106 | protected Bundle mState; 107 | 108 | private Scroller mScroller; 109 | 110 | private int mShadowSize; 111 | private Drawable mShadowDrawable; 112 | private boolean mForceLayout; 113 | 114 | /** 115 | * The size of the panel that sticks out when closed 116 | */ 117 | private int mOffsetDistance; 118 | 119 | private boolean mDrawingCacheEnabled; 120 | private int mScreenSide; 121 | 122 | /** 123 | * If the user taps the layer then we will switch state it if enabled. 124 | */ 125 | private boolean changeStateOnTap = true; 126 | 127 | /** 128 | * The size of the panel in preview mode 129 | */ 130 | private int mPreviewOffsetDistance = INVALID_VALUE; 131 | 132 | private boolean mEnabled = true; 133 | private boolean mSlidingFromShadowEnabled = true; 134 | private boolean mIsDragging; 135 | private boolean mIsUnableToDrag; 136 | private int mTouchSlop; 137 | 138 | private float mLastX = INVALID_VALUE; 139 | private float mLastY = INVALID_VALUE; 140 | 141 | private float mInitialX = INVALID_VALUE; 142 | private float mInitialY = INVALID_VALUE; 143 | private float mInitialRawX = INVALID_VALUE; 144 | private float mInitialRawY = INVALID_VALUE; 145 | 146 | /** 147 | * Flags to determine the state of the layer 148 | */ 149 | private static final int STATE_CLOSED = 0; 150 | private static final int STATE_PREVIEW = 1; 151 | private static final int STATE_OPENED = 2; 152 | 153 | private int mCurrentState; 154 | 155 | private boolean mScrolling; 156 | 157 | private OnInteractListener mOnInteractListener; 158 | 159 | /** 160 | * Optional callback to notify client when scroll position has changed 161 | */ 162 | private OnScrollListener mOnScrollListener; 163 | 164 | private int mMinimumVelocity; 165 | private int mFlingDistance; 166 | 167 | private LayerTransformer mLayerTransformer; 168 | 169 | public SlidingLayer(Context context) { 170 | this(context, null); 171 | } 172 | 173 | public SlidingLayer(Context context, AttributeSet attrs) { 174 | this(context, attrs, 0); 175 | } 176 | 177 | /** 178 | * Constructor for the sliding layer.
179 | * By default this panel will 180 | *
    181 | *
  1. {@link #setStickTo(int)} with param {@link #STICK_TO_RIGHT}
  2. 182 | *
  3. Use no shadow drawable. (i.e. with size of 0)
  4. 183 | *
  5. Close when the panel is tapped
  6. 184 | *
  7. Open when the offset is tapped, but will have an offset of 0
  8. 185 | *
186 | * 187 | * @param context a reference to an existing context 188 | * @param attrs attribute set constructed from attributes set in android .xml file 189 | * @param defStyle style res id 190 | */ 191 | public SlidingLayer(Context context, AttributeSet attrs, int defStyle) { 192 | super(context, attrs, defStyle); 193 | 194 | // Style 195 | final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingLayer); 196 | 197 | // Set the side of the screen 198 | setStickTo(ta.getInt(R.styleable.SlidingLayer_stickTo, STICK_TO_RIGHT)); 199 | 200 | // Sets the shadow drawable 201 | int shadowRes = ta.getResourceId(R.styleable.SlidingLayer_shadowDrawable, INVALID_VALUE); 202 | if (shadowRes != INVALID_VALUE) { 203 | setShadowDrawable(shadowRes); 204 | } 205 | 206 | // Sets the shadow size 207 | mShadowSize = (int) ta.getDimension(R.styleable.SlidingLayer_shadowSize, 0); 208 | 209 | // Sets the ability to open or close the layer by tapping in any empty space 210 | changeStateOnTap = ta.getBoolean(R.styleable.SlidingLayer_changeStateOnTap, true); 211 | 212 | // How much of the view sticks out when closed 213 | mOffsetDistance = ta.getDimensionPixelOffset(R.styleable.SlidingLayer_offsetDistance, 0); 214 | 215 | // Sets the size of the preview summary, if any 216 | mPreviewOffsetDistance = ta.getDimensionPixelOffset(R.styleable.SlidingLayer_previewOffsetDistance, 217 | INVALID_VALUE); 218 | 219 | // If showing offset is greater than preview mode offset dimension, exception is thrown 220 | checkPreviewModeConsistency(); 221 | 222 | ta.recycle(); 223 | 224 | init(); 225 | } 226 | 227 | private void init() { 228 | 229 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 230 | setLayerType(LAYER_TYPE_HARDWARE, null); 231 | } 232 | 233 | setWillNotDraw(false); 234 | setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 235 | setFocusable(true); 236 | 237 | final Context context = getContext(); 238 | mScroller = new Scroller(context, sMenuInterpolator); 239 | 240 | final ViewConfiguration configuration = ViewConfiguration.get(context); 241 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 242 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 243 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 244 | 245 | final float density = context.getResources().getDisplayMetrics().density; 246 | mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); 247 | 248 | mRandom = new Random(); 249 | } 250 | 251 | /** 252 | * Method exposing the state of the panel 253 | * 254 | * @return returns the state of the panel (@link STATE_OPENED, STATE_CLOSED or STATE_PREVIEW). Please note 255 | * that if the panel was opened with smooth animation this method is not guaranteed to return 256 | * its final value until the panel has reached its final position. 257 | */ 258 | private int getCurrentState() { 259 | return mCurrentState; 260 | } 261 | 262 | public boolean isOpened() { 263 | return mCurrentState == STATE_OPENED; 264 | } 265 | 266 | public boolean isInPreviewMode() { 267 | return mCurrentState == STATE_PREVIEW; 268 | } 269 | 270 | public boolean isClosed() { 271 | return mCurrentState == STATE_CLOSED; 272 | } 273 | 274 | public void openLayer(final boolean smoothAnimation) { 275 | setLayerState(STATE_OPENED, smoothAnimation); 276 | } 277 | 278 | public void openPreview(final boolean smoothAnimation) { 279 | if (mPreviewOffsetDistance == INVALID_VALUE) { 280 | throw new IllegalStateException("A value offset for the preview has to be specified in order to open " + 281 | "the layer in preview mode. Use setPreviewOffsetDistance or set its associated XML property "); 282 | } 283 | setLayerState(STATE_PREVIEW, smoothAnimation); 284 | } 285 | 286 | public void closeLayer(final boolean smoothAnimation) { 287 | setLayerState(STATE_CLOSED, smoothAnimation); 288 | } 289 | 290 | private void setLayerState(final int state, final boolean smoothAnimation) { 291 | setLayerState(state, smoothAnimation, false); 292 | } 293 | 294 | private void setLayerState(final int state, final boolean smoothAnimation, boolean force) { 295 | setLayerState(state, smoothAnimation, force, 0, 0); 296 | } 297 | 298 | private void setLayerState(final int state, final boolean smoothAnimation, final boolean force, 299 | final int velocityX, final int velocityY) { 300 | 301 | if (!force && mCurrentState == state) { 302 | setDrawingCacheEnabled(false); 303 | return; 304 | } 305 | 306 | if (mOnInteractListener != null) { 307 | notifyActionStartedForState(state); 308 | } 309 | 310 | final int pos[] = getDestScrollPosForState(state); 311 | 312 | if (smoothAnimation) { 313 | int velocity = allowedDirection() == HORIZONTAL ? velocityX : velocityY; 314 | smoothScrollTo(pos[0], pos[1], velocity); 315 | } else { 316 | completeScroll(); 317 | scrollToAndNotify(pos[0], pos[1]); 318 | } 319 | 320 | mCurrentState = state; 321 | } 322 | 323 | /** 324 | * Sets the listener to be invoked after a switch change 325 | * {@link OnInteractListener}. 326 | * 327 | * @param listener Listener to set 328 | */ 329 | public void setOnInteractListener(OnInteractListener listener) { 330 | mOnInteractListener = listener; 331 | } 332 | 333 | /** 334 | * Sets the listener to be invoked when the layer is being scrolled 335 | * {@link OnScrollListener}. 336 | * 337 | * @param listener Listener to set 338 | */ 339 | public void setOnScrollListener(OnScrollListener listener) { 340 | mOnScrollListener = listener; 341 | } 342 | 343 | /** 344 | * Sets the transformer to use when the layer is being scrolled 345 | * {@link LayerTransformer}. 346 | * 347 | * @param layerTransformer Transformer to adopt 348 | */ 349 | public void setLayerTransformer(LayerTransformer layerTransformer) { 350 | mLayerTransformer = layerTransformer; 351 | } 352 | 353 | /** 354 | * Sets the shadow of the size which will be included within the view by 355 | * using padding since it's on the left of the view in this case 356 | * 357 | * @param shadowSize Desired size of the shadow 358 | * @see #getShadowSize() 359 | * @see #setShadowDrawable(Drawable) 360 | * @see #setShadowDrawable(int) 361 | */ 362 | public void setShadowSize(final int shadowSize) { 363 | mShadowSize = shadowSize; 364 | invalidate(getLeft(), getTop(), getRight(), getBottom()); 365 | } 366 | 367 | /** 368 | * Sets the shadow size by the value of a resource. 369 | * 370 | * @param resId The dimension resource id to be set as the shadow size. 371 | */ 372 | public void setShadowSizeRes(int resId) { 373 | setShadowSize((int) getResources().getDimension(resId)); 374 | } 375 | 376 | /** 377 | * Return the current size of the shadow. 378 | * 379 | * @return The size of the shadow in pixels 380 | */ 381 | public int getShadowSize() { 382 | return mShadowSize; 383 | } 384 | 385 | /** 386 | * Sets a drawable that will be used to create the shadow for the layer. 387 | * 388 | * @param d Drawable append as a shadow 389 | */ 390 | public void setShadowDrawable(final Drawable d) { 391 | mShadowDrawable = d; 392 | refreshDrawableState(); 393 | setWillNotDraw(false); 394 | invalidate(getLeft(), getTop(), getRight(), getBottom()); 395 | } 396 | 397 | /** 398 | * Sets a drawable resource that will be used to create the shadow for the 399 | * layer. 400 | * 401 | * @param resId Resource ID of a drawable 402 | */ 403 | public void setShadowDrawable(int resId) { 404 | setShadowDrawable(getContext().getResources().getDrawable(resId)); 405 | } 406 | 407 | /** 408 | * Sets the offset distance of the panel by using a dimension resource. 409 | * 410 | * @param resId The dimension resource id to be set as the offset. 411 | */ 412 | public void setOffsetDistanceRes(int resId) { 413 | setOffsetDistance((int) getResources().getDimension(resId)); 414 | } 415 | 416 | /** 417 | * Sets the offset distance of the panel. How much sticks out when off screen. 418 | * 419 | * @param offsetDistance Size of the offset in pixels 420 | * @see #getOffsetDistance() 421 | */ 422 | public void setOffsetDistance(int offsetDistance) { 423 | mOffsetDistance = offsetDistance; 424 | checkPreviewModeConsistency(); 425 | invalidate(getLeft(), getTop(), getRight(), getBottom()); 426 | } 427 | 428 | /** 429 | * @return returns the number of pixels that are visible when the panel is closed 430 | */ 431 | public int getOffsetDistance() { 432 | return mOffsetDistance; 433 | } 434 | 435 | /** 436 | * Sets the offset distance of the preview panel by using a dimension resource. 437 | * 438 | * @param resId The dimension resource id to be set as the size of the preview mode. 439 | */ 440 | public void setPreviewOffsetDistanceRes(int resId) { 441 | setPreviewOffsetDistance((int) getResources().getDimension(resId)); 442 | } 443 | 444 | /** 445 | * Sets the size of the panel when in preview mode. 446 | * 447 | * @param previewOffsetDistance Size of the offset in pixels 448 | * @see #getOffsetDistance() 449 | */ 450 | public void setPreviewOffsetDistance(int previewOffsetDistance) { 451 | mPreviewOffsetDistance = previewOffsetDistance; 452 | 453 | checkPreviewModeConsistency(); 454 | invalidate(getLeft(), getTop(), getRight(), getBottom()); 455 | 456 | if (mCurrentState == STATE_PREVIEW) { 457 | smoothScrollToCurrentPosition(); 458 | } 459 | } 460 | 461 | private void checkPreviewModeConsistency() { 462 | if (isPreviewModeEnabled() && mOffsetDistance > mPreviewOffsetDistance) { 463 | throw new IllegalStateException("The showing offset of the layer can never be greater than the " + 464 | "offset dimension of the preview mode"); 465 | } 466 | } 467 | 468 | /** 469 | * @return true if the preview mode is enabled 470 | */ 471 | private boolean isPreviewModeEnabled() { 472 | return mPreviewOffsetDistance != INVALID_VALUE; 473 | } 474 | 475 | @Override 476 | protected boolean verifyDrawable(Drawable who) { 477 | return super.verifyDrawable(who) || who == mShadowDrawable; 478 | } 479 | 480 | @Override 481 | protected void drawableStateChanged() { 482 | super.drawableStateChanged(); 483 | final Drawable d = mShadowDrawable; 484 | if (d != null && d.isStateful()) { 485 | d.setState(getDrawableState()); 486 | } 487 | } 488 | 489 | public boolean isSlidingEnabled() { 490 | return mEnabled; 491 | } 492 | 493 | public void setSlidingEnabled(boolean _enabled) { 494 | mEnabled = _enabled; 495 | } 496 | 497 | public boolean isSlidingFromShadowEnabled() { 498 | return mSlidingFromShadowEnabled; 499 | } 500 | 501 | public void setSlidingFromShadowEnabled(boolean _slidingShadow) { 502 | mSlidingFromShadowEnabled = _slidingShadow; 503 | } 504 | 505 | @Override 506 | protected Parcelable onSaveInstanceState() { 507 | Parcelable superState = super.onSaveInstanceState(); 508 | SavedState state = new SavedState(superState); 509 | if (mState == null) { 510 | mState = new Bundle(); 511 | } 512 | mState.putInt(STATE_KEY, mCurrentState); 513 | state.mState = mState; 514 | return state; 515 | } 516 | 517 | @Override 518 | protected void onRestoreInstanceState(Parcelable state) { 519 | SavedState savedState = (SavedState) state; 520 | super.onRestoreInstanceState(savedState.getSuperState()); 521 | restoreState(savedState.mState); 522 | } 523 | 524 | public void restoreState(Parcelable in) { 525 | mState = (Bundle) in; 526 | int state = mState.getInt(STATE_KEY); 527 | setLayerState(state, true); 528 | } 529 | 530 | private float getViewX(MotionEvent event) { 531 | return event.getRawX(); 532 | } 533 | 534 | private float getViewY(MotionEvent event) { 535 | return event.getRawY(); 536 | } 537 | 538 | @Override 539 | public boolean onInterceptTouchEvent(MotionEvent ev) { 540 | 541 | if (!mEnabled) { 542 | return false; 543 | } 544 | 545 | final int action = ev.getAction() & MotionEvent.ACTION_MASK; 546 | 547 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 548 | mIsDragging = false; 549 | mIsUnableToDrag = false; 550 | mActivePointerId = INVALID_VALUE; 551 | if (mVelocityTracker != null) { 552 | mVelocityTracker.recycle(); 553 | mVelocityTracker = null; 554 | } 555 | return false; 556 | } 557 | 558 | if (action != MotionEvent.ACTION_DOWN) { 559 | if (mIsDragging) { 560 | return true; 561 | } else if (mIsUnableToDrag) { 562 | return false; 563 | } 564 | } 565 | 566 | switch (action) { 567 | case MotionEvent.ACTION_MOVE: 568 | 569 | final int activePointerId = mActivePointerId; 570 | if (activePointerId == INVALID_VALUE) { 571 | break; 572 | } 573 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 574 | final float x = getViewX(ev); 575 | final float dx = x - mLastX; 576 | final float xDiff = Math.abs(dx); 577 | final float y = getViewY(ev); 578 | final float dy = y - mLastY; 579 | final float yDiff = Math.abs(dy); 580 | 581 | if ((dx != 0 || dy != 0) && 582 | canScroll(this, false, (int) dx, (int) dy, (int) x, (int) y)) { 583 | mLastX = mInitialRawX = x; 584 | mLastY = mInitialRawY = y; 585 | mInitialX = ev.getX(pointerIndex); 586 | mInitialY = ev.getY(pointerIndex); 587 | return false; 588 | } 589 | 590 | final boolean validHorizontalDrag = xDiff > mTouchSlop && xDiff > yDiff; 591 | final boolean validVerticalDrag = yDiff > mTouchSlop && yDiff > xDiff; 592 | 593 | if (validHorizontalDrag) { 594 | mLastX = x; 595 | } else if (validVerticalDrag) { 596 | mLastY = y; 597 | } 598 | 599 | if (validHorizontalDrag || validVerticalDrag) { 600 | mIsDragging = true; 601 | setDrawingCacheEnabled(true); 602 | } 603 | break; 604 | 605 | case MotionEvent.ACTION_DOWN: 606 | mLastX = mInitialRawX = getViewX(ev); 607 | mLastY = mInitialRawY = getViewY(ev); 608 | mInitialX = ev.getX(0); 609 | mInitialY = ev.getY(0); 610 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 611 | 612 | if (touchPointIsWithinBounds(ev.getX(), ev.getY())) { 613 | mIsDragging = false; 614 | mIsUnableToDrag = false; 615 | // We don't want to do anything, send the event up 616 | return super.onInterceptTouchEvent(ev); 617 | } else { 618 | completeScroll(); 619 | mIsDragging = false; 620 | mIsUnableToDrag = true; 621 | } 622 | break; 623 | case MotionEvent.ACTION_POINTER_UP: 624 | onSecondaryPointerUp(ev); 625 | break; 626 | } 627 | 628 | if (!mIsDragging) { 629 | if (mVelocityTracker == null) { 630 | mVelocityTracker = VelocityTracker.obtain(); 631 | } 632 | mVelocityTracker.addMovement(ev); 633 | } 634 | 635 | return mIsDragging; 636 | } 637 | 638 | @Override 639 | public boolean onTouchEvent(MotionEvent ev) { 640 | 641 | if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 642 | return false; 643 | } 644 | 645 | if (!mEnabled || !mIsDragging && !touchPointIsWithinBounds(mInitialX, mInitialY)) { 646 | return false; 647 | } 648 | 649 | if (mVelocityTracker == null) { 650 | mVelocityTracker = VelocityTracker.obtain(); 651 | } 652 | mVelocityTracker.addMovement(ev); 653 | 654 | final int action = ev.getAction(); 655 | 656 | switch (action & MotionEvent.ACTION_MASK) { 657 | case MotionEvent.ACTION_DOWN: { 658 | completeScroll(); 659 | 660 | // Remember where the motion event started 661 | mLastX = mInitialRawX = getViewX(ev); 662 | mLastY = mInitialRawY = getViewY(ev); 663 | mInitialX = ev.getX(); 664 | mInitialY = ev.getY(); 665 | mActivePointerId = ev.getPointerId(0); 666 | break; 667 | } 668 | 669 | case MotionEvent.ACTION_MOVE: { 670 | 671 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 672 | 673 | if (!touchPointIsWithinBounds(ev.getX(), ev.getY(), false)) return false; 674 | 675 | final float x = getViewX(ev); 676 | final float y = getViewY(ev); 677 | 678 | final float deltaX = mLastX - x; 679 | final float deltaY = mLastY - y; 680 | 681 | mLastX = x; 682 | mLastY = y; 683 | 684 | if (!mIsDragging) { 685 | 686 | final float xDiff = Math.abs(x - mInitialRawX); 687 | final float yDiff = Math.abs(y - mInitialRawY); 688 | 689 | final boolean validHorizontalDrag = xDiff > mTouchSlop && xDiff > yDiff; 690 | final boolean validVerticalDrag = yDiff > mTouchSlop && yDiff > xDiff; 691 | 692 | if (validHorizontalDrag || validVerticalDrag) { 693 | mIsDragging = true; 694 | setDrawingCacheEnabled(true); 695 | } 696 | } 697 | 698 | if (mIsDragging) { 699 | 700 | final float oldScrollX = getScrollX(); 701 | final float oldScrollY = getScrollY(); 702 | float scrollX = oldScrollX + deltaX; 703 | float scrollY = oldScrollY + deltaY; 704 | 705 | // Log.d("Layer", String.format("Layer scrollX[%f],scrollY[%f]", scrollX, scrollY)); 706 | final float leftBound, rightBound; 707 | final float bottomBound, topBound; 708 | switch (mScreenSide) { 709 | case STICK_TO_LEFT: 710 | topBound = bottomBound = rightBound = 0; 711 | leftBound = getWidth(); // How far left we can scroll 712 | break; 713 | case STICK_TO_RIGHT: 714 | rightBound = -getWidth(); 715 | topBound = bottomBound = leftBound = 0; 716 | break; 717 | case STICK_TO_TOP: 718 | topBound = getHeight(); 719 | bottomBound = rightBound = leftBound = 0; 720 | break; 721 | case STICK_TO_BOTTOM: 722 | bottomBound = -getHeight(); 723 | topBound = rightBound = leftBound = 0; 724 | break; 725 | default: 726 | topBound = bottomBound = rightBound = leftBound = 0; 727 | break; 728 | } 729 | 730 | if (scrollX > leftBound) { 731 | scrollX = leftBound; 732 | } else if (scrollX < rightBound) { 733 | scrollX = rightBound; 734 | } 735 | if (scrollY > topBound) { 736 | scrollY = topBound; 737 | } else if (scrollY < bottomBound) { 738 | scrollY = bottomBound; 739 | } 740 | 741 | // Keep the precision 742 | mLastX += scrollX - (int) scrollX; 743 | mLastY += scrollY - (int) scrollY; 744 | 745 | scrollToAndNotify((int) scrollX, (int) scrollY); 746 | } 747 | break; 748 | } 749 | 750 | case MotionEvent.ACTION_UP: { 751 | 752 | if (mIsDragging) { 753 | final VelocityTracker velocityTracker = mVelocityTracker; 754 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 755 | final int initialVelocityX = (int) VelocityTrackerCompat.getXVelocity(velocityTracker, 756 | mActivePointerId); 757 | final int initialVelocityY = (int) VelocityTrackerCompat.getYVelocity(velocityTracker, 758 | mActivePointerId); 759 | final int scrollX = getScrollX(); 760 | final int scrollY = getScrollY(); 761 | 762 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 763 | final float x = getViewX(ev); 764 | final float y = getViewY(ev); 765 | 766 | int nextState = determineNextStateForDrag(scrollX, scrollY, initialVelocityX, initialVelocityY, 767 | (int) mInitialRawX, (int) mInitialRawY, (int) x, (int) y); 768 | setLayerState(nextState, true, true, initialVelocityX, initialVelocityY); 769 | 770 | mActivePointerId = INVALID_VALUE; 771 | endDrag(); 772 | 773 | } else if (changeStateOnTap) { 774 | int nextState = determineNextStateAfterTap(); 775 | setLayerState(nextState, true, true); 776 | } 777 | break; 778 | } 779 | 780 | case MotionEvent.ACTION_CANCEL: 781 | if (mIsDragging) { 782 | setLayerState(mCurrentState, true, true); 783 | mActivePointerId = INVALID_VALUE; 784 | endDrag(); 785 | } 786 | break; 787 | 788 | case MotionEvent.ACTION_POINTER_DOWN: { 789 | final int pointerIndex = MotionEventCompat.getActionIndex(ev); 790 | mActivePointerId = ev.getPointerId(pointerIndex); 791 | mLastX = getViewX(ev); 792 | mLastY = getViewY(ev); 793 | break; 794 | 795 | } 796 | case MotionEvent.ACTION_POINTER_UP: { 797 | onSecondaryPointerUp(ev); 798 | final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 799 | mLastX = getViewX(ev); 800 | mLastY = getViewY(ev); 801 | break; 802 | } 803 | } 804 | 805 | return true; 806 | } 807 | 808 | /** 809 | * Checks if it's allowed to slide from the given position. 810 | * 811 | * @param touchX where the touch event started 812 | * @param touchY where the touch event started. 813 | * @return true if you can drag this view, false otherwise 814 | */ 815 | private boolean touchPointIsWithinBounds(final float touchX, final float touchY) { 816 | return touchPointIsWithinBounds(touchX, touchY, true); 817 | } 818 | 819 | private boolean touchPointIsWithinBounds(final float touchX, final float touchY, boolean withinLayer) { 820 | 821 | int scroll = 0; 822 | float touch; 823 | 824 | if (allowedDirection() == HORIZONTAL) { 825 | if (withinLayer) scroll = getScrollX(); 826 | touch = touchX; 827 | } else { 828 | if (withinLayer) scroll = getScrollY(); 829 | touch = touchY; 830 | } 831 | 832 | switch (mScreenSide) { 833 | case STICK_TO_RIGHT: 834 | case STICK_TO_BOTTOM: 835 | return touch >= -scroll; 836 | case STICK_TO_LEFT: 837 | return touch <= getWidth() - scroll; 838 | case STICK_TO_TOP: 839 | return touch <= getHeight() - scroll; 840 | default: 841 | throw new IllegalStateException("The layer has to be stuck to one of the sides of the screen. " + 842 | "Current value is: " + mScreenSide); 843 | } 844 | } 845 | 846 | protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) { 847 | 848 | if (v instanceof ViewGroup) { 849 | final ViewGroup group = (ViewGroup) v; 850 | final int scrollX = v.getScrollX(); 851 | final int scrollY = v.getScrollY(); 852 | 853 | final int count = group.getChildCount(); 854 | // Count backwards - let topmost views consume scroll distance first. 855 | for (int i = count - 1; i >= 0; i--) { 856 | // TODO: Add versioned support here for transformed views. 857 | // This will not work for transformed views in Honeycomb+ 858 | final View child = group.getChildAt(i); 859 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 860 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 861 | canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), 862 | y + scrollY - child.getTop())) { 863 | return true; 864 | } 865 | } 866 | } 867 | 868 | return checkV && ( 869 | (allowedDirection() == HORIZONTAL && ViewCompat.canScrollHorizontally(v, -dx) || 870 | allowedDirection() == VERTICAL && ViewCompat.canScrollVertically(v, -dy))); 871 | } 872 | 873 | /** 874 | * Based on the position and velocity of the layer we calculate what the next state should be. 875 | * 876 | * @param velocityX 877 | * @param velocityY 878 | * @param initialX 879 | * @param initialY 880 | * @param currentX 881 | * @param currentY 882 | * @return the state of the panel (@link STATE_OPENED, STATE_CLOSED or STATE_PREVIEW). 883 | */ 884 | private int determineNextStateForDrag(final int scrollX, final int scrollY, final int velocityX, 885 | final int velocityY, final int initialX, final int initialY, 886 | final int currentX, final int currentY) { 887 | 888 | int panelOffset; 889 | int panelSize; 890 | int relativeVelocity; 891 | int absoluteDelta; 892 | 893 | if (allowedDirection() == HORIZONTAL) { 894 | panelSize = getWidth(); 895 | panelOffset = Math.abs(panelSize - Math.abs(scrollX)); 896 | absoluteDelta = Math.abs(currentX - initialX); 897 | relativeVelocity = velocityX * (mScreenSide == STICK_TO_LEFT ? 1 : -1); 898 | } else { 899 | panelSize = getHeight(); 900 | panelOffset = Math.abs(panelSize - Math.abs(scrollY)); 901 | absoluteDelta = Math.abs(currentY - initialY); 902 | relativeVelocity = velocityY * (mScreenSide == STICK_TO_TOP ? 1 : -1); 903 | } 904 | 905 | final int absoluteVelocity = Math.abs(relativeVelocity); 906 | final boolean isOverThreshold = absoluteDelta > mFlingDistance && absoluteVelocity > mMinimumVelocity; 907 | 908 | if (isOverThreshold) { 909 | 910 | if (relativeVelocity > 0) { 911 | return STATE_OPENED; 912 | } else { 913 | 914 | boolean goesToPreview = isPreviewModeEnabled() 915 | && panelOffset > mPreviewOffsetDistance 916 | && absoluteVelocity < HIGH_VELOCITY; 917 | 918 | if (goesToPreview) { 919 | return STATE_PREVIEW; 920 | } else { 921 | return STATE_CLOSED; 922 | } 923 | } 924 | 925 | } else { 926 | 927 | int openedThreshold = (panelSize + (isPreviewModeEnabled() ? mPreviewOffsetDistance : 0)) / 2; 928 | 929 | if (panelOffset > openedThreshold) { 930 | return STATE_OPENED; 931 | } else if (isPreviewModeEnabled() && panelOffset > mPreviewOffsetDistance / 2) { 932 | return STATE_PREVIEW; 933 | } else { 934 | return STATE_CLOSED; 935 | } 936 | } 937 | } 938 | 939 | /** 940 | * Based on the current state of the panel, this method returns the next state after tapping. 941 | * 942 | * @return the state of the panel (@link STATE_OPENED, STATE_CLOSED or STATE_PREVIEW). 943 | */ 944 | private int determineNextStateAfterTap() { 945 | 946 | switch (mCurrentState) { 947 | case STATE_CLOSED: 948 | return isPreviewModeEnabled() ? STATE_PREVIEW : STATE_OPENED; 949 | case STATE_PREVIEW: 950 | return STATE_OPENED; 951 | case STATE_OPENED: 952 | return isPreviewModeEnabled() ? STATE_PREVIEW : STATE_CLOSED; 953 | } 954 | 955 | return STATE_CLOSED; 956 | } 957 | 958 | /** 959 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 960 | * 961 | * @param x the number of pixels to scroll by on the X axis 962 | * @param y the number of pixels to scroll by on the Y axis 963 | */ 964 | void smoothScrollTo(int x, int y) { 965 | smoothScrollTo(x, y, 0); 966 | } 967 | 968 | /** 969 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 970 | * 971 | * @param x the number of pixels to scroll by on the X axis 972 | * @param y the number of pixels to scroll by on the Y axis 973 | * @param velocity the velocity associated with a fling, if applicable. (0 974 | * otherwise) 975 | */ 976 | void smoothScrollTo(int x, int y, int velocity) { 977 | 978 | if (getChildCount() == 0) { 979 | setDrawingCacheEnabled(false); 980 | return; 981 | } 982 | 983 | int sx = getScrollX(); 984 | int sy = getScrollY(); 985 | int dx = x - sx; 986 | int dy = y - sy; 987 | if (dx == 0 && dy == 0) { 988 | completeScroll(); 989 | if (mOnInteractListener != null) { 990 | notifyActionFinished(); 991 | } 992 | return; 993 | } 994 | 995 | setDrawingCacheEnabled(true); 996 | mScrolling = true; 997 | 998 | final int width = getWidth(); 999 | final int halfWidth = width / 2; 1000 | final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); 1001 | final float distance = halfWidth + halfWidth * distanceInfluenceForSnapDuration(distanceRatio); 1002 | 1003 | int duration; 1004 | velocity = Math.abs(velocity); 1005 | if (velocity > 0) { 1006 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 1007 | } else { 1008 | duration = MAX_SCROLLING_DURATION; 1009 | } 1010 | duration = Math.min(duration, MAX_SCROLLING_DURATION); 1011 | 1012 | mScroller.startScroll(sx, sy, dx, dy, duration); 1013 | ViewCompat.postInvalidateOnAnimation(this); 1014 | } 1015 | 1016 | private void smoothScrollToCurrentPosition() { 1017 | int[] pos = getDestScrollPosForState(mCurrentState); 1018 | smoothScrollTo(pos[0], pos[1]); 1019 | } 1020 | 1021 | // We want the duration of the page snap animation to be influenced by the 1022 | // distance that 1023 | // the screen has to travel, however, we don't want this duration to be 1024 | // effected in a 1025 | // purely linear fashion. Instead, we use this method to moderate the effect 1026 | // that the distance 1027 | // of travel has on the overall snap duration. 1028 | float distanceInfluenceForSnapDuration(float f) { 1029 | f -= 0.5f; // center the values about 0. 1030 | f *= 0.3f * Math.PI / 2.0f; 1031 | return (float) Math.sin(f); 1032 | } 1033 | 1034 | private void endDrag() { 1035 | mIsDragging = false; 1036 | mIsUnableToDrag = false; 1037 | 1038 | if (mVelocityTracker != null) { 1039 | mVelocityTracker.recycle(); 1040 | mVelocityTracker = null; 1041 | } 1042 | } 1043 | 1044 | @Override 1045 | public void setDrawingCacheEnabled(boolean enabled) { 1046 | 1047 | if (mDrawingCacheEnabled != enabled) { 1048 | super.setDrawingCacheEnabled(enabled); 1049 | mDrawingCacheEnabled = enabled; 1050 | 1051 | final int l = getChildCount(); 1052 | for (int i = 0; i < l; i++) { 1053 | final View child = getChildAt(i); 1054 | if (child.getVisibility() != GONE) { 1055 | child.setDrawingCacheEnabled(enabled); 1056 | } 1057 | } 1058 | } 1059 | } 1060 | 1061 | private void onSecondaryPointerUp(MotionEvent ev) { 1062 | final int pointerIndex = ev.getActionIndex(); 1063 | final int pointerId = ev.getPointerId(pointerIndex); 1064 | if (pointerId == mActivePointerId) { 1065 | // This was our active pointer going up. Choose a new 1066 | // active pointer and adjust accordingly. 1067 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1068 | mLastX = ev.getX(newPointerIndex); 1069 | mActivePointerId = ev.getPointerId(newPointerIndex); 1070 | if (mVelocityTracker != null) { 1071 | mVelocityTracker.clear(); 1072 | } 1073 | } 1074 | } 1075 | 1076 | private void completeScroll() { 1077 | 1078 | boolean needPopulate = mScrolling; 1079 | if (needPopulate) { 1080 | // Done with scroll, no longer want to cache view drawing. 1081 | setDrawingCacheEnabled(false); 1082 | mScroller.abortAnimation(); 1083 | int oldX = getScrollX(); 1084 | int oldY = getScrollY(); 1085 | int x = mScroller.getCurrX(); 1086 | int y = mScroller.getCurrY(); 1087 | if (oldX != x || oldY != y) { 1088 | scrollToAndNotify(x, y); 1089 | } 1090 | if (mOnInteractListener != null) { 1091 | notifyActionFinished(); 1092 | } 1093 | } 1094 | mScrolling = false; 1095 | } 1096 | 1097 | private void scrollToAndNotify(int x, int y) { 1098 | 1099 | scrollTo(x, y); 1100 | 1101 | if (mOnScrollListener == null && mLayerTransformer == null) { 1102 | return; 1103 | } 1104 | 1105 | int scroll; 1106 | if (allowedDirection() == VERTICAL) { 1107 | scroll = getHeight() - Math.abs(y); 1108 | } else { 1109 | scroll = getWidth() - Math.abs(x); 1110 | } 1111 | 1112 | if (mOnScrollListener != null) { 1113 | mOnScrollListener.onScroll(Math.abs(scroll)); 1114 | } 1115 | 1116 | if (mLayerTransformer != null) { 1117 | 1118 | int absoluteScroll = Math.abs(scroll); 1119 | int layerSize = allowedDirection() == HORIZONTAL ? getMeasuredWidth() : getMeasuredHeight(); 1120 | 1121 | float layerProgress = (float) absoluteScroll / layerSize; 1122 | float previewProgress = mPreviewOffsetDistance > 0 ? 1123 | Math.min(1, (float) absoluteScroll / mPreviewOffsetDistance) : 1124 | 0; 1125 | 1126 | mLayerTransformer.internalTransform(this, previewProgress, layerProgress, mScreenSide); 1127 | } 1128 | } 1129 | 1130 | /** 1131 | * Sets the default location where the SlidingLayer will appear 1132 | * 1133 | * @param screenSide The location where the Sliding layer will appear. Possible values are 1134 | * {@link #STICK_TO_BOTTOM}, {@link #STICK_TO_LEFT} 1135 | * {@link #STICK_TO_RIGHT}, {@link #STICK_TO_TOP} 1136 | */ 1137 | public void setStickTo(int screenSide) { 1138 | mForceLayout = true; 1139 | mScreenSide = screenSide; 1140 | setLayerState(STATE_CLOSED, false, true); 1141 | } 1142 | 1143 | private int allowedDirection() { 1144 | 1145 | if (mScreenSide == STICK_TO_TOP || mScreenSide == STICK_TO_BOTTOM) { 1146 | return VERTICAL; 1147 | } else if (mScreenSide == STICK_TO_LEFT || mScreenSide == STICK_TO_RIGHT) { 1148 | return HORIZONTAL; 1149 | } 1150 | 1151 | throw new IllegalStateException("The screen side of the layer is illegal"); 1152 | } 1153 | 1154 | /** 1155 | * Sets the behavior when tapping the sliding layer 1156 | * 1157 | * @param changeStateOnTap 1158 | */ 1159 | public void setChangeStateOnTap(boolean changeStateOnTap) { 1160 | this.changeStateOnTap = changeStateOnTap; 1161 | } 1162 | 1163 | @Override 1164 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1165 | 1166 | int width = getDefaultSize(0, widthMeasureSpec); 1167 | int height = getDefaultSize(0, heightMeasureSpec); 1168 | setMeasuredDimension(width, height); 1169 | 1170 | if (mLayerTransformer != null) { 1171 | mLayerTransformer.onMeasure(this, mScreenSide); 1172 | } 1173 | 1174 | super.onMeasure(getChildMeasureSpec(widthMeasureSpec, 0, width), 1175 | getChildMeasureSpec(heightMeasureSpec, 0, height)); 1176 | } 1177 | 1178 | @Override 1179 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1180 | super.onSizeChanged(w, h, oldw, oldh); 1181 | 1182 | boolean scrollMustChange = false; 1183 | if (allowedDirection() == VERTICAL) { 1184 | if (h != oldh) { 1185 | scrollMustChange = true; 1186 | } 1187 | } else if (w != oldw) { 1188 | scrollMustChange = true; 1189 | } 1190 | 1191 | if (scrollMustChange) { 1192 | completeScroll(); 1193 | int[] pos = getDestScrollPosForState(mCurrentState); 1194 | scrollTo(pos[0], pos[1]); 1195 | } 1196 | } 1197 | 1198 | @Override 1199 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1200 | 1201 | if (mForceLayout) { 1202 | mForceLayout = false; 1203 | adjustLayoutParams(); 1204 | 1205 | if (mScreenSide == STICK_TO_RIGHT) { 1206 | setPadding(getPaddingLeft() + mShadowSize, getPaddingTop(), getPaddingRight(), getPaddingBottom()); 1207 | } else if (mScreenSide == STICK_TO_BOTTOM) { 1208 | setPadding(getPaddingLeft(), getPaddingTop() + mShadowSize, getPaddingRight(), getPaddingBottom()); 1209 | } else if (mScreenSide == STICK_TO_LEFT) { 1210 | setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight() + mShadowSize, getPaddingBottom()); 1211 | } else if (mScreenSide == STICK_TO_TOP) { 1212 | setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + mShadowSize); 1213 | } 1214 | } 1215 | 1216 | super.onLayout(changed, left, top, right, bottom); 1217 | } 1218 | 1219 | @Override 1220 | protected void onDraw(Canvas canvas) { 1221 | super.onDraw(canvas); 1222 | } 1223 | 1224 | private void adjustLayoutParams() { 1225 | 1226 | ViewGroup.LayoutParams baseParams = getLayoutParams(); 1227 | 1228 | if (baseParams instanceof LayoutParams) { 1229 | 1230 | LayoutParams layoutParams = (LayoutParams) baseParams; 1231 | 1232 | switch (mScreenSide) { 1233 | case STICK_TO_BOTTOM: 1234 | layoutParams.gravity = Gravity.BOTTOM; 1235 | break; 1236 | case STICK_TO_LEFT: 1237 | layoutParams.gravity = Gravity.LEFT; 1238 | break; 1239 | case STICK_TO_RIGHT: 1240 | layoutParams.gravity = Gravity.RIGHT; 1241 | break; 1242 | case STICK_TO_TOP: 1243 | layoutParams.gravity = Gravity.TOP; 1244 | break; 1245 | } 1246 | setLayoutParams(baseParams); 1247 | 1248 | } else if (baseParams instanceof RelativeLayout.LayoutParams) { 1249 | 1250 | RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) baseParams; 1251 | 1252 | switch (mScreenSide) { 1253 | case STICK_TO_BOTTOM: 1254 | layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 1255 | break; 1256 | case STICK_TO_LEFT: 1257 | layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); 1258 | break; 1259 | case STICK_TO_RIGHT: 1260 | layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1261 | break; 1262 | case STICK_TO_TOP: 1263 | layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); 1264 | break; 1265 | } 1266 | } 1267 | 1268 | } 1269 | 1270 | /** 1271 | * Get the destination position based on the velocity 1272 | * 1273 | * @return 1274 | * @since 1.0 1275 | */ 1276 | private int[] getDestScrollPosForState(int state) { 1277 | 1278 | int[] pos = new int[2]; 1279 | 1280 | if (state == STATE_OPENED) { 1281 | return pos; 1282 | } else { 1283 | 1284 | int layerOffset = state == STATE_CLOSED ? mOffsetDistance : mPreviewOffsetDistance; 1285 | 1286 | switch (mScreenSide) { 1287 | case STICK_TO_RIGHT: 1288 | pos[0] = -getWidth() + layerOffset; 1289 | break; 1290 | case STICK_TO_LEFT: 1291 | pos[0] = getWidth() - layerOffset; 1292 | break; 1293 | case STICK_TO_TOP: 1294 | pos[1] = getHeight() - layerOffset; 1295 | break; 1296 | case STICK_TO_BOTTOM: 1297 | pos[1] = -getHeight() + layerOffset; 1298 | break; 1299 | } 1300 | 1301 | return pos; 1302 | } 1303 | } 1304 | 1305 | public int getContentLeft() { 1306 | return getLeft() + getPaddingLeft(); 1307 | } 1308 | 1309 | @Override 1310 | protected void dispatchDraw(Canvas canvas) { 1311 | super.dispatchDraw(canvas); 1312 | // Draw the margin drawable if needed. 1313 | if (mShadowSize > 0 && mShadowDrawable != null) { 1314 | if (mScreenSide == STICK_TO_RIGHT) { 1315 | mShadowDrawable.setBounds(0, 0, mShadowSize, getHeight()); 1316 | } 1317 | if (mScreenSide == STICK_TO_TOP) { 1318 | mShadowDrawable.setBounds(0, getHeight() - mShadowSize, getWidth(), getHeight()); 1319 | } 1320 | if (mScreenSide == STICK_TO_LEFT) { 1321 | mShadowDrawable.setBounds(getWidth() - mShadowSize, 0, getWidth(), getHeight()); 1322 | } 1323 | if (mScreenSide == STICK_TO_BOTTOM) { 1324 | mShadowDrawable.setBounds(0, 0, getWidth(), mShadowSize); 1325 | } 1326 | mShadowDrawable.draw(canvas); 1327 | } 1328 | } 1329 | 1330 | @Override 1331 | public void computeScroll() { 1332 | if (!mScroller.isFinished()) { 1333 | if (mScroller.computeScrollOffset()) { 1334 | final int oldX = getScrollX(); 1335 | final int oldY = getScrollY(); 1336 | final int x = mScroller.getCurrX(); 1337 | final int y = mScroller.getCurrY(); 1338 | 1339 | if (oldX != x || oldY != y) { 1340 | scrollToAndNotify(x, y); 1341 | } 1342 | 1343 | // Keep on drawing until the animation has finished. 1344 | ViewCompat.postInvalidateOnAnimation(this); 1345 | return; 1346 | } 1347 | } 1348 | 1349 | // Done with scroll, clean up state. 1350 | completeScroll(); 1351 | } 1352 | 1353 | /** 1354 | * Handler interface for obtaining updates on the SlidingLayer's state. 1355 | * OnInteractListener allows for external classes to be notified when the SlidingLayer 1356 | * receives input to be opened or closed. 1357 | */ 1358 | public interface OnInteractListener { 1359 | 1360 | /** 1361 | * This method is called when an attempt is made to open the current SlidingLayer. Note 1362 | * that because of animation, the SlidingLayer may not be visible yet. 1363 | */ 1364 | void onOpen(); 1365 | 1366 | /** 1367 | * This method is called when an attempt is made to show the preview mode in the current 1368 | * SlidingLayer. Note that because of animation, the SlidingLayer may not be 1369 | * visible yet. 1370 | */ 1371 | void onShowPreview(); 1372 | 1373 | /** 1374 | * This method is called when an attempt is made to close the current SlidingLayer. Note 1375 | * that because of animation, the SlidingLayer may still be visible. 1376 | */ 1377 | void onClose(); 1378 | 1379 | /** 1380 | * this method is executed after onOpen(), when the animation has finished. 1381 | */ 1382 | void onOpened(); 1383 | 1384 | /** 1385 | * this method is executed after onShowPreview(), when the animation has finished. 1386 | */ 1387 | void onPreviewShowed(); 1388 | 1389 | /** 1390 | * this method is executed after onClose(), when the animation has finished and the 1391 | * SlidingLayer is 1392 | * therefore no longer visible. 1393 | */ 1394 | void onClosed(); 1395 | } 1396 | 1397 | private void notifyActionStartedForState(int state) { 1398 | 1399 | switch (state) { 1400 | case STATE_CLOSED: 1401 | mOnInteractListener.onClose(); 1402 | break; 1403 | 1404 | case STATE_PREVIEW: 1405 | mOnInteractListener.onShowPreview(); 1406 | break; 1407 | 1408 | case STATE_OPENED: 1409 | mOnInteractListener.onOpen(); 1410 | break; 1411 | } 1412 | } 1413 | 1414 | private void notifyActionFinished() { 1415 | 1416 | switch (mCurrentState) { 1417 | case STATE_CLOSED: 1418 | mOnInteractListener.onClosed(); 1419 | break; 1420 | 1421 | case STATE_PREVIEW: 1422 | mOnInteractListener.onPreviewShowed(); 1423 | break; 1424 | 1425 | case STATE_OPENED: 1426 | mOnInteractListener.onOpened(); 1427 | break; 1428 | } 1429 | } 1430 | 1431 | /** 1432 | * Interface definition for a callback to be invoked when the layer has been scrolled. 1433 | */ 1434 | public interface OnScrollListener { 1435 | 1436 | /** 1437 | * Callback method to be invoked when the layer has been scrolled. This will be 1438 | * called after the scroll has completed 1439 | * 1440 | * @param absoluteScroll The absolute scrolling delta relative to the position of the container 1441 | */ 1442 | void onScroll(int absoluteScroll); 1443 | } 1444 | 1445 | static class SavedState extends BaseSavedState { 1446 | 1447 | Bundle mState; 1448 | 1449 | public SavedState(Parcelable superState) { 1450 | super(superState); 1451 | } 1452 | 1453 | public SavedState(Parcel in) { 1454 | super(in); 1455 | mState = in.readBundle(); 1456 | } 1457 | 1458 | @Override 1459 | public void writeToParcel(Parcel dest, int flags) { 1460 | super.writeToParcel(dest, flags); 1461 | dest.writeBundle(mState); 1462 | } 1463 | 1464 | public static final Creator CREATOR = new Creator() { 1465 | @Override 1466 | public SavedState createFromParcel(Parcel in) { 1467 | return new SavedState(in); 1468 | } 1469 | 1470 | @Override 1471 | public SavedState[] newArray(int size) { 1472 | return new SavedState[size]; 1473 | } 1474 | }; 1475 | } 1476 | } -------------------------------------------------------------------------------- /Library/src/main/java/com/wunderlist/slidinglayer/transformer/AlphaTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SlidingLayer.java 3 | * 4 | * Copyright (C) 2015 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @version 1.2.0 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | package com.wunderlist.slidinglayer.transformer; 25 | 26 | import android.view.View; 27 | 28 | import com.wunderlist.slidinglayer.LayerTransformer; 29 | 30 | /** 31 | * Created by joseluisugia on 16/03/15. 32 | */ 33 | public final class AlphaTransformer extends LayerTransformer { 34 | 35 | private static final int DEFAULT_MULTIPLIER = 1; 36 | 37 | private final float mMultiplier; 38 | 39 | public AlphaTransformer() { 40 | this(DEFAULT_MULTIPLIER); 41 | } 42 | 43 | public AlphaTransformer(float multiplier) { 44 | mMultiplier = multiplier; 45 | } 46 | 47 | @Override 48 | public void transform(View layerView, float previewProgress, float layerProgress) { 49 | 50 | final float progressRatioToAnimate = Math.max(previewProgress, layerProgress); 51 | final float alpha = Math.max(0, Math.min(1, progressRatioToAnimate * mMultiplier)); 52 | layerView.setAlpha(alpha); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Library/src/main/java/com/wunderlist/slidinglayer/transformer/RotationTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SlidingLayer.java 3 | * 4 | * Copyright (C) 2015 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @version 1.2.0 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | package com.wunderlist.slidinglayer.transformer; 25 | 26 | import android.view.View; 27 | 28 | import com.wunderlist.slidinglayer.LayerTransformer; 29 | import com.wunderlist.slidinglayer.SlidingLayer; 30 | 31 | /** 32 | * Created by joseluisugia on 16/03/15. 33 | */ 34 | public final class RotationTransformer extends LayerTransformer { 35 | 36 | private static final int DEFAULT_ANGLE = 10; 37 | 38 | private final float mMaxAngle; 39 | private float mAngle; 40 | 41 | public RotationTransformer() { 42 | this(DEFAULT_ANGLE); 43 | } 44 | 45 | public RotationTransformer(float maxAngle) { 46 | mMaxAngle = maxAngle; 47 | } 48 | 49 | @Override 50 | protected void onMeasure(View layerView, int screenSide) { 51 | 52 | final int[] pivotPosition = pivotPositionForScreenSide(layerView, screenSide); 53 | layerView.setPivotX(pivotPosition[0]); 54 | layerView.setPivotY(pivotPosition[1]); 55 | 56 | mAngle = mMaxAngle * 57 | (screenSide == SlidingLayer.STICK_TO_LEFT || screenSide == SlidingLayer.STICK_TO_TOP ? -1 : 1); 58 | } 59 | 60 | @Override 61 | public void transform(View layerView, float previewProgress, float layerProgress) { 62 | } 63 | 64 | @Override 65 | protected void internalTransform(View layerView, float previewProgress, float layerProgress, int screenSide) { 66 | 67 | final float progressRatioToAnimate = Math.max(previewProgress, layerProgress); 68 | layerView.setRotation(mAngle * (1 - progressRatioToAnimate)); 69 | } 70 | 71 | private int[] pivotPositionForScreenSide(View layerView, int screenSide) { 72 | 73 | switch (screenSide) { 74 | 75 | case SlidingLayer.STICK_TO_LEFT: 76 | return new int[] { 0, layerView.getMeasuredHeight() }; 77 | 78 | case SlidingLayer.STICK_TO_TOP: 79 | return new int[] { 0, 0 }; 80 | 81 | case SlidingLayer.STICK_TO_RIGHT: 82 | return new int[] { layerView.getMeasuredWidth(), layerView.getMeasuredHeight() }; 83 | 84 | case SlidingLayer.STICK_TO_BOTTOM: 85 | return new int[] { 0, layerView.getMeasuredHeight() }; 86 | 87 | default: 88 | return new int[] { 0, 0 }; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Library/src/main/java/com/wunderlist/slidinglayer/transformer/SlideJoyTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SlidingLayer.java 3 | * 4 | * Copyright (C) 2015 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @version 1.2.0 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | package com.wunderlist.slidinglayer.transformer; 25 | 26 | import android.view.View; 27 | 28 | import com.wunderlist.slidinglayer.LayerTransformer; 29 | import com.wunderlist.slidinglayer.SlidingLayer; 30 | import com.wunderlist.slidinglayer.utils.Transitions; 31 | 32 | /** 33 | * Created by joseluisugia on 16/03/15. 34 | */ 35 | public final class SlideJoyTransformer extends LayerTransformer { 36 | 37 | private final float[] mCuePoints = new float[] { 0.7f, 0.9f, 1 }; 38 | 39 | private float[] mRotationXValues; 40 | private float[] mRotationYValues; 41 | 42 | @Override 43 | protected void onMeasure(View layerView, int screenSide) { 44 | 45 | // Rotation 46 | float[] rotationXY = rotationValueForScreenSide(-4.75f, screenSide); 47 | 48 | mRotationXValues = new float[] { 0, rotationXY[0], 0 }; 49 | mRotationYValues = new float[] { 0, rotationXY[1], 0 }; 50 | 51 | // Pivot 52 | int[] pivotPosition = pivotPositionForScreenSide(layerView, screenSide); 53 | layerView.setPivotX(pivotPosition[0]); 54 | layerView.setPivotY(pivotPosition[1]); 55 | } 56 | 57 | @Override 58 | public void transform(View layerView, float previewProgress, float layerProgress) { 59 | } 60 | 61 | @Override 62 | protected void internalTransform(View layerView, float previewProgress, float layerProgress, int screenSide) { 63 | 64 | float progressRatioToAnimate = Math.max(previewProgress, layerProgress); 65 | 66 | // Scale 67 | float scaleValue = Transitions.intermediateValueForCuePoints(progressRatioToAnimate, 68 | new float[] { 0.9f, 1 }); 69 | layerView.setScaleX(scaleValue); 70 | layerView.setScaleY(scaleValue); 71 | 72 | // Rotation 73 | float rotationX, rotationY; 74 | 75 | rotationX = Transitions.intermediateValueForRange(progressRatioToAnimate, mCuePoints, mRotationXValues); 76 | layerView.setRotationX(rotationX); 77 | 78 | rotationY = Transitions.intermediateValueForRange(progressRatioToAnimate, mCuePoints, mRotationYValues); 79 | layerView.setRotationY(rotationY); 80 | } 81 | 82 | private float[] rotationValueForScreenSide(float value, int screenSide) { 83 | 84 | switch (screenSide) { 85 | 86 | case SlidingLayer.STICK_TO_LEFT: 87 | return new float[] { 0, value }; 88 | 89 | case SlidingLayer.STICK_TO_TOP: 90 | return new float[] { -value, 0 }; 91 | 92 | case SlidingLayer.STICK_TO_RIGHT: 93 | return new float[] { 0, -value }; 94 | 95 | case SlidingLayer.STICK_TO_BOTTOM: 96 | return new float[] { value, 0 }; 97 | 98 | default: 99 | return new float[] { 0, 0 }; 100 | } 101 | } 102 | 103 | private int[] pivotPositionForScreenSide(View layerView, int screenSide) { 104 | 105 | switch (screenSide) { 106 | 107 | case SlidingLayer.STICK_TO_LEFT: 108 | return new int[] { 0, layerView.getMeasuredHeight() / 2 }; 109 | 110 | case SlidingLayer.STICK_TO_TOP: 111 | return new int[] { layerView.getMeasuredWidth() / 2, 0 }; 112 | 113 | case SlidingLayer.STICK_TO_RIGHT: 114 | return new int[] { layerView.getMeasuredWidth(), layerView.getMeasuredHeight() / 2 }; 115 | 116 | case SlidingLayer.STICK_TO_BOTTOM: 117 | return new int[] { layerView.getMeasuredWidth() / 2, layerView.getMeasuredHeight() }; 118 | 119 | default: 120 | return new int[] { 0, 0 }; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Library/src/main/java/com/wunderlist/slidinglayer/utils/Transitions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SlidingLayer.java 3 | * 4 | * Copyright (C) 2015 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @version 1.2.0 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | package com.wunderlist.slidinglayer.utils; 25 | 26 | /** 27 | * Created by joseluisugia on 17/03/15. 28 | */ 29 | public class Transitions { 30 | 31 | 32 | public static float intermediateValueForRange(float position, float[] values) { 33 | return intermediateValueForRange(position, new float[] { 0, 1 }, values); 34 | } 35 | 36 | public static float intermediateValueForCuePoints(float position, float[] range) { 37 | return intermediateValueForRange(position, range, range); 38 | } 39 | 40 | public static float intermediateValueForRange(float position, float[] cuePoints, float[] values) { 41 | 42 | if (cuePoints.length != values.length) { 43 | throw new IllegalArgumentException("Range and values arrays must be of the same size"); 44 | } 45 | 46 | int length = cuePoints.length; 47 | 48 | if (position <= cuePoints[0]) { 49 | return values[0]; 50 | } else { 51 | 52 | float cuePoint, previousCuePoint, value, previousValue; 53 | float rangeRatio; 54 | 55 | for (int i = 1; i < length; i++) { 56 | 57 | cuePoint = cuePoints[i]; 58 | 59 | if (position <= cuePoint) { 60 | previousCuePoint = cuePoints[i - 1]; 61 | value = values[i]; 62 | previousValue = values[i - 1]; 63 | rangeRatio = (position - previousCuePoint) / (cuePoint - previousCuePoint); 64 | return previousValue + ((value - previousValue) * rangeRatio); 65 | } 66 | } 67 | 68 | return values[length - 1]; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Library/src/main/res/values/slidinglayer-attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6Wunderkinder SlidingLayer for Android 2 | ============================= 3 | This repository hosts a library that provides an easy way to include an autonomous layer/view that slides from any side of your screen and which is fully gesture ready, the same way as our detail view in Wunderlist 2 does. 4 | This pattern can also be seen in Google+’s notification center, Google Maps, Google Music, Contacts app in Lollipop, Basecamp’s detail view, among others. 5 | 6 | If you want to see how it works you can have a look to our [video](http://www.youtube.com/watch?v=162oD0XPM40) or directly download from [Google Play](https://play.google.com/store/apps/details?id=com.slidinglayersample) it to test in on your device. 7 | 8 | 9 | Implementation setup 10 | ------------------------------ 11 | As easy as to draw a green droid yourself, just grab it in your build gradle: 12 | 13 | ```gradle 14 | compile 'com.wunderlist:sliding-layer:1.2.5' 15 | ``` 16 | 17 | or directly add it as a submodule inside of your project. 18 | 19 | 20 | Integration 21 | --------------- 22 | Due to simplicity and lightness, this container is currently based on a FrameLayout. Just treat it as you would with other container: Place it in any of your XML layout files or drag it from the Custom Components panel. Additionally you can add this view programmatically. 23 | In the following example the same layout will be added by using the two mentioned ways. 24 | 25 | 26 | XML 27 | ----- 28 | ```xml 29 | 40 | 41 | … 42 | … 43 | 44 | ``` 45 | 46 | Properties: 47 | * `shadowDrawable` - a reference to the resource drawable used to paint the shadow of the container 48 | * `shadowSize` - a reference to the dimension of the desired size of the given shadow 49 | * `offsetDistance` - a reference to the dimension of the desired size for the layer to offset in the screen in order for it to be directly swipable to open 50 | * `previewOffsetDistance` - a reference to the dimension of the desired size of the preview mode. When opening the layer in this state, it will only show this amount of the layer. Preview mode can be open by tapping the layer, dragging or programmatically by calling `openPreview(boolean smoothAnimation)`. 51 | * `stickTo` - an enum that determines to where the container should stick to. ‘left’ sticks the container to the left side of the screen. ‘right’ sticks the container to the right side of the screen, and so on with ‘top‘ and ‘bottom‘ states. Default is ‘right’. 52 | * `changeStateOnTap` - a boolean that enables/disables the action to change the state of the layer -open, preview or close- by tapping on an empty space of the container. Default value is true. 53 | 54 | 55 | Java 56 | ----- 57 | ```java 58 | public class SlidingLayerExampleActivity extends Activity { 59 | 60 | @Override 61 | public void onCreate(Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.main_view); 64 | 65 | SlidingLayer slidingLayer = (SlidingLayer) findViewById(R.id.slidingLayer1); 66 | 67 | slidingLayer.setShadowDrawable(R.drawable.sidebar_shadow); 68 | slidingLayer.setShadowSizeRes(R.dimen.shadow_size); 69 | slidingLayer.setOffsetDistanceRes(R.dimen.offset_distance); 70 | slidingLayer.setPreviewOffsetDistanceRes(R.dimen.preview_offset_distance); 71 | slidingLayer.setStickTo(SlidingLayer.STICK_TO_LEFT); 72 | slidingLayer.setChangeStateOnTap(false); 73 | 74 | slidingLayer.addView(new Button(this)); 75 | ... 76 | } 77 | } 78 | ``` 79 | Code of Conduct 80 | ------------------- 81 | 82 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 83 | -------------------------------------------------------------------------------- /SlidingLayerSample/art/device-2015-09-13-131637.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/art/device-2015-09-13-131637.png -------------------------------------------------------------------------------- /SlidingLayerSample/art/device-2015-09-13-131714.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/art/device-2015-09-13-131714.png -------------------------------------------------------------------------------- /SlidingLayerSample/art/device-2015-09-13-131735.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/art/device-2015-09-13-131735.png -------------------------------------------------------------------------------- /SlidingLayerSample/art/device-2015-09-13-131825.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/art/device-2015-09-13-131825.png -------------------------------------------------------------------------------- /SlidingLayerSample/art/device-2015-09-13-131907.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/art/device-2015-09-13-131907.png -------------------------------------------------------------------------------- /SlidingLayerSample/art/device-2015-09-13-131931.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/art/device-2015-09-13-131931.png -------------------------------------------------------------------------------- /SlidingLayerSample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_1_7 6 | targetCompatibility JavaVersion.VERSION_1_7 7 | } 8 | compileSdkVersion 23 9 | buildToolsVersion "23.0.1" 10 | defaultConfig { 11 | minSdkVersion 8 12 | targetSdkVersion 23 13 | versionCode 3 14 | versionName "2.0.0" 15 | } 16 | } 17 | 18 | dependencies { 19 | compile project(':Library') 20 | compile 'com.android.support:appcompat-v7:23.0.1' 21 | } 22 | -------------------------------------------------------------------------------- /SlidingLayerSample/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/ic_launcher-web.png -------------------------------------------------------------------------------- /SlidingLayerSample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 4.0.0 23 | 24 | SlidingLayerSample 25 | SlidingLayer (Sample) 26 | apk 27 | 28 | 29 | com.slidinglayer 30 | parent 31 | 1.0 32 | ../pom.xml 33 | 34 | 35 | 36 | 37 | android 38 | android 39 | ${android.version} 40 | provided 41 | 42 | 43 | android.support 44 | compatibility-v4 45 | ${android-support.version} 46 | compile 47 | 48 | 49 | com.slidinglayer 50 | Library 51 | ${project.version} 52 | apklib 53 | compile 54 | 55 | 56 | 57 | 58 | src 59 | 60 | 61 | 62 | com.jayway.maven.plugins.android.generation2 63 | android-maven-plugin 64 | true 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 25 | 26 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/java/com/slidinglayersample/InitSelectionActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * InitSelectionActivity.java 3 | * 4 | * Copyright (C) 2013 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @version 1.0 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | package com.slidinglayersample; 24 | 25 | import android.content.Intent; 26 | import android.os.Bundle; 27 | import android.preference.ListPreference; 28 | import android.preference.Preference; 29 | import android.preference.Preference.OnPreferenceChangeListener; 30 | import android.preference.Preference.OnPreferenceClickListener; 31 | import android.preference.PreferenceActivity; 32 | import android.preference.PreferenceManager; 33 | import android.support.v7.widget.Toolbar; 34 | 35 | public class InitSelectionActivity extends PreferenceActivity { 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_settings); 41 | initToolbar(); 42 | } 43 | 44 | @Override 45 | protected void onPostCreate(Bundle savedInstanceState) { 46 | super.onPostCreate(savedInstanceState); 47 | 48 | init(); 49 | } 50 | 51 | private void initToolbar() { 52 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 53 | toolbar.setTitle(R.string.app_name); 54 | } 55 | 56 | /** 57 | * Initialization 58 | */ 59 | @SuppressWarnings("deprecation") 60 | private void init() { 61 | 62 | addPreferencesFromResource(R.xml.pref_general_activity); 63 | 64 | final Preference goPreference = findPreference("pref_go"); 65 | if (goPreference != null) { 66 | goPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { 67 | @Override 68 | public boolean onPreferenceClick(Preference preference) { 69 | startActivity(new Intent(getBaseContext(), MainActivity.class)); 70 | return false; 71 | } 72 | }); 73 | } 74 | 75 | final ListPreference posPreference = (ListPreference) findPreference("layer_location"); 76 | if (posPreference != null) { 77 | 78 | setPreferenceSummary(posPreference, 79 | PreferenceManager.getDefaultSharedPreferences(this).getString("layer_location", "right")); 80 | 81 | posPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 82 | 83 | @Override 84 | public boolean onPreferenceChange(Preference preference, Object newValue) { 85 | 86 | setPreferenceSummary(posPreference, (String) newValue); 87 | return true; 88 | } 89 | }); 90 | } 91 | 92 | final ListPreference transformPreference = (ListPreference) findPreference("layer_transform"); 93 | if (transformPreference != null) { 94 | 95 | setTransformPreferenceSummary(transformPreference, 96 | PreferenceManager.getDefaultSharedPreferences(this).getString("layer_transform", "none")); 97 | 98 | transformPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 99 | 100 | @Override 101 | public boolean onPreferenceChange(Preference preference, Object newValue) { 102 | 103 | setTransformPreferenceSummary(transformPreference, (String) newValue); 104 | return true; 105 | } 106 | }); 107 | } 108 | } 109 | 110 | private void setPreferenceSummary(Preference _preference, String _locationString) { 111 | 112 | switch (_locationString) { 113 | case "right": 114 | _preference.setSummary(getResources().getString(R.string.label_right)); 115 | break; 116 | case "left": 117 | _preference.setSummary(getResources().getString(R.string.label_left)); 118 | break; 119 | case "top": 120 | _preference.setSummary(getResources().getString(R.string.label_top)); 121 | break; 122 | case "bottom": 123 | _preference.setSummary(getResources().getString(R.string.label_bottom)); 124 | break; 125 | } 126 | } 127 | 128 | private void setTransformPreferenceSummary(Preference _preference, String _transformString) { 129 | 130 | switch (_transformString) { 131 | case "none": 132 | _preference.setSummary(getResources().getString(R.string.label_none)); 133 | break; 134 | case "alpha": 135 | _preference.setSummary(getResources().getString(R.string.label_alpha)); 136 | break; 137 | case "rotation": 138 | _preference.setSummary(getResources().getString(R.string.label_rotation)); 139 | break; 140 | case "slide": 141 | _preference.setSummary(getResources().getString(R.string.label_slide_joy)); 142 | break; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/java/com/slidinglayersample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MainActivity.java 3 | * 4 | * Copyright (C) 2013 6 Wunderkinder GmbH. 5 | * 6 | * @author Jose L Ugia - @Jl_Ugia 7 | * @author Antonio Consuegra - @aconsuegra 8 | * @author Cesar Valiente - @CesarValiente 9 | * @author Benedikt Lehnert - @blehnert 10 | * @author Timothy Achumba - @iam_timm 11 | * @version 1.0 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); 14 | * you may not use this file except in compliance with the License. 15 | * You may obtain a copy of the License at 16 | * 17 | * http://www.apache.org/licenses/LICENSE-2.0 18 | * 19 | * Unless required by applicable law or agreed to in writing, software 20 | * distributed under the License is distributed on an "AS IS" BASIS, 21 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | * See the License for the specific language governing permissions and 23 | * limitations under the License. 24 | */ 25 | 26 | package com.slidinglayersample; 27 | 28 | import android.content.SharedPreferences; 29 | import android.graphics.drawable.Drawable; 30 | import android.os.Bundle; 31 | import android.preference.PreferenceManager; 32 | import android.support.v7.app.AppCompatActivity; 33 | import android.support.v7.widget.Toolbar; 34 | import android.view.KeyEvent; 35 | import android.view.MenuItem; 36 | import android.view.View; 37 | import android.widget.RelativeLayout.LayoutParams; 38 | import android.widget.TextView; 39 | 40 | import com.wunderlist.slidinglayer.LayerTransformer; 41 | import com.wunderlist.slidinglayer.SlidingLayer; 42 | import com.wunderlist.slidinglayer.transformer.AlphaTransformer; 43 | import com.wunderlist.slidinglayer.transformer.RotationTransformer; 44 | import com.wunderlist.slidinglayer.transformer.SlideJoyTransformer; 45 | 46 | public class MainActivity extends AppCompatActivity { 47 | 48 | private SlidingLayer mSlidingLayer; 49 | private TextView swipeText; 50 | 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | setContentView(R.layout.activity_main); 55 | 56 | bindViews(); 57 | initToolbar(); 58 | initState(); 59 | } 60 | 61 | /** 62 | * View binding 63 | */ 64 | private void bindViews() { 65 | mSlidingLayer = (SlidingLayer) findViewById(R.id.slidingLayer1); 66 | swipeText = (TextView) findViewById(R.id.swipeText); 67 | } 68 | 69 | /** 70 | * Initializes the origin state of the layer 71 | */ 72 | private void initState() { 73 | 74 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 75 | 76 | setupSlidingLayerPosition(prefs.getString("layer_location", "right")); 77 | setupSlidingLayerTransform(prefs.getString("layer_transform", "none")); 78 | 79 | setupShadow(prefs.getBoolean("layer_has_shadow", false)); 80 | setupLayerOffset(prefs.getBoolean("layer_has_offset", false)); 81 | setupPreviewMode(prefs.getBoolean("preview_mode_enabled", false)); 82 | } 83 | 84 | private void initToolbar() { 85 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 86 | setSupportActionBar(toolbar); 87 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 88 | } 89 | 90 | private void setupSlidingLayerPosition(String layerPosition) { 91 | 92 | LayoutParams rlp = (LayoutParams) mSlidingLayer.getLayoutParams(); 93 | int textResource; 94 | Drawable d; 95 | 96 | switch (layerPosition) { 97 | case "right": 98 | textResource = R.string.swipe_right_label; 99 | d = getResources().getDrawable(R.drawable.container_rocket_right); 100 | 101 | mSlidingLayer.setStickTo(SlidingLayer.STICK_TO_RIGHT); 102 | break; 103 | case "left": 104 | textResource = R.string.swipe_left_label; 105 | d = getResources().getDrawable(R.drawable.container_rocket_left); 106 | 107 | mSlidingLayer.setStickTo(SlidingLayer.STICK_TO_LEFT); 108 | break; 109 | case "top": 110 | textResource = R.string.swipe_up_label; 111 | d = getResources().getDrawable(R.drawable.container_rocket); 112 | 113 | mSlidingLayer.setStickTo(SlidingLayer.STICK_TO_TOP); 114 | rlp.width = LayoutParams.MATCH_PARENT; 115 | rlp.height = getResources().getDimensionPixelSize(R.dimen.layer_size); 116 | break; 117 | default: 118 | textResource = R.string.swipe_down_label; 119 | d = getResources().getDrawable(R.drawable.container_rocket); 120 | 121 | mSlidingLayer.setStickTo(SlidingLayer.STICK_TO_BOTTOM); 122 | rlp.width = LayoutParams.MATCH_PARENT; 123 | rlp.height = getResources().getDimensionPixelSize(R.dimen.layer_size); 124 | } 125 | 126 | d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 127 | swipeText.setCompoundDrawables(null, d, null, null); 128 | swipeText.setText(getResources().getString(textResource)); 129 | mSlidingLayer.setLayoutParams(rlp); 130 | } 131 | 132 | private void setupSlidingLayerTransform(String layerTransform) { 133 | 134 | LayerTransformer transformer; 135 | 136 | switch (layerTransform) { 137 | case "alpha": 138 | transformer = new AlphaTransformer(); 139 | break; 140 | case "rotation": 141 | transformer = new RotationTransformer(); 142 | break; 143 | case "slide": 144 | transformer = new SlideJoyTransformer(); 145 | break; 146 | default: 147 | return; 148 | } 149 | mSlidingLayer.setLayerTransformer(transformer); 150 | } 151 | 152 | private void setupShadow(boolean enabled) { 153 | if (enabled) { 154 | mSlidingLayer.setShadowSizeRes(R.dimen.shadow_size); 155 | mSlidingLayer.setShadowDrawable(R.drawable.sidebar_shadow); 156 | } else { 157 | mSlidingLayer.setShadowSize(0); 158 | mSlidingLayer.setShadowDrawable(null); 159 | } 160 | } 161 | 162 | private void setupLayerOffset(boolean enabled) { 163 | int offsetDistance = enabled ? getResources().getDimensionPixelOffset(R.dimen.offset_distance) : 0; 164 | mSlidingLayer.setOffsetDistance(offsetDistance); 165 | } 166 | 167 | private void setupPreviewMode(boolean enabled) { 168 | int previewOffset = enabled ? getResources().getDimensionPixelOffset(R.dimen.preview_offset_distance) : -1; 169 | mSlidingLayer.setPreviewOffsetDistance(previewOffset); 170 | } 171 | 172 | public void buttonClicked(View v) { 173 | switch (v.getId()) { 174 | case R.id.buttonOpen: 175 | mSlidingLayer.openLayer(true); 176 | break; 177 | case R.id.buttonClose: 178 | mSlidingLayer.closeLayer(true); 179 | break; 180 | } 181 | } 182 | 183 | @Override 184 | public boolean onKeyDown(int keyCode, KeyEvent event) { 185 | switch (keyCode) { 186 | case KeyEvent.KEYCODE_BACK: 187 | if (mSlidingLayer.isOpened()) { 188 | mSlidingLayer.closeLayer(true); 189 | return true; 190 | } 191 | 192 | default: 193 | return super.onKeyDown(keyCode, event); 194 | } 195 | } 196 | 197 | @Override 198 | public boolean onOptionsItemSelected(MenuItem item) { 199 | finish(); 200 | return true; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/button_red.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/button_red.9.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/button_red_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/button_red_pressed.9.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/container_rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/container_rocket.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/container_rocket_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/container_rocket_left.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/container_rocket_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/container_rocket_right.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable-xhdpi/main_screen_rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftarchive/android-sliding-layer-lib/1c6be9a04a41f56f8bf0fa6f69bde29d9641653f/SlidingLayerSample/src/main/res/drawable-xhdpi/main_screen_rocket.png -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable/button_red_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/drawable/sidebar_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /SlidingLayerSample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 31 | 34 | 35 | 43 | 44 |