├── .gitignore
├── LICENSE.txt
├── README.md
├── build.gradle
├── demo
├── AndroidManifest.xml
├── build.gradle
├── ic_launcher-web.png
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ ├── graphic.png
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── activity_demo.xml
│ ├── menu
│ │ └── demo.xml
│ └── values
│ │ ├── strings.xml
│ │ └── styles.xml
├── settings.gradle
└── src
│ └── com
│ └── sothree
│ └── slidinguppanel
│ └── demo
│ └── DemoActivity.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── libs
│ ├── android-support-v4.jar
│ └── nineoldandroids-2.4.0.jar
├── project.properties
├── res
│ ├── drawable
│ │ ├── above_shadow.xml
│ │ └── below_shadow.xml
│ └── values
│ │ └── attrs.xml
└── src
│ └── com
│ └── sothree
│ └── slidinguppanel
│ ├── SlidingUpPanelLayout.java
│ └── ViewDragHelper.java
├── maven_push.gradle
├── settings.gradle
└── slidinguppanel.png
/.gitignore:
--------------------------------------------------------------------------------
1 | #Android generated
2 | bin
3 | gen
4 | gen*
5 |
6 | #Eclipse
7 | .project
8 | .classpath
9 | .settings
10 |
11 | #IntelliJ IDEA
12 | .idea
13 | *.iml
14 | *.ipr
15 | *.iws
16 | out
17 |
18 | #Maven
19 | target
20 | release.properties
21 | pom.xml.*
22 |
23 | #Ant
24 | build.xml
25 | local.properties
26 | proguard.cfg
27 |
28 | #Gradle
29 | .gradle
30 | build
31 |
32 | #OSX
33 | .DS_Store
34 |
35 | #Personal Files
36 | signing.properties
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://maven-badges.herokuapp.com/maven-central/com.sothree.slidinguppanel/library)
2 |
3 |
4 | Android Sliding Up Panel
5 | =========================
6 |
7 | This library provides a simple way to add a draggable sliding up panel (popularized by Google Music, Google Maps and Rdio) to your Android application. Umano Team <3 Open Source.
8 |
9 | As seen in [Umano](http://umanoapp.com) [Android app](https://play.google.com/store/apps/details?id=com.sothree.umano):
10 |
11 | 
12 |
13 | ### Importing the library
14 |
15 | #### Eclipse
16 |
17 | Download the [latest release](https://github.com/umano/AndroidSlidingUpPanel/releases) and include the `library` project as a dependency in Eclipse.
18 |
19 | #### Android Studio
20 |
21 | Simply add the following dependency to your `build.gradle` file to use the latest version:
22 |
23 | ```groovy
24 | dependencies {
25 | repositories {
26 | mavenCentral()
27 | }
28 | compile 'com.sothree.slidinguppanel:library:3.1.1'
29 | }
30 | ```
31 |
32 | ### Usage
33 |
34 | * Include `com.sothree.slidinguppanel.SlidingUpPanelLayout` as the root element in your activity layout.
35 | * The layout must have `gravity` set to either `top` or `bottom`.
36 | * Make sure that it has two children. The first child is your main layout. The second child is your layout for the sliding up panel.
37 | * The main layout should have the width and the height set to `match_parent`.
38 | * The sliding layout should have the width set to `match_parent` and the height set to either `match_parent`, `wrap_content` or the max desireable height.
39 | * By default, the whole panel will act as a drag region and will intercept clicks and drag events. You can restrict the drag area to a specific view by using the `setDragView` method or `umanoDragView` attribute.
40 |
41 | For more information, please refer to the sample code.
42 |
43 | ```xml
44 |
52 |
53 |
59 |
60 |
66 |
67 | ```
68 | For smooth interaction with the ActionBar, make sure that `windowActionBarOverlay` is set to `true` in your styles:
69 | ```xml
70 |
73 | ```
74 | However, in this case you would likely want to add a top margin to your main layout of `?android:attr/actionBarSize`
75 | or `?attr/actionBarSize` to support older API versions.
76 |
77 | ### Caveats, Additional Features and Customization
78 |
79 | * If you are using a custom `umanoDragView`, the panel will pass through the click events to the main layout. Make your second layout `clickable` to prevent this.
80 | * You can change the panel height by using the `setPanelHeight` method or `umanoPanelHeight` attribute.
81 | * If you would like to hide the shadow above the sliding panel, set `shadowHeight` attribute to 0.
82 | * Use `setEnabled(false)` to completely disable the sliding panel (including touch and programmatic sliding)
83 | * Use `setTouchEnabled(false)` to disables panel's touch responsiveness (drag and click), you can still control the panel programatically
84 | * Use `getPanelState` to get the current panel state
85 | * Use `setPanelState` to set the current panel state
86 | * You can add paralax to the main view by setting `umanoParalaxOffset` attribute (see demo for the example).
87 | * You can set a anchor point in the middle of the screen using `setAnchorPoint` to allow an intermediate expanded state for the panel (similar to Google Maps).
88 | * You can set a `PanelSlideListener` to monitor events about sliding panes.
89 | * You can also make the panel slide from the top by changing the `layout_gravity` attribute of the layout to `top`.
90 | * If you have a ScrollView or a ListView inside of the panel, make sure to set `umanoScrollableView` attribute on the panel to supported nested scrolling.
91 | * By default, the panel pushes up the main content. You can make it overlay the main content by using `setOverlayed` method or `umanoOverlay` attribute. This is useful if you would like to make the sliding layout semi-transparent. You can also set `umanoClipPanel` to false to make the panel transparent in non-overlay mode.
92 | * By default, the main content is dimmed as the panel slides up. You can change the dim color by changing `umanoFadeColor`. Set it to `"@android:color/transparent"` to remove dimming completely.
93 |
94 | ### Implementation
95 |
96 | This library was initially based on the opened-sourced [SlidingPaneLayout](http://developer.android.com/reference/android/support/v4/widget/SlidingPaneLayout.html) component from the r13 of the Android Support Library. Thanks Android team!
97 |
98 | ### Requirements
99 |
100 | Tested on Android 2.2+
101 |
102 | ### Other Contributors
103 |
104 | * Jan 21, 14 - ChaYoung You ([@yous](https://github.com/yous)) - Slide from the top support
105 | * Aug 20, 13 - ([@gipi](https://github.com/gipi)) - Android Studio Support
106 | * Jul 24, 13 - Philip Schiffer ([@hameno](https://github.com/hameno)) - Maven Support
107 | * Oct 20, 13 - Irina Preșa ([@iriina](https://github.com/iriina)) - Anchor Support
108 | * Dec 1, 13 - ([@youchy](https://github.com/youchy)) - XML Attributes Support
109 | * Dec 22, 13 - Vladimir Mironov ([@MironovNsk](https://github.com/nsk-mironov)) - Custom Expanded Panel Height
110 |
111 | If you have an awesome pull request, send it over!
112 |
113 | ### Changelog
114 |
115 | * 3.1.0
116 | * Added `umanoScrollableView` to supported nested scrolling in children (only ScrollView and ListView are supported for now)
117 | * 3.0.0
118 | * Added `umano` prefix for all attributes
119 | * Added `clipPanel` attribute for supporting transparent panels in non-overlay mode.
120 | * `setEnabled(false)` - now completely disables the sliding panel (touch and programmatic sliding)
121 | * `setTouchEnabled(false)` - disables panel's touch responsiveness (drag and click), you can still control the panel programatically
122 | * `getPanelState` - is now the only method to get the current panel state
123 | * `setPanelState` - is now the only method to modify the panel state from code
124 | * 2.0.2 - Allow `wrap_content` for sliding view height attribute. Bug fixes.
125 | * 2.0.1 - Bug fixes.
126 | * 2.0.0 - Cleaned up various public method calls. Added animated `showPanel`/`hidePanel` methods.
127 | * 1.0.1 - Initial Release
128 |
129 | ### Licence
130 |
131 | > Licensed under the Apache License, Version 2.0 (the "License");
132 | > you may not use this work except in compliance with the License.
133 | > You may obtain a copy of the License in the LICENSE file, or at:
134 | >
135 | > [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
136 | >
137 | > Unless required by applicable law or agreed to in writing, software
138 | > distributed under the License is distributed on an "AS IS" BASIS,
139 | > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
140 | > See the License for the specific language governing permissions and
141 | > limitations under the License.
142 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | mavenCentral()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.0.0'
9 | }
10 | }
11 |
12 | def isReleaseBuild() {
13 | return version.contains("SNAPSHOT") == false
14 | }
15 |
16 | allprojects {
17 | version = VERSION_NAME
18 | group = GROUP
19 |
20 | repositories {
21 | mavenCentral()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.0.1"
6 |
7 | lintOptions {
8 | abortOnError false
9 | }
10 |
11 | defaultConfig {
12 | minSdkVersion 11
13 | targetSdkVersion 21
14 | }
15 | sourceSets {
16 | main {
17 | manifest.srcFile 'AndroidManifest.xml'
18 | java.srcDirs = ['src']
19 | resources.srcDirs = ['src']
20 | res.srcDirs = ['res']
21 | assets.srcDirs = ['assets']
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile 'com.android.support:support-v4:21.0.0'
28 | compile 'com.android.support:appcompat-v7:21.0.0'
29 | compile project(':library')
30 | }
31 |
--------------------------------------------------------------------------------
/demo/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/ic_launcher-web.png
--------------------------------------------------------------------------------
/demo/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/demo/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-16
15 | android.library.reference.1=../library
16 |
--------------------------------------------------------------------------------
/demo/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/res/drawable-xhdpi/graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/res/drawable-xhdpi/graphic.png
--------------------------------------------------------------------------------
/demo/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/demo/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/res/layout/activity_demo.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
19 |
20 |
21 |
25 |
33 |
44 |
45 |
46 |
47 |
55 |
56 |
60 |
61 |
69 |
70 |
78 |
79 |
80 |
81 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | -->
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/demo/res/menu/demo.xml:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/demo/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sliding Up Panel Demo
4 | Settings
5 | Hide Panel
6 | Show Panel
7 | Enable Anchor Point
8 | Disable Anchor Point
9 | The Awesome Sliding Up Panel Brought to you by http://umanoapp.com]]>
10 | on Twitter]]>
11 |
12 |
--------------------------------------------------------------------------------
/demo/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
17 |
18 |
19 |
22 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':Tnt2'
2 |
3 | include 'androidslidingup'
4 |
5 | project(':androidslidingup').projectDir = new File(settingsDir, '../library/')
6 |
--------------------------------------------------------------------------------
/demo/src/com/sothree/slidinguppanel/demo/DemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.sothree.slidinguppanel.demo;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.support.v7.app.ActionBarActivity;
7 | import android.support.v7.widget.Toolbar;
8 | import android.text.Html;
9 | import android.text.method.LinkMovementMethod;
10 | import android.util.Log;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 | import android.view.View.OnClickListener;
15 | import android.widget.AdapterView;
16 | import android.widget.ArrayAdapter;
17 | import android.widget.Button;
18 | import android.widget.ListView;
19 | import android.widget.TextView;
20 | import android.widget.Toast;
21 |
22 | import com.sothree.slidinguppanel.SlidingUpPanelLayout;
23 | import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelSlideListener;
24 | import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelState;
25 |
26 | import java.util.Arrays;
27 | import java.util.List;
28 |
29 | public class DemoActivity extends ActionBarActivity {
30 | private static final String TAG = "DemoActivity";
31 |
32 | private SlidingUpPanelLayout mLayout;
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_demo);
38 |
39 | setSupportActionBar((Toolbar) findViewById(R.id.main_toolbar));
40 |
41 | ListView lv = (ListView) findViewById(R.id.list);
42 | lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
43 | @Override
44 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
45 | Toast.makeText(DemoActivity.this, "onItemClick", Toast.LENGTH_SHORT).show();
46 | }
47 | });
48 |
49 | List your_array_list = Arrays.asList(
50 | "This",
51 | "Is",
52 | "An",
53 | "Example",
54 | "ListView",
55 | "That",
56 | "You",
57 | "Can",
58 | "Scroll",
59 | ".",
60 | "It",
61 | "Shows",
62 | "How",
63 | "Any",
64 | "Scrollable",
65 | "View",
66 | "Can",
67 | "Be",
68 | "Included",
69 | "As",
70 | "A",
71 | "Child",
72 | "Of",
73 | "SlidingUpPanelLayout"
74 | );
75 |
76 | // This is the array adapter, it takes the context of the activity as a
77 | // first parameter, the type of list view as a second parameter and your
78 | // array as a third parameter.
79 | ArrayAdapter arrayAdapter = new ArrayAdapter(
80 | this,
81 | android.R.layout.simple_list_item_1,
82 | your_array_list );
83 |
84 | lv.setAdapter(arrayAdapter);
85 |
86 | mLayout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
87 | mLayout.setPanelSlideListener(new PanelSlideListener() {
88 | @Override
89 | public void onPanelSlide(View panel, float slideOffset) {
90 | Log.i(TAG, "onPanelSlide, offset " + slideOffset);
91 | }
92 |
93 | @Override
94 | public void onPanelExpanded(View panel) {
95 | Log.i(TAG, "onPanelExpanded");
96 |
97 | }
98 |
99 | @Override
100 | public void onPanelCollapsed(View panel) {
101 | Log.i(TAG, "onPanelCollapsed");
102 |
103 | }
104 |
105 | @Override
106 | public void onPanelAnchored(View panel) {
107 | Log.i(TAG, "onPanelAnchored");
108 | }
109 |
110 | @Override
111 | public void onPanelHidden(View panel) {
112 | Log.i(TAG, "onPanelHidden");
113 | }
114 | });
115 |
116 | TextView t = (TextView) findViewById(R.id.name);
117 | t.setText(Html.fromHtml(getString(R.string.hello)));
118 | Button f = (Button) findViewById(R.id.follow);
119 | f.setText(Html.fromHtml(getString(R.string.follow)));
120 | f.setMovementMethod(LinkMovementMethod.getInstance());
121 | f.setOnClickListener(new OnClickListener() {
122 | @Override
123 | public void onClick(View v) {
124 | Intent i = new Intent(Intent.ACTION_VIEW);
125 | i.setData(Uri.parse("http://www.twitter.com/umanoapp"));
126 | startActivity(i);
127 | }
128 | });
129 | }
130 |
131 | @Override
132 | public boolean onCreateOptionsMenu(Menu menu) {
133 | // Inflate the menu; this adds items to the action bar if it is present.
134 | getMenuInflater().inflate(R.menu.demo, menu);
135 | MenuItem item = menu.findItem(R.id.action_toggle);
136 | if (mLayout != null) {
137 | if (mLayout.getPanelState() == PanelState.HIDDEN) {
138 | item.setTitle(R.string.action_show);
139 | } else {
140 | item.setTitle(R.string.action_hide);
141 | }
142 | }
143 | return true;
144 | }
145 |
146 | @Override
147 | public boolean onPrepareOptionsMenu(Menu menu) {
148 | return super.onPrepareOptionsMenu(menu);
149 | }
150 |
151 | @Override
152 | public boolean onOptionsItemSelected(MenuItem item) {
153 | switch (item.getItemId()){
154 | case R.id.action_toggle: {
155 | if (mLayout != null) {
156 | if (mLayout.getPanelState() != PanelState.HIDDEN) {
157 | mLayout.setPanelState(PanelState.HIDDEN);
158 | item.setTitle(R.string.action_show);
159 | } else {
160 | mLayout.setPanelState(PanelState.COLLAPSED);
161 | item.setTitle(R.string.action_hide);
162 | }
163 | }
164 | return true;
165 | }
166 | case R.id.action_anchor: {
167 | if (mLayout != null) {
168 | if (mLayout.getAnchorPoint() == 1.0f) {
169 | mLayout.setAnchorPoint(0.7f);
170 | mLayout.setPanelState(PanelState.ANCHORED);
171 | item.setTitle(R.string.action_anchor_disable);
172 | } else {
173 | mLayout.setAnchorPoint(1.0f);
174 | mLayout.setPanelState(PanelState.COLLAPSED);
175 | item.setTitle(R.string.action_anchor_enable);
176 | }
177 | }
178 | return true;
179 | }
180 | }
181 | return super.onOptionsItemSelected(item);
182 | }
183 |
184 | @Override
185 | public void onBackPressed() {
186 | if (mLayout != null &&
187 | (mLayout.getPanelState() == PanelState.EXPANDED || mLayout.getPanelState() == PanelState.ANCHORED)) {
188 | mLayout.setPanelState(PanelState.COLLAPSED);
189 | } else {
190 | super.onBackPressed();
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | VERSION_NAME=3.1.1
2 | VERSION_CODE=12
3 | GROUP=com.sothree.slidinguppanel
4 |
5 | POM_DESCRIPTION=Android Sliding Up Panel Library
6 | POM_URL=https://github.com/umano/AndroidSlidingUpPanel
7 | POM_SCM_URL=https://github.com/umano/AndroidSlidingUpPanel
8 | POM_SCM_CONNECTION=scm:git@github.com:umano/AndroidSlidingUpPanel.git
9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:umano/AndroidSlidingUpPanel.git
10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
12 | POM_LICENCE_DIST=repo
13 | POM_DEVELOPER_ID=tokudu
14 | POM_DEVELOPER_NAME=Anton Lopyrev
15 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jan 14 17:29:14 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | buildscript {
4 | repositories {
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.0.0'
9 | }
10 | }
11 |
12 | repositories {
13 | mavenCentral()
14 | }
15 |
16 | dependencies {
17 | compile 'com.android.support:support-v4:22.2.1'
18 | compile 'com.android.support:support-annotations:22.2.1'
19 | compile 'com.android.support:recyclerview-v7:22.2.1'
20 | compile 'com.nineoldandroids:library:2.4.0'
21 | }
22 |
23 | android {
24 | compileSdkVersion 21
25 | buildToolsVersion "22.0.1"
26 | lintOptions {
27 | abortOnError false
28 | }
29 | sourceSets {
30 | main {
31 | manifest.srcFile 'AndroidManifest.xml'
32 | java.srcDirs = ['src']
33 | resources.srcDirs = ['src']
34 | res.srcDirs = ['res']
35 | assets.srcDirs = ['assets']
36 | }
37 | }
38 | }
39 |
40 | apply from: '../maven_push.gradle'
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Android Sliding Up Panel Library
2 | POM_ARTIFACT_ID=library
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/library/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/library/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/library/libs/nineoldandroids-2.4.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/library/libs/nineoldandroids-2.4.0.jar
--------------------------------------------------------------------------------
/library/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library=true
16 |
--------------------------------------------------------------------------------
/library/res/drawable/above_shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/library/res/drawable/below_shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/library/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/library/src/com/sothree/slidinguppanel/SlidingUpPanelLayout.java:
--------------------------------------------------------------------------------
1 | package com.sothree.slidinguppanel;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.graphics.PixelFormat;
9 | import android.graphics.Rect;
10 | import android.graphics.drawable.Drawable;
11 | import android.os.Build;
12 | import android.os.Parcel;
13 | import android.os.Parcelable;
14 | import android.support.annotation.NonNull;
15 | import android.support.v4.view.MotionEventCompat;
16 | import android.support.v4.view.ViewCompat;
17 | import android.support.v7.widget.RecyclerView;
18 | import android.util.AttributeSet;
19 | import android.view.Gravity;
20 | import android.view.MotionEvent;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.view.accessibility.AccessibilityEvent;
24 | import android.widget.ListView;
25 | import android.widget.ScrollView;
26 |
27 | import com.nineoldandroids.view.animation.AnimatorProxy;
28 | import com.sothree.slidinguppanel.library.R;
29 |
30 | public class SlidingUpPanelLayout extends ViewGroup {
31 |
32 | private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
33 |
34 | /**
35 | * Default peeking out panel height
36 | */
37 | private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
38 |
39 | /**
40 | * Default anchor point height
41 | */
42 | private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %
43 |
44 | /**
45 | * Default initial state for the component
46 | */
47 | private static PanelState DEFAULT_SLIDE_STATE = PanelState.COLLAPSED;
48 |
49 | /**
50 | * Default height of the shadow above the peeking out panel
51 | */
52 | private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
53 |
54 | /**
55 | * If no fade color is given by default it will fade to 80% gray.
56 | */
57 | private static final int DEFAULT_FADE_COLOR = 0x99000000;
58 |
59 | /**
60 | * Default Minimum velocity that will be detected as a fling
61 | */
62 | private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
63 | /**
64 | * Default is set to false because that is how it was written
65 | */
66 | private static final boolean DEFAULT_OVERLAY_FLAG = false;
67 | /**
68 | * Default is set to true for clip panel for performance reasons
69 | */
70 | private static final boolean DEFAULT_CLIP_PANEL_FLAG = true;
71 | /**
72 | * Default attributes for layout
73 | */
74 | private static final int[] DEFAULT_ATTRS = new int[]{
75 | android.R.attr.gravity
76 | };
77 |
78 | /**
79 | * Minimum velocity that will be detected as a fling
80 | */
81 | private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
82 |
83 | /**
84 | * The fade color used for the panel covered by the slider. 0 = no fading.
85 | */
86 | private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
87 |
88 | /**
89 | * Default paralax length of the main view
90 | */
91 | private static final int DEFAULT_PARALAX_OFFSET = 0;
92 |
93 | /**
94 | * The paint used to dim the main layout when sliding
95 | */
96 | private final Paint mCoveredFadePaint = new Paint();
97 |
98 | /**
99 | * Drawable used to draw the shadow between panes.
100 | */
101 | private final Drawable mShadowDrawable;
102 |
103 | /**
104 | * The size of the overhang in pixels.
105 | */
106 | private int mPanelHeight = -1;
107 |
108 | /**
109 | * The size of the shadow in pixels.
110 | */
111 | private int mShadowHeight = -1;
112 |
113 | /**
114 | * Paralax offset
115 | */
116 | private int mParallaxOffset = -1;
117 |
118 | /**
119 | * True if the collapsed panel should be dragged up.
120 | */
121 | private boolean mIsSlidingUp;
122 |
123 | /**
124 | * Panel overlays the windows instead of putting it underneath it.
125 | */
126 | private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
127 |
128 | /**
129 | * The main view is clipped to the main top border
130 | */
131 | private boolean mClipPanel = DEFAULT_CLIP_PANEL_FLAG;
132 |
133 | /**
134 | * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
135 | * used for dragging.
136 | */
137 | private View mDragView;
138 |
139 | /**
140 | * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
141 | * used for dragging.
142 | */
143 | private int mDragViewResId = -1;
144 |
145 | /**
146 | * If provided, the panel will transfer the scroll from this view to itself when needed.
147 | */
148 | private View mScrollableView;
149 | private int mScrollableViewResId;
150 |
151 | /**
152 | * The child view that can slide, if any.
153 | */
154 | private View mSlideableView;
155 |
156 | /**
157 | * The main view
158 | */
159 | private View mMainView;
160 |
161 | /**
162 | * Current state of the slideable view.
163 | */
164 | public enum PanelState {
165 | EXPANDED,
166 | COLLAPSED,
167 | ANCHORED,
168 | HIDDEN,
169 | DRAGGING
170 | }
171 |
172 | private PanelState mSlideState = DEFAULT_SLIDE_STATE;
173 |
174 | /**
175 | * If the current slide state is DRAGGING, this will store the last non dragging state
176 | */
177 | private PanelState mLastNotDraggingSlideState = null;
178 |
179 | /**
180 | * How far the panel is offset from its expanded position.
181 | * range [0, 1] where 0 = collapsed, 1 = expanded.
182 | */
183 | private float mSlideOffset;
184 |
185 | /**
186 | * How far in pixels the slideable panel may move.
187 | */
188 | private int mSlideRange;
189 |
190 | /**
191 | * An anchor point where the panel can stop during sliding
192 | */
193 | private float mAnchorPoint = 1.f;
194 |
195 | /**
196 | * A panel view is locked into internal scrolling or another condition that
197 | * is preventing a drag.
198 | */
199 | private boolean mIsUnableToDrag;
200 |
201 | /**
202 | * Flag indicating that sliding feature is enabled\disabled
203 | */
204 | private boolean mIsTouchEnabled;
205 |
206 | private float mPrevMotionY;
207 | private float mInitialMotionX;
208 | private float mInitialMotionY;
209 | private boolean mIsScrollableViewHandlingTouch = false;
210 |
211 | private PanelSlideListener mPanelSlideListener;
212 |
213 | private final ViewDragHelper mDragHelper;
214 |
215 | /**
216 | * Stores whether or not the pane was expanded the last time it was slideable.
217 | * If expand/collapse operations are invoked this state is modified. Used by
218 | * instance state save/restore.
219 | */
220 | private boolean mFirstLayout = true;
221 |
222 | private final Rect mTmpRect = new Rect();
223 |
224 | /**
225 | * Listener for monitoring events about sliding panes.
226 | */
227 | public interface PanelSlideListener {
228 | /**
229 | * Called when a sliding pane's position changes.
230 | *
231 | * @param panel The child view that was moved
232 | * @param slideOffset The new offset of this sliding pane within its range, from 0-1
233 | */
234 | public void onPanelSlide(View panel, float slideOffset);
235 |
236 | /**
237 | * Called when a sliding panel becomes slid completely collapsed.
238 | *
239 | * @param panel The child view that was slid to an collapsed position
240 | */
241 | public void onPanelCollapsed(View panel);
242 |
243 | /**
244 | * Called when a sliding panel becomes slid completely expanded.
245 | *
246 | * @param panel The child view that was slid to a expanded position
247 | */
248 | public void onPanelExpanded(View panel);
249 |
250 | /**
251 | * Called when a sliding panel becomes anchored.
252 | *
253 | * @param panel The child view that was slid to a anchored position
254 | */
255 | public void onPanelAnchored(View panel);
256 |
257 | /**
258 | * Called when a sliding panel becomes completely hidden.
259 | *
260 | * @param panel The child view that was slid to a hidden position
261 | */
262 | public void onPanelHidden(View panel);
263 | }
264 |
265 | /**
266 | * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
267 | * of the listener methods you can extend this instead of implement the full interface.
268 | */
269 | public static class SimplePanelSlideListener implements PanelSlideListener {
270 | @Override
271 | public void onPanelSlide(View panel, float slideOffset) {
272 | }
273 |
274 | @Override
275 | public void onPanelCollapsed(View panel) {
276 | }
277 |
278 | @Override
279 | public void onPanelExpanded(View panel) {
280 | }
281 |
282 | @Override
283 | public void onPanelAnchored(View panel) {
284 | }
285 |
286 | @Override
287 | public void onPanelHidden(View panel) {
288 | }
289 | }
290 |
291 | public SlidingUpPanelLayout(Context context) {
292 | this(context, null);
293 | }
294 |
295 | public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
296 | this(context, attrs, 0);
297 | }
298 |
299 | public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
300 | super(context, attrs, defStyle);
301 |
302 | if (isInEditMode()) {
303 | mShadowDrawable = null;
304 | mDragHelper = null;
305 | return;
306 | }
307 |
308 | if (attrs != null) {
309 | TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
310 |
311 | if (defAttrs != null) {
312 | int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
313 | setGravity(gravity);
314 | }
315 |
316 | defAttrs.recycle();
317 |
318 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
319 |
320 | if (ta != null) {
321 | mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoPanelHeight, -1);
322 | mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoShadowHeight, -1);
323 | mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoParalaxOffset, -1);
324 |
325 | mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_umanoFlingVelocity, DEFAULT_MIN_FLING_VELOCITY);
326 | mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_umanoFadeColor, DEFAULT_FADE_COLOR);
327 |
328 | mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoDragView, -1);
329 | mScrollableViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoScrollableView, -1);
330 |
331 | mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoOverlay, DEFAULT_OVERLAY_FLAG);
332 | mClipPanel = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoClipPanel, DEFAULT_CLIP_PANEL_FLAG);
333 |
334 | mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_umanoAnchorPoint, DEFAULT_ANCHOR_POINT);
335 |
336 | mSlideState = PanelState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_umanoInitialState, DEFAULT_SLIDE_STATE.ordinal())];
337 | }
338 |
339 | ta.recycle();
340 | }
341 |
342 | final float density = context.getResources().getDisplayMetrics().density;
343 | if (mPanelHeight == -1) {
344 | mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
345 | }
346 | if (mShadowHeight == -1) {
347 | mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
348 | }
349 | if (mParallaxOffset == -1) {
350 | mParallaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density);
351 | }
352 | // If the shadow height is zero, don't show the shadow
353 | if (mShadowHeight > 0) {
354 | if (mIsSlidingUp) {
355 | mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
356 | } else {
357 | mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
358 | }
359 | } else {
360 | mShadowDrawable = null;
361 | }
362 |
363 | setWillNotDraw(false);
364 |
365 | mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
366 | mDragHelper.setMinVelocity(mMinFlingVelocity * density);
367 |
368 | mIsTouchEnabled = true;
369 | }
370 |
371 | /**
372 | * Set the Drag View after the view is inflated
373 | */
374 | @Override
375 | protected void onFinishInflate() {
376 | super.onFinishInflate();
377 | if (mDragViewResId != -1) {
378 | setDragView(findViewById(mDragViewResId));
379 | }
380 | if (mScrollableViewResId != -1) {
381 | setScrollableView(findViewById(mScrollableViewResId));
382 | }
383 | }
384 |
385 | public void setGravity(int gravity) {
386 | if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
387 | throw new IllegalArgumentException("gravity must be set to either top or bottom");
388 | }
389 | mIsSlidingUp = gravity == Gravity.BOTTOM;
390 | if (!mFirstLayout) {
391 | requestLayout();
392 | }
393 | }
394 |
395 | /**
396 | * Set the color used to fade the pane covered by the sliding pane out when the pane
397 | * will become fully covered in the expanded state.
398 | *
399 | * @param color An ARGB-packed color value
400 | */
401 | public void setCoveredFadeColor(int color) {
402 | mCoveredFadeColor = color;
403 | invalidate();
404 | }
405 |
406 | /**
407 | * @return The ARGB-packed color value used to fade the fixed pane
408 | */
409 | public int getCoveredFadeColor() {
410 | return mCoveredFadeColor;
411 | }
412 |
413 | /**
414 | * Set sliding enabled flag
415 | *
416 | * @param enabled flag value
417 | */
418 | public void setTouchEnabled(boolean enabled) {
419 | mIsTouchEnabled = enabled;
420 | }
421 |
422 | public boolean isTouchEnabled() {
423 | return mIsTouchEnabled && mSlideableView != null && mSlideState != PanelState.HIDDEN;
424 | }
425 |
426 | /**
427 | * Set the collapsed panel height in pixels
428 | *
429 | * @param val A height in pixels
430 | */
431 | public void setPanelHeight(int val) {
432 | if (getPanelHeight() == val) {
433 | return;
434 | }
435 |
436 | mPanelHeight = val;
437 | if (!mFirstLayout) {
438 | requestLayout();
439 | }
440 |
441 | if (getPanelState() == PanelState.COLLAPSED) {
442 | smoothToBottom();
443 | invalidate();
444 | return;
445 | }
446 | }
447 |
448 | protected void smoothToBottom() {
449 | smoothSlideTo(0, 0);
450 | }
451 |
452 | /**
453 | * @return The current shadow height
454 | */
455 | public int getShadowHeight() {
456 | return mShadowHeight;
457 | }
458 |
459 | /**
460 | * Set the shadow height
461 | *
462 | * @param val A height in pixels
463 | */
464 | public void setShadowHeight(int val) {
465 | mShadowHeight = val;
466 | if (!mFirstLayout) {
467 | invalidate();
468 | }
469 | }
470 |
471 | /**
472 | * @return The current collapsed panel height
473 | */
474 | public int getPanelHeight() {
475 | return mPanelHeight;
476 | }
477 |
478 | /**
479 | * @return The current paralax offset
480 | */
481 | public int getCurrentParalaxOffset() {
482 | // Clamp slide offset at zero for parallax computation;
483 | int offset = (int) (mParallaxOffset * Math.max(mSlideOffset, 0));
484 | return mIsSlidingUp ? -offset : offset;
485 | }
486 |
487 | /**
488 | * Set parallax offset for the panel
489 | *
490 | * @param val A height in pixels
491 | */
492 | public void setParalaxOffset(int val) {
493 | mParallaxOffset = val;
494 | if (!mFirstLayout) {
495 | requestLayout();
496 | }
497 | }
498 |
499 | /**
500 | * @return The current minimin fling velocity
501 | */
502 | public int getMinFlingVelocity() {
503 | return mMinFlingVelocity;
504 | }
505 |
506 | /**
507 | * Sets the minimum fling velocity for the panel
508 | *
509 | * @param val the new value
510 | */
511 | public void setMinFlingVelocity(int val) {
512 | mMinFlingVelocity = val;
513 | }
514 |
515 | /**
516 | * Sets the panel slide listener
517 | *
518 | * @param listener
519 | */
520 | public void setPanelSlideListener(PanelSlideListener listener) {
521 | mPanelSlideListener = listener;
522 | }
523 |
524 | /**
525 | * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
526 | *
527 | * @param dragView A view that will be used to drag the panel.
528 | */
529 | public void setDragView(View dragView) {
530 | if (mDragView != null) {
531 | mDragView.setOnClickListener(null);
532 | }
533 | mDragView = dragView;
534 | if (mDragView != null) {
535 | mDragView.setClickable(true);
536 | mDragView.setFocusable(false);
537 | mDragView.setFocusableInTouchMode(false);
538 | mDragView.setOnClickListener(new OnClickListener() {
539 | @Override
540 | public void onClick(View v) {
541 | if (!isEnabled() || !isTouchEnabled()) return;
542 | if (mSlideState != PanelState.EXPANDED && mSlideState != PanelState.ANCHORED) {
543 | if (mAnchorPoint < 1.0f) {
544 | setPanelState(PanelState.ANCHORED);
545 | } else {
546 | setPanelState(PanelState.EXPANDED);
547 | }
548 | } else {
549 | setPanelState(PanelState.COLLAPSED);
550 | }
551 | }
552 | });
553 | ;
554 | }
555 | }
556 |
557 | /**
558 | * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
559 | *
560 | * @param dragViewResId The resource ID of the new drag view
561 | */
562 | public void setDragView(int dragViewResId) {
563 | mDragViewResId = dragViewResId;
564 | setDragView(findViewById(dragViewResId));
565 | }
566 |
567 | /**
568 | * Set the scrollable child of the sliding layout. If set, scrolling will be transfered between
569 | * the panel and the view when necessary
570 | *
571 | * @param scrollableView The scrollable view
572 | */
573 | public void setScrollableView(View scrollableView) {
574 | mScrollableView = scrollableView;
575 | }
576 |
577 | /**
578 | * Set an anchor point where the panel can stop during sliding
579 | *
580 | * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
581 | * starting from the top of the layout.
582 | */
583 | public void setAnchorPoint(float anchorPoint) {
584 | if (anchorPoint > 0 && anchorPoint <= 1) {
585 | mAnchorPoint = anchorPoint;
586 | }
587 | }
588 |
589 | /**
590 | * Gets the currently set anchor point
591 | *
592 | * @return the currently set anchor point
593 | */
594 | public float getAnchorPoint() {
595 | return mAnchorPoint;
596 | }
597 |
598 | /**
599 | * Sets whether or not the panel overlays the content
600 | *
601 | * @param overlayed
602 | */
603 | public void setOverlayed(boolean overlayed) {
604 | mOverlayContent = overlayed;
605 | }
606 |
607 | /**
608 | * Check if the panel is set as an overlay.
609 | */
610 | public boolean isOverlayed() {
611 | return mOverlayContent;
612 | }
613 |
614 | /**
615 | * Sets whether or not the main content is clipped to the top of the panel
616 | *
617 | * @param overlayed
618 | */
619 | public void setClipPanel(boolean clip) {
620 | mClipPanel = clip;
621 | }
622 |
623 | /**
624 | * Check whether or not the main content is clipped to the top of the panel
625 | */
626 | public boolean isClipPanel() {
627 | return mClipPanel;
628 | }
629 |
630 | void dispatchOnPanelSlide(View panel) {
631 | if (mPanelSlideListener != null) {
632 | mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
633 | }
634 | }
635 |
636 | void dispatchOnPanelExpanded(View panel) {
637 | if (mPanelSlideListener != null) {
638 | mPanelSlideListener.onPanelExpanded(panel);
639 | }
640 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
641 | }
642 |
643 | void dispatchOnPanelCollapsed(View panel) {
644 | if (mPanelSlideListener != null) {
645 | mPanelSlideListener.onPanelCollapsed(panel);
646 | }
647 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
648 | }
649 |
650 | void dispatchOnPanelAnchored(View panel) {
651 | if (mPanelSlideListener != null) {
652 | mPanelSlideListener.onPanelAnchored(panel);
653 | }
654 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
655 | }
656 |
657 | void dispatchOnPanelHidden(View panel) {
658 | if (mPanelSlideListener != null) {
659 | mPanelSlideListener.onPanelHidden(panel);
660 | }
661 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
662 | }
663 |
664 | void updateObscuredViewVisibility() {
665 | if (getChildCount() == 0) {
666 | return;
667 | }
668 | final int leftBound = getPaddingLeft();
669 | final int rightBound = getWidth() - getPaddingRight();
670 | final int topBound = getPaddingTop();
671 | final int bottomBound = getHeight() - getPaddingBottom();
672 | final int left;
673 | final int right;
674 | final int top;
675 | final int bottom;
676 | if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
677 | left = mSlideableView.getLeft();
678 | right = mSlideableView.getRight();
679 | top = mSlideableView.getTop();
680 | bottom = mSlideableView.getBottom();
681 | } else {
682 | left = right = top = bottom = 0;
683 | }
684 | View child = getChildAt(0);
685 | final int clampedChildLeft = Math.max(leftBound, child.getLeft());
686 | final int clampedChildTop = Math.max(topBound, child.getTop());
687 | final int clampedChildRight = Math.min(rightBound, child.getRight());
688 | final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
689 | final int vis;
690 | if (clampedChildLeft >= left && clampedChildTop >= top &&
691 | clampedChildRight <= right && clampedChildBottom <= bottom) {
692 | vis = INVISIBLE;
693 | } else {
694 | vis = VISIBLE;
695 | }
696 | child.setVisibility(vis);
697 | }
698 |
699 | void setAllChildrenVisible() {
700 | for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
701 | final View child = getChildAt(i);
702 | if (child.getVisibility() == INVISIBLE) {
703 | child.setVisibility(VISIBLE);
704 | }
705 | }
706 | }
707 |
708 | private static boolean hasOpaqueBackground(View v) {
709 | final Drawable bg = v.getBackground();
710 | return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
711 | }
712 |
713 | @Override
714 | protected void onAttachedToWindow() {
715 | super.onAttachedToWindow();
716 | mFirstLayout = true;
717 | }
718 |
719 | @Override
720 | protected void onDetachedFromWindow() {
721 | super.onDetachedFromWindow();
722 | mFirstLayout = true;
723 | }
724 |
725 | @Override
726 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
727 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
728 | final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
729 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
730 | final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
731 |
732 | if (widthMode != MeasureSpec.EXACTLY) {
733 | throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
734 | } else if (heightMode != MeasureSpec.EXACTLY) {
735 | throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
736 | }
737 |
738 | final int childCount = getChildCount();
739 |
740 | if (childCount != 2) {
741 | throw new IllegalStateException("Sliding up panel layout must have exactly 2 children!");
742 | }
743 |
744 | mMainView = getChildAt(0);
745 | mSlideableView = getChildAt(1);
746 | if (mDragView == null) {
747 | setDragView(mSlideableView);
748 | }
749 |
750 | // If the sliding panel is not visible, then put the whole view in the hidden state
751 | if (mSlideableView.getVisibility() != VISIBLE) {
752 | mSlideState = PanelState.HIDDEN;
753 | }
754 |
755 | int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
756 | int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
757 |
758 | // First pass. Measure based on child LayoutParams width/height.
759 | for (int i = 0; i < childCount; i++) {
760 | final View child = getChildAt(i);
761 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
762 |
763 | // We always measure the sliding panel in order to know it's height (needed for show panel)
764 | if (child.getVisibility() == GONE && i == 0) {
765 | continue;
766 | }
767 |
768 | int height = layoutHeight;
769 | int width = layoutWidth;
770 | if (child == mMainView) {
771 | if (!mOverlayContent && mSlideState != PanelState.HIDDEN) {
772 | height -= mPanelHeight;
773 | }
774 |
775 | width -= lp.leftMargin + lp.rightMargin;
776 | } else if (child == mSlideableView) {
777 | // The slideable view should be aware of its top margin.
778 | // See https://github.com/umano/AndroidSlidingUpPanel/issues/412.
779 | height -= lp.topMargin;
780 | }
781 |
782 | int childWidthSpec;
783 | if (lp.width == LayoutParams.WRAP_CONTENT) {
784 | childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
785 | } else if (lp.width == LayoutParams.MATCH_PARENT) {
786 | childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
787 | } else {
788 | childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
789 | }
790 |
791 | int childHeightSpec;
792 | if (lp.height == LayoutParams.WRAP_CONTENT) {
793 | childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
794 | } else if (lp.height == LayoutParams.MATCH_PARENT) {
795 | childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
796 | } else {
797 | childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
798 | }
799 |
800 | child.measure(childWidthSpec, childHeightSpec);
801 |
802 | if (child == mSlideableView) {
803 | mSlideRange = mSlideableView.getMeasuredHeight() - mPanelHeight;
804 | }
805 | }
806 |
807 | setMeasuredDimension(widthSize, heightSize);
808 | }
809 |
810 | @Override
811 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
812 | final int paddingLeft = getPaddingLeft();
813 | final int paddingTop = getPaddingTop();
814 |
815 | final int childCount = getChildCount();
816 |
817 | if (mFirstLayout) {
818 | switch (mSlideState) {
819 | case EXPANDED:
820 | mSlideOffset = 1.0f;
821 | break;
822 | case ANCHORED:
823 | mSlideOffset = mAnchorPoint;
824 | break;
825 | case HIDDEN:
826 | int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
827 | mSlideOffset = computeSlideOffset(newTop);
828 | break;
829 | default:
830 | mSlideOffset = 0.f;
831 | break;
832 | }
833 | }
834 |
835 | for (int i = 0; i < childCount; i++) {
836 | final View child = getChildAt(i);
837 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
838 |
839 | // Always layout the sliding view on the first layout
840 | if (child.getVisibility() == GONE && (i == 0 || mFirstLayout)) {
841 | continue;
842 | }
843 |
844 | final int childHeight = child.getMeasuredHeight();
845 | int childTop = paddingTop;
846 |
847 | if (child == mSlideableView) {
848 | childTop = computePanelTopPosition(mSlideOffset);
849 | }
850 |
851 | if (!mIsSlidingUp) {
852 | if (child == mMainView && !mOverlayContent) {
853 | childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight();
854 | }
855 | }
856 | final int childBottom = childTop + childHeight;
857 | final int childLeft = paddingLeft + lp.leftMargin;
858 | final int childRight = childLeft + child.getMeasuredWidth();
859 |
860 | child.layout(childLeft, childTop, childRight, childBottom);
861 | }
862 |
863 | if (mFirstLayout) {
864 | updateObscuredViewVisibility();
865 | }
866 | applyParallaxForCurrentSlideOffset();
867 |
868 | mFirstLayout = false;
869 | }
870 |
871 | @Override
872 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
873 | super.onSizeChanged(w, h, oldw, oldh);
874 | // Recalculate sliding panes and their details
875 | if (h != oldh) {
876 | mFirstLayout = true;
877 | }
878 | }
879 |
880 | @Override
881 | public boolean onInterceptTouchEvent(MotionEvent ev) {
882 | // If the scrollable view is handling touch, never intercept
883 | if (mIsScrollableViewHandlingTouch) {
884 | mDragHelper.cancel();
885 | return false;
886 | }
887 |
888 | final int action = MotionEventCompat.getActionMasked(ev);
889 | final float x = ev.getX();
890 | final float y = ev.getY();
891 |
892 | switch (action) {
893 | case MotionEvent.ACTION_DOWN: {
894 | mIsUnableToDrag = false;
895 | mInitialMotionX = x;
896 | mInitialMotionY = y;
897 | break;
898 | }
899 |
900 | case MotionEvent.ACTION_MOVE: {
901 | final float adx = Math.abs(x - mInitialMotionX);
902 | final float ady = Math.abs(y - mInitialMotionY);
903 | final int dragSlop = mDragHelper.getTouchSlop();
904 |
905 | if ((ady > dragSlop && adx > ady) || !isViewUnder(mDragView, (int) mInitialMotionX, (int) mInitialMotionY)) {
906 | mDragHelper.cancel();
907 | mIsUnableToDrag = true;
908 | return false;
909 | }
910 | break;
911 | }
912 |
913 | case MotionEvent.ACTION_CANCEL:
914 | case MotionEvent.ACTION_UP:
915 | // If the dragView is still dragging when we get here, we need to call processTouchEvent
916 | // so that the view is settled
917 | // Added to make scrollable views work (tokudu)
918 | if (mDragHelper.isDragging()) {
919 | mDragHelper.processTouchEvent(ev);
920 | return true;
921 | }
922 | break;
923 | }
924 | return mDragHelper.shouldInterceptTouchEvent(ev);
925 | }
926 |
927 | @Override
928 | public boolean onTouchEvent(@NonNull MotionEvent ev) {
929 | if (!isEnabled() || !isTouchEnabled()) {
930 | return super.onTouchEvent(ev);
931 | }
932 | try {
933 | mDragHelper.processTouchEvent(ev);
934 | return true;
935 | } catch (Exception ex) {
936 | // Ignore the pointer out of range exception
937 | return false;
938 | }
939 | }
940 |
941 | @Override
942 | public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
943 | final int action = MotionEventCompat.getActionMasked(ev);
944 |
945 | if (!isEnabled() || !isTouchEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
946 | mDragHelper.cancel();
947 | return super.dispatchTouchEvent(ev);
948 | }
949 |
950 | final float y = ev.getY();
951 |
952 | if (action == MotionEvent.ACTION_DOWN) {
953 | mIsScrollableViewHandlingTouch = false;
954 | mPrevMotionY = y;
955 | } else if (action == MotionEvent.ACTION_MOVE) {
956 | float dy = y - mPrevMotionY;
957 | mPrevMotionY = y;
958 |
959 | // If the scroll view isn't under the touch, pass the
960 | // event along to the dragView.
961 | if (!isViewUnder(mScrollableView, (int) mInitialMotionX, (int) mInitialMotionY)) {
962 | return super.dispatchTouchEvent(ev);
963 | }
964 |
965 | // Which direction (up or down) is the drag moving?
966 | if (dy * (mIsSlidingUp ? 1 : -1) > 0) { // Collapsing
967 | // Is the child less than fully scrolled?
968 | // Then let the child handle it.
969 | if (getScrollableViewScrollPosition() > 0) {
970 | mIsScrollableViewHandlingTouch = true;
971 | return super.dispatchTouchEvent(ev);
972 | }
973 |
974 | // Was the child handling the touch previously?
975 | // Then we need to rejigger things so that the
976 | // drag panel gets a proper down event.
977 | if (mIsScrollableViewHandlingTouch) {
978 | // Send an 'UP' event to the child.
979 | MotionEvent up = MotionEvent.obtain(ev);
980 | up.setAction(MotionEvent.ACTION_CANCEL);
981 | super.dispatchTouchEvent(up);
982 | up.recycle();
983 |
984 | // Send a 'DOWN' event to the panel. (We'll cheat
985 | // and hijack this one)
986 | ev.setAction(MotionEvent.ACTION_DOWN);
987 | }
988 |
989 | mIsScrollableViewHandlingTouch = false;
990 | return this.onTouchEvent(ev);
991 | } else if (dy * (mIsSlidingUp ? 1 : -1) < 0) { // Expanding
992 | // Is the panel less than fully expanded?
993 | // Then we'll handle the drag here.
994 | if (mSlideOffset < 1.0f) {
995 | mIsScrollableViewHandlingTouch = false;
996 | return this.onTouchEvent(ev);
997 | }
998 |
999 | // Was the panel handling the touch previously?
1000 | // Then we need to rejigger things so that the
1001 | // child gets a proper down event.
1002 | if (!mIsScrollableViewHandlingTouch && mDragHelper.isDragging()) {
1003 | mDragHelper.cancel();
1004 | ev.setAction(MotionEvent.ACTION_DOWN);
1005 | }
1006 |
1007 | mIsScrollableViewHandlingTouch = true;
1008 | return super.dispatchTouchEvent(ev);
1009 | }
1010 | } else if (action == MotionEvent.ACTION_UP && mIsScrollableViewHandlingTouch) {
1011 | // If the scrollable view was handling the touch and we receive an up
1012 | // we want to clear any previous dragging state so we don't intercept a touch stream accidentally
1013 | mDragHelper.setDragState(ViewDragHelper.STATE_IDLE);
1014 | }
1015 |
1016 | // In all other cases, just let the default behavior take over.
1017 | return super.dispatchTouchEvent(ev);
1018 | }
1019 |
1020 | private boolean isViewUnder(View view, int x, int y) {
1021 | if (view == null) return false;
1022 | int[] viewLocation = new int[2];
1023 | view.getLocationOnScreen(viewLocation);
1024 | int[] parentLocation = new int[2];
1025 | this.getLocationOnScreen(parentLocation);
1026 | int screenX = parentLocation[0] + x;
1027 | int screenY = parentLocation[1] + y;
1028 | return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
1029 | screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
1030 | }
1031 |
1032 | private int getScrollableViewScrollPosition() {
1033 | if (mScrollableView == null) return 0;
1034 | if (mScrollableView instanceof ScrollView) {
1035 | if (mIsSlidingUp) {
1036 | return mScrollableView.getScrollY();
1037 | } else {
1038 | ScrollView sv = ((ScrollView) mScrollableView);
1039 | View child = sv.getChildAt(0);
1040 | return (child.getBottom() - (sv.getHeight() + sv.getScrollY()));
1041 | }
1042 | } else if (mScrollableView instanceof ListView && ((ListView) mScrollableView).getChildCount() > 0) {
1043 | ListView lv = ((ListView) mScrollableView);
1044 | if (lv.getAdapter() == null) return 0;
1045 | if (mIsSlidingUp) {
1046 | View firstChild = lv.getChildAt(0);
1047 | // Approximate the scroll position based on the top child and the first visible item
1048 | return lv.getFirstVisiblePosition() * firstChild.getHeight() - firstChild.getTop();
1049 | } else {
1050 | View lastChild = lv.getChildAt(lv.getChildCount() - 1);
1051 | // Approximate the scroll position based on the bottom child and the last visible item
1052 | return (lv.getAdapter().getCount() - lv.getLastVisiblePosition() - 1) * lastChild.getHeight() + lastChild.getBottom() - lv.getBottom();
1053 | }
1054 | }else if (mScrollableView instanceof RecyclerView && ((RecyclerView) mScrollableView).getChildCount() > 0) {
1055 | RecyclerView rv = ((RecyclerView) mScrollableView);
1056 | if (rv.getAdapter() == null) return 0;
1057 | if (mIsSlidingUp) {
1058 | View firstChild = rv.getChildAt(0);
1059 | // Approximate the scroll position based on the top child and the first visible item
1060 | return rv.getChildLayoutPosition(firstChild) * firstChild.getHeight() - firstChild.getTop();
1061 | } else {
1062 | View lastChild = rv.getChildAt(rv.getChildCount() - 1);
1063 | // Approximate the scroll position based on the bottom child and the last visible item
1064 | return (rv.getAdapter().getItemCount() - 1) * lastChild.getHeight() + lastChild.getBottom() - rv.getBottom();
1065 | }
1066 | } else {
1067 | return 0;
1068 | }
1069 | }
1070 |
1071 | /*
1072 | * Computes the top position of the panel based on the slide offset.
1073 | */
1074 | private int computePanelTopPosition(float slideOffset) {
1075 | int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0;
1076 | int slidePixelOffset = (int) (slideOffset * mSlideRange);
1077 | // Compute the top of the panel if its collapsed
1078 | return mIsSlidingUp
1079 | ? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset
1080 | : getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset;
1081 | }
1082 |
1083 | /*
1084 | * Computes the slide offset based on the top position of the panel
1085 | */
1086 | private float computeSlideOffset(int topPosition) {
1087 | // Compute the panel top position if the panel is collapsed (offset 0)
1088 | final int topBoundCollapsed = computePanelTopPosition(0);
1089 |
1090 | // Determine the new slide offset based on the collapsed top position and the new required
1091 | // top position
1092 | return (mIsSlidingUp
1093 | ? (float) (topBoundCollapsed - topPosition) / mSlideRange
1094 | : (float) (topPosition - topBoundCollapsed) / mSlideRange);
1095 | }
1096 |
1097 | /**
1098 | * Returns the current state of the panel as an enum.
1099 | *
1100 | * @return the current panel state
1101 | */
1102 | public PanelState getPanelState() {
1103 | return mSlideState;
1104 | }
1105 |
1106 | /**
1107 | * Change panel state to the given state with
1108 | *
1109 | * @param state - new panel state
1110 | */
1111 | public void setPanelState(PanelState state) {
1112 | if (state == null || state == PanelState.DRAGGING) {
1113 | throw new IllegalArgumentException("Panel state cannot be null or DRAGGING.");
1114 | }
1115 | if (!isEnabled()
1116 | || (!mFirstLayout && mSlideableView == null)
1117 | || state == mSlideState
1118 | || mSlideState == PanelState.DRAGGING) return;
1119 |
1120 | if (mFirstLayout) {
1121 | mSlideState = state;
1122 | } else {
1123 | if (mSlideState == PanelState.HIDDEN) {
1124 | mSlideableView.setVisibility(View.VISIBLE);
1125 | requestLayout();
1126 | }
1127 | switch (state) {
1128 | case ANCHORED:
1129 | smoothSlideTo(mAnchorPoint, 0);
1130 | break;
1131 | case COLLAPSED:
1132 | smoothSlideTo(0, 0);
1133 | break;
1134 | case EXPANDED:
1135 | smoothSlideTo(1.0f, 0);
1136 | break;
1137 | case HIDDEN:
1138 | int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
1139 | smoothSlideTo(computeSlideOffset(newTop), 0);
1140 | break;
1141 | }
1142 | }
1143 | }
1144 |
1145 | /**
1146 | * Update the parallax based on the current slide offset.
1147 | */
1148 | @SuppressLint("NewApi")
1149 | private void applyParallaxForCurrentSlideOffset() {
1150 | if (mParallaxOffset > 0) {
1151 | int mainViewOffset = getCurrentParalaxOffset();
1152 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
1153 | mMainView.setTranslationY(mainViewOffset);
1154 | } else {
1155 | AnimatorProxy.wrap(mMainView).setTranslationY(mainViewOffset);
1156 | }
1157 | }
1158 | }
1159 |
1160 | private void onPanelDragged(int newTop) {
1161 | mLastNotDraggingSlideState = mSlideState;
1162 | mSlideState = PanelState.DRAGGING;
1163 | // Recompute the slide offset based on the new top position
1164 | mSlideOffset = computeSlideOffset(newTop);
1165 | applyParallaxForCurrentSlideOffset();
1166 | // Dispatch the slide event
1167 | dispatchOnPanelSlide(mSlideableView);
1168 | // If the slide offset is negative, and overlay is not on, we need to increase the
1169 | // height of the main content
1170 | LayoutParams lp = (LayoutParams) mMainView.getLayoutParams();
1171 | int defaultHeight = getHeight() - getPaddingBottom() - getPaddingTop() - mPanelHeight;
1172 |
1173 | if (mSlideOffset <= 0 && !mOverlayContent) {
1174 | // expand the main view
1175 | lp.height = mIsSlidingUp ? (newTop - getPaddingBottom()) : (getHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() - newTop);
1176 | mMainView.requestLayout();
1177 | } else if (lp.height != defaultHeight && !mOverlayContent) {
1178 | lp.height = defaultHeight;
1179 | mMainView.requestLayout();
1180 | }
1181 | }
1182 |
1183 | @Override
1184 | protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1185 | boolean result;
1186 | final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
1187 |
1188 | if (mSlideableView != child) { // if main view
1189 | // Clip against the slider; no sense drawing what will immediately be covered,
1190 | // Unless the panel is set to overlay content
1191 | canvas.getClipBounds(mTmpRect);
1192 | if (!mOverlayContent) {
1193 | if (mIsSlidingUp) {
1194 | mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
1195 | } else {
1196 | mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
1197 | }
1198 | }
1199 | if (mClipPanel) {
1200 | canvas.clipRect(mTmpRect);
1201 | }
1202 |
1203 | result = super.drawChild(canvas, child, drawingTime);
1204 |
1205 | if (mCoveredFadeColor != 0 && mSlideOffset > 0) {
1206 | final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
1207 | final int imag = (int) (baseAlpha * mSlideOffset);
1208 | final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
1209 | mCoveredFadePaint.setColor(color);
1210 | canvas.drawRect(mTmpRect, mCoveredFadePaint);
1211 | }
1212 | } else {
1213 | result = super.drawChild(canvas, child, drawingTime);
1214 | }
1215 |
1216 | canvas.restoreToCount(save);
1217 |
1218 | return result;
1219 | }
1220 |
1221 | /**
1222 | * Smoothly animate mDraggingPane to the target X position within its range.
1223 | *
1224 | * @param slideOffset position to animate to
1225 | * @param velocity initial velocity in case of fling, or 0.
1226 | */
1227 | boolean smoothSlideTo(float slideOffset, int velocity) {
1228 | if (!isEnabled() || mSlideableView == null) {
1229 | // Nothing to do.
1230 | return false;
1231 | }
1232 |
1233 | int panelTop = computePanelTopPosition(slideOffset);
1234 | if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {
1235 | setAllChildrenVisible();
1236 | ViewCompat.postInvalidateOnAnimation(this);
1237 | return true;
1238 | }
1239 | return false;
1240 | }
1241 |
1242 | @Override
1243 | public void computeScroll() {
1244 | if (mDragHelper != null && mDragHelper.continueSettling(true)) {
1245 | if (!isEnabled()) {
1246 | mDragHelper.abort();
1247 | return;
1248 | }
1249 |
1250 | ViewCompat.postInvalidateOnAnimation(this);
1251 | }
1252 | }
1253 |
1254 | @Override
1255 | public void draw(Canvas c) {
1256 | super.draw(c);
1257 |
1258 | // draw the shadow
1259 | if (mShadowDrawable != null) {
1260 | final int right = mSlideableView.getRight();
1261 | final int top;
1262 | final int bottom;
1263 | if (mIsSlidingUp) {
1264 | top = mSlideableView.getTop() - mShadowHeight;
1265 | bottom = mSlideableView.getTop();
1266 | } else {
1267 | top = mSlideableView.getBottom();
1268 | bottom = mSlideableView.getBottom() + mShadowHeight;
1269 | }
1270 | final int left = mSlideableView.getLeft();
1271 | mShadowDrawable.setBounds(left, top, right, bottom);
1272 | mShadowDrawable.draw(c);
1273 | }
1274 | }
1275 |
1276 | /**
1277 | * Tests scrollability within child views of v given a delta of dx.
1278 | *
1279 | * @param v View to test for horizontal scrollability
1280 | * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1281 | * or just its children (false).
1282 | * @param dx Delta scrolled in pixels
1283 | * @param x X coordinate of the active touch point
1284 | * @param y Y coordinate of the active touch point
1285 | * @return true if child views of v can be scrolled by delta of dx.
1286 | */
1287 | protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1288 | if (v instanceof ViewGroup) {
1289 | final ViewGroup group = (ViewGroup) v;
1290 | final int scrollX = v.getScrollX();
1291 | final int scrollY = v.getScrollY();
1292 | final int count = group.getChildCount();
1293 | // Count backwards - let topmost views consume scroll distance first.
1294 | for (int i = count - 1; i >= 0; i--) {
1295 | final View child = group.getChildAt(i);
1296 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1297 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1298 | canScroll(child, true, dx, x + scrollX - child.getLeft(),
1299 | y + scrollY - child.getTop())) {
1300 | return true;
1301 | }
1302 | }
1303 | }
1304 | return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1305 | }
1306 |
1307 |
1308 | @Override
1309 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1310 | return new LayoutParams();
1311 | }
1312 |
1313 | @Override
1314 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1315 | return p instanceof MarginLayoutParams
1316 | ? new LayoutParams((MarginLayoutParams) p)
1317 | : new LayoutParams(p);
1318 | }
1319 |
1320 | @Override
1321 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1322 | return p instanceof LayoutParams && super.checkLayoutParams(p);
1323 | }
1324 |
1325 | @Override
1326 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1327 | return new LayoutParams(getContext(), attrs);
1328 | }
1329 |
1330 | @Override
1331 | public Parcelable onSaveInstanceState() {
1332 | Parcelable superState = super.onSaveInstanceState();
1333 |
1334 | SavedState ss = new SavedState(superState);
1335 | if (mSlideState != PanelState.DRAGGING) {
1336 | ss.mSlideState = mSlideState;
1337 | } else {
1338 | ss.mSlideState = mLastNotDraggingSlideState;
1339 | }
1340 | return ss;
1341 | }
1342 |
1343 | @Override
1344 | public void onRestoreInstanceState(Parcelable state) {
1345 | SavedState ss = (SavedState) state;
1346 | super.onRestoreInstanceState(ss.getSuperState());
1347 | mSlideState = ss.mSlideState != null ? ss.mSlideState : DEFAULT_SLIDE_STATE;
1348 | }
1349 |
1350 | private class DragHelperCallback extends ViewDragHelper.Callback {
1351 |
1352 | @Override
1353 | public boolean tryCaptureView(View child, int pointerId) {
1354 | if (mIsUnableToDrag) {
1355 | return false;
1356 | }
1357 |
1358 | return child == mSlideableView;
1359 | }
1360 |
1361 | @Override
1362 | public void onViewDragStateChanged(int state) {
1363 | if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1364 | mSlideOffset = computeSlideOffset(mSlideableView.getTop());
1365 | applyParallaxForCurrentSlideOffset();
1366 |
1367 | if (mSlideOffset == 1) {
1368 | if (mSlideState != PanelState.EXPANDED) {
1369 | updateObscuredViewVisibility();
1370 | mSlideState = PanelState.EXPANDED;
1371 | dispatchOnPanelExpanded(mSlideableView);
1372 | }
1373 | } else if (mSlideOffset == 0) {
1374 | if (mSlideState != PanelState.COLLAPSED) {
1375 | mSlideState = PanelState.COLLAPSED;
1376 | dispatchOnPanelCollapsed(mSlideableView);
1377 | }
1378 | } else if (mSlideOffset < 0) {
1379 | mSlideState = PanelState.HIDDEN;
1380 | mSlideableView.setVisibility(View.INVISIBLE);
1381 | dispatchOnPanelHidden(mSlideableView);
1382 | } else if (mSlideState != PanelState.ANCHORED) {
1383 | updateObscuredViewVisibility();
1384 | mSlideState = PanelState.ANCHORED;
1385 | dispatchOnPanelAnchored(mSlideableView);
1386 | }
1387 | }
1388 | }
1389 |
1390 | @Override
1391 | public void onViewCaptured(View capturedChild, int activePointerId) {
1392 | setAllChildrenVisible();
1393 | }
1394 |
1395 | @Override
1396 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1397 | onPanelDragged(top);
1398 | invalidate();
1399 | }
1400 |
1401 | @Override
1402 | public void onViewReleased(View releasedChild, float xvel, float yvel) {
1403 | int target = 0;
1404 |
1405 | // direction is always positive if we are sliding in the expanded direction
1406 | float direction = mIsSlidingUp ? -yvel : yvel;
1407 |
1408 | if (direction > 0 && mSlideOffset <= mAnchorPoint) {
1409 | // swipe up -> expand and stop at anchor point
1410 | target = computePanelTopPosition(mAnchorPoint);
1411 | } else if (direction > 0 && mSlideOffset > mAnchorPoint) {
1412 | // swipe up past anchor -> expand
1413 | target = computePanelTopPosition(1.0f);
1414 | } else if (direction < 0 && mSlideOffset >= mAnchorPoint) {
1415 | // swipe down -> collapse and stop at anchor point
1416 | target = computePanelTopPosition(mAnchorPoint);
1417 | } else if (direction < 0 && mSlideOffset < mAnchorPoint) {
1418 | // swipe down past anchor -> collapse
1419 | target = computePanelTopPosition(0.0f);
1420 | } else if (mSlideOffset >= (1.f + mAnchorPoint) / 2) {
1421 | // zero velocity, and far enough from anchor point => expand to the top
1422 | target = computePanelTopPosition(1.0f);
1423 | } else if (mSlideOffset >= mAnchorPoint / 2) {
1424 | // zero velocity, and close enough to anchor point => go to anchor
1425 | target = computePanelTopPosition(mAnchorPoint);
1426 | } else {
1427 | // settle at the bottom
1428 | target = computePanelTopPosition(0.0f);
1429 | }
1430 |
1431 | mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);
1432 | invalidate();
1433 | }
1434 |
1435 | @Override
1436 | public int getViewVerticalDragRange(View child) {
1437 | return mSlideRange;
1438 | }
1439 |
1440 | @Override
1441 | public int clampViewPositionVertical(View child, int top, int dy) {
1442 | final int collapsedTop = computePanelTopPosition(0.f);
1443 | final int expandedTop = computePanelTopPosition(1.0f);
1444 | if (mIsSlidingUp) {
1445 | return Math.min(Math.max(top, expandedTop), collapsedTop);
1446 | } else {
1447 | return Math.min(Math.max(top, collapsedTop), expandedTop);
1448 | }
1449 | }
1450 | }
1451 |
1452 | public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1453 | private static final int[] ATTRS = new int[]{
1454 | android.R.attr.layout_weight
1455 | };
1456 |
1457 | public LayoutParams() {
1458 | super(MATCH_PARENT, MATCH_PARENT);
1459 | }
1460 |
1461 | public LayoutParams(int width, int height) {
1462 | super(width, height);
1463 | }
1464 |
1465 | public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1466 | super(source);
1467 | }
1468 |
1469 | public LayoutParams(MarginLayoutParams source) {
1470 | super(source);
1471 | }
1472 |
1473 | public LayoutParams(LayoutParams source) {
1474 | super(source);
1475 | }
1476 |
1477 | public LayoutParams(Context c, AttributeSet attrs) {
1478 | super(c, attrs);
1479 |
1480 | final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1481 | a.recycle();
1482 | }
1483 | }
1484 |
1485 | static class SavedState extends BaseSavedState {
1486 | PanelState mSlideState;
1487 |
1488 | SavedState(Parcelable superState) {
1489 | super(superState);
1490 | }
1491 |
1492 | private SavedState(Parcel in) {
1493 | super(in);
1494 | try {
1495 | mSlideState = Enum.valueOf(PanelState.class, in.readString());
1496 | } catch (IllegalArgumentException e) {
1497 | mSlideState = PanelState.COLLAPSED;
1498 | }
1499 | }
1500 |
1501 | @Override
1502 | public void writeToParcel(Parcel out, int flags) {
1503 | super.writeToParcel(out, flags);
1504 | out.writeString(mSlideState.toString());
1505 | }
1506 |
1507 | public static final Parcelable.Creator CREATOR =
1508 | new Parcelable.Creator() {
1509 | @Override
1510 | public SavedState createFromParcel(Parcel in) {
1511 | return new SavedState(in);
1512 | }
1513 |
1514 | @Override
1515 | public SavedState[] newArray(int size) {
1516 | return new SavedState[size];
1517 | }
1518 | };
1519 | }
1520 | }
1521 |
--------------------------------------------------------------------------------
/library/src/com/sothree/slidinguppanel/ViewDragHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | package com.sothree.slidinguppanel;
19 |
20 | import android.content.Context;
21 | import android.support.v4.view.MotionEventCompat;
22 | import android.support.v4.view.VelocityTrackerCompat;
23 | import android.support.v4.view.ViewCompat;
24 | import android.support.v4.widget.ScrollerCompat;
25 | import android.view.MotionEvent;
26 | import android.view.VelocityTracker;
27 | import android.view.View;
28 | import android.view.ViewConfiguration;
29 | import android.view.ViewGroup;
30 | import android.view.animation.Interpolator;
31 |
32 | import java.util.Arrays;
33 |
34 | /**
35 | * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
36 | * of useful operations and state tracking for allowing a user to drag and reposition
37 | * views within their parent ViewGroup.
38 | */
39 | public class ViewDragHelper {
40 | private static final String TAG = "ViewDragHelper";
41 |
42 | /**
43 | * A null/invalid pointer ID.
44 | */
45 | public static final int INVALID_POINTER = -1;
46 |
47 | /**
48 | * A view is not currently being dragged or animating as a result of a fling/snap.
49 | */
50 | public static final int STATE_IDLE = 0;
51 |
52 | /**
53 | * A view is currently being dragged. The position is currently changing as a result
54 | * of user input or simulated user input.
55 | */
56 | public static final int STATE_DRAGGING = 1;
57 |
58 | /**
59 | * A view is currently settling into place as a result of a fling or
60 | * predefined non-interactive motion.
61 | */
62 | public static final int STATE_SETTLING = 2;
63 |
64 | /**
65 | * Edge flag indicating that the left edge should be affected.
66 | */
67 | public static final int EDGE_LEFT = 1 << 0;
68 |
69 | /**
70 | * Edge flag indicating that the right edge should be affected.
71 | */
72 | public static final int EDGE_RIGHT = 1 << 1;
73 |
74 | /**
75 | * Edge flag indicating that the top edge should be affected.
76 | */
77 | public static final int EDGE_TOP = 1 << 2;
78 |
79 | /**
80 | * Edge flag indicating that the bottom edge should be affected.
81 | */
82 | public static final int EDGE_BOTTOM = 1 << 3;
83 |
84 | /**
85 | * Edge flag set indicating all edges should be affected.
86 | */
87 | public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
88 |
89 | /**
90 | * Indicates that a check should occur along the horizontal axis
91 | */
92 | public static final int DIRECTION_HORIZONTAL = 1 << 0;
93 |
94 | /**
95 | * Indicates that a check should occur along the vertical axis
96 | */
97 | public static final int DIRECTION_VERTICAL = 1 << 1;
98 |
99 | /**
100 | * Indicates that a check should occur along all axes
101 | */
102 | public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
103 |
104 | private static final int EDGE_SIZE = 20; // dp
105 |
106 | private static final int BASE_SETTLE_DURATION = 256; // ms
107 | private static final int MAX_SETTLE_DURATION = 600; // ms
108 |
109 | // Current drag state; idle, dragging or settling
110 | private int mDragState;
111 |
112 | // Distance to travel before a drag may begin
113 | private int mTouchSlop;
114 |
115 | // Last known position/pointer tracking
116 | private int mActivePointerId = INVALID_POINTER;
117 | private float[] mInitialMotionX;
118 | private float[] mInitialMotionY;
119 | private float[] mLastMotionX;
120 | private float[] mLastMotionY;
121 | private int[] mInitialEdgesTouched;
122 | private int[] mEdgeDragsInProgress;
123 | private int[] mEdgeDragsLocked;
124 | private int mPointersDown;
125 |
126 | private VelocityTracker mVelocityTracker;
127 | private float mMaxVelocity;
128 | private float mMinVelocity;
129 |
130 | private int mEdgeSize;
131 | private int mTrackingEdges;
132 |
133 | private ScrollerCompat mScroller;
134 |
135 | private final Callback mCallback;
136 |
137 | private View mCapturedView;
138 | private boolean mReleaseInProgress;
139 |
140 | private final ViewGroup mParentView;
141 |
142 | /**
143 | * A Callback is used as a communication channel with the ViewDragHelper back to the
144 | * parent view using it. on*methods are invoked on siginficant events and several
145 | * accessor methods are expected to provide the ViewDragHelper with more information
146 | * about the state of the parent view upon request. The callback also makes decisions
147 | * governing the range and draggability of child views.
148 | */
149 | public static abstract class Callback {
150 | /**
151 | * Called when the drag state changes. See the STATE_* constants
152 | * for more information.
153 | *
154 | * @param state The new drag state
155 | *
156 | * @see #STATE_IDLE
157 | * @see #STATE_DRAGGING
158 | * @see #STATE_SETTLING
159 | */
160 | public void onViewDragStateChanged(int state) {}
161 |
162 | /**
163 | * Called when the captured view's position changes as the result of a drag or settle.
164 | *
165 | * @param changedView View whose position changed
166 | * @param left New X coordinate of the left edge of the view
167 | * @param top New Y coordinate of the top edge of the view
168 | * @param dx Change in X position from the last call
169 | * @param dy Change in Y position from the last call
170 | */
171 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
172 |
173 | /**
174 | * Called when a child view is captured for dragging or settling. The ID of the pointer
175 | * currently dragging the captured view is supplied. If activePointerId is
176 | * identified as {@link #INVALID_POINTER} the capture is programmatic instead of
177 | * pointer-initiated.
178 | *
179 | * @param capturedChild Child view that was captured
180 | * @param activePointerId Pointer id tracking the child capture
181 | */
182 | public void onViewCaptured(View capturedChild, int activePointerId) {}
183 |
184 | /**
185 | * Called when the child view is no longer being actively dragged.
186 | * The fling velocity is also supplied, if relevant. The velocity values may
187 | * be clamped to system minimums or maximums.
188 | *
189 | *
Calling code may decide to fling or otherwise release the view to let it
190 | * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}
191 | * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes
192 | * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}
193 | * and the view capture will not fully end until it comes to a complete stop.
194 | * If neither of these methods is invoked before onViewReleased returns,
195 | * the view will stop in place and the ViewDragHelper will return to
196 | * {@link #STATE_IDLE}.
197 | *
198 | * @param releasedChild The captured child view now being released
199 | * @param xvel X velocity of the pointer as it left the screen in pixels per second.
200 | * @param yvel Y velocity of the pointer as it left the screen in pixels per second.
201 | */
202 | public void onViewReleased(View releasedChild, float xvel, float yvel) {}
203 |
204 | /**
205 | * Called when one of the subscribed edges in the parent view has been touched
206 | * by the user while no child view is currently captured.
207 | *
208 | * @param edgeFlags A combination of edge flags describing the edge(s) currently touched
209 | * @param pointerId ID of the pointer touching the described edge(s)
210 | * @see #EDGE_LEFT
211 | * @see #EDGE_TOP
212 | * @see #EDGE_RIGHT
213 | * @see #EDGE_BOTTOM
214 | */
215 | public void onEdgeTouched(int edgeFlags, int pointerId) {}
216 |
217 | /**
218 | * Called when the given edge may become locked. This can happen if an edge drag
219 | * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
220 | * was called. This method should return true to lock this edge or false to leave it
221 | * unlocked. The default behavior is to leave edges unlocked.
222 | *
223 | * @param edgeFlags A combination of edge flags describing the edge(s) locked
224 | * @return true to lock the edge, false to leave it unlocked
225 | */
226 | public boolean onEdgeLock(int edgeFlags) {
227 | return false;
228 | }
229 |
230 | /**
231 | * Called when the user has started a deliberate drag away from one
232 | * of the subscribed edges in the parent view while no child view is currently captured.
233 | *
234 | * @param edgeFlags A combination of edge flags describing the edge(s) dragged
235 | * @param pointerId ID of the pointer touching the described edge(s)
236 | * @see #EDGE_LEFT
237 | * @see #EDGE_TOP
238 | * @see #EDGE_RIGHT
239 | * @see #EDGE_BOTTOM
240 | */
241 | public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
242 |
243 | /**
244 | * Called to determine the Z-order of child views.
245 | *
246 | * @param index the ordered position to query for
247 | * @return index of the view that should be ordered at position index
248 | */
249 | public int getOrderedChildIndex(int index) {
250 | return index;
251 | }
252 |
253 | /**
254 | * Return the magnitude of a draggable child view's horizontal range of motion in pixels.
255 | * This method should return 0 for views that cannot move horizontally.
256 | *
257 | * @param child Child view to check
258 | * @return range of horizontal motion in pixels
259 | */
260 | public int getViewHorizontalDragRange(View child) {
261 | return 0;
262 | }
263 |
264 | /**
265 | * Return the magnitude of a draggable child view's vertical range of motion in pixels.
266 | * This method should return 0 for views that cannot move vertically.
267 | *
268 | * @param child Child view to check
269 | * @return range of vertical motion in pixels
270 | */
271 | public int getViewVerticalDragRange(View child) {
272 | return 0;
273 | }
274 |
275 | /**
276 | * Called when the user's input indicates that they want to capture the given child view
277 | * with the pointer indicated by pointerId. The callback should return true if the user
278 | * is permitted to drag the given view with the indicated pointer.
279 | *
280 | *
ViewDragHelper may call this method multiple times for the same view even if
281 | * the view is already captured; this indicates that a new pointer is trying to take
282 | * control of the view.
283 | *
284 | *
If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}
285 | * will follow if the capture is successful.
286 | *
287 | * @param child Child the user is attempting to capture
288 | * @param pointerId ID of the pointer attempting the capture
289 | * @return true if capture should be allowed, false otherwise
290 | */
291 | public abstract boolean tryCaptureView(View child, int pointerId);
292 |
293 | /**
294 | * Restrict the motion of the dragged child view along the horizontal axis.
295 | * The default implementation does not allow horizontal motion; the extending
296 | * class must override this method and provide the desired clamping.
297 | *
298 | *
299 | * @param child Child view being dragged
300 | * @param left Attempted motion along the X axis
301 | * @param dx Proposed change in position for left
302 | * @return The new clamped position for left
303 | */
304 | public int clampViewPositionHorizontal(View child, int left, int dx) {
305 | return 0;
306 | }
307 |
308 | /**
309 | * Restrict the motion of the dragged child view along the vertical axis.
310 | * The default implementation does not allow vertical motion; the extending
311 | * class must override this method and provide the desired clamping.
312 | *
313 | *
314 | * @param child Child view being dragged
315 | * @param top Attempted motion along the Y axis
316 | * @param dy Proposed change in position for top
317 | * @return The new clamped position for top
318 | */
319 | public int clampViewPositionVertical(View child, int top, int dy) {
320 | return 0;
321 | }
322 | }
323 |
324 | /**
325 | * Interpolator defining the animation curve for mScroller
326 | */
327 | private static final Interpolator sInterpolator = new Interpolator() {
328 | public float getInterpolation(float t) {
329 | t -= 1.0f;
330 | return t * t * t * t * t + 1.0f;
331 | }
332 | };
333 |
334 | private final Runnable mSetIdleRunnable = new Runnable() {
335 | public void run() {
336 | setDragState(STATE_IDLE);
337 | }
338 | };
339 |
340 | /**
341 | * Factory method to create a new ViewDragHelper.
342 | *
343 | * @param forParent Parent view to monitor
344 | * @param cb Callback to provide information and receive events
345 | * @return a new ViewDragHelper instance
346 | */
347 | public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
348 | return new ViewDragHelper(forParent.getContext(), forParent, cb);
349 | }
350 |
351 | /**
352 | * Factory method to create a new ViewDragHelper.
353 | *
354 | * @param forParent Parent view to monitor
355 | * @param sensitivity Multiplier for how sensitive the helper should be about detecting
356 | * the start of a drag. Larger values are more sensitive. 1.0f is normal.
357 | * @param cb Callback to provide information and receive events
358 | * @return a new ViewDragHelper instance
359 | */
360 | public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
361 | final ViewDragHelper helper = create(forParent, cb);
362 | helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
363 | return helper;
364 | }
365 |
366 | /**
367 | * Apps should use ViewDragHelper.create() to get a new instance.
368 | * This will allow VDH to use internal compatibility implementations for different
369 | * platform versions.
370 | *
371 | * @param context Context to initialize config-dependent params from
372 | * @param forParent Parent view to monitor
373 | */
374 | private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
375 | if (forParent == null) {
376 | throw new IllegalArgumentException("Parent view may not be null");
377 | }
378 | if (cb == null) {
379 | throw new IllegalArgumentException("Callback may not be null");
380 | }
381 |
382 | mParentView = forParent;
383 | mCallback = cb;
384 |
385 | final ViewConfiguration vc = ViewConfiguration.get(context);
386 | final float density = context.getResources().getDisplayMetrics().density;
387 | mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
388 |
389 | mTouchSlop = vc.getScaledTouchSlop();
390 | mMaxVelocity = vc.getScaledMaximumFlingVelocity();
391 | mMinVelocity = vc.getScaledMinimumFlingVelocity();
392 | mScroller = ScrollerCompat.create(context, sInterpolator);
393 | }
394 |
395 | /**
396 | * Set the minimum velocity that will be detected as having a magnitude greater than zero
397 | * in pixels per second. Callback methods accepting a velocity will be clamped appropriately.
398 | *
399 | * @param minVel Minimum velocity to detect
400 | */
401 | public void setMinVelocity(float minVel) {
402 | mMinVelocity = minVel;
403 | }
404 |
405 | /**
406 | * Return the currently configured minimum velocity. Any flings with a magnitude less
407 | * than this value in pixels per second. Callback methods accepting a velocity will receive
408 | * zero as a velocity value if the real detected velocity was below this threshold.
409 | *
410 | * @return the minimum velocity that will be detected
411 | */
412 | public float getMinVelocity() {
413 | return mMinVelocity;
414 | }
415 |
416 | /**
417 | * Retrieve the current drag state of this helper. This will return one of
418 | * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
419 | * @return The current drag state
420 | */
421 | public int getViewDragState() {
422 | return mDragState;
423 | }
424 |
425 | /**
426 | * Enable edge tracking for the selected edges of the parent view.
427 | * The callback's {@link Callback#onEdgeTouched(int, int)} and
428 | * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
429 | * for edges for which edge tracking has been enabled.
430 | *
431 | * @param edgeFlags Combination of edge flags describing the edges to watch
432 | * @see #EDGE_LEFT
433 | * @see #EDGE_TOP
434 | * @see #EDGE_RIGHT
435 | * @see #EDGE_BOTTOM
436 | */
437 | public void setEdgeTrackingEnabled(int edgeFlags) {
438 | mTrackingEdges = edgeFlags;
439 | }
440 |
441 | /**
442 | * Return the size of an edge. This is the range in pixels along the edges of this view
443 | * that will actively detect edge touches or drags if edge tracking is enabled.
444 | *
445 | * @return The size of an edge in pixels
446 | * @see #setEdgeTrackingEnabled(int)
447 | */
448 | public int getEdgeSize() {
449 | return mEdgeSize;
450 | }
451 |
452 | /**
453 | * Capture a specific child view for dragging within the parent. The callback will be notified
454 | * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
455 | * capture this view.
456 | *
457 | * @param childView Child view to capture
458 | * @param activePointerId ID of the pointer that is dragging the captured child view
459 | */
460 | public void captureChildView(View childView, int activePointerId) {
461 | if (childView.getParent() != mParentView) {
462 | throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
463 | "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
464 | }
465 |
466 | mCapturedView = childView;
467 | mActivePointerId = activePointerId;
468 | mCallback.onViewCaptured(childView, activePointerId);
469 | setDragState(STATE_DRAGGING);
470 | }
471 |
472 | /**
473 | * @return The currently captured view, or null if no view has been captured.
474 | */
475 | public View getCapturedView() {
476 | return mCapturedView;
477 | }
478 |
479 | /**
480 | * @return The ID of the pointer currently dragging the captured view,
481 | * or {@link #INVALID_POINTER}.
482 | */
483 | public int getActivePointerId() {
484 | return mActivePointerId;
485 | }
486 |
487 | /**
488 | * @return The minimum distance in pixels that the user must travel to initiate a drag
489 | */
490 | public int getTouchSlop() {
491 | return mTouchSlop;
492 | }
493 |
494 | /**
495 | * The result of a call to this method is equivalent to
496 | * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.
497 | */
498 | public void cancel() {
499 | mActivePointerId = INVALID_POINTER;
500 | clearMotionHistory();
501 |
502 | if (mVelocityTracker != null) {
503 | mVelocityTracker.recycle();
504 | mVelocityTracker = null;
505 | }
506 | }
507 |
508 | /**
509 | * {@link #cancel()}, but also abort all motion in progress and snap to the end of any
510 | * animation.
511 | */
512 | public void abort() {
513 | cancel();
514 | if (mDragState == STATE_SETTLING) {
515 | final int oldX = mScroller.getCurrX();
516 | final int oldY = mScroller.getCurrY();
517 | mScroller.abortAnimation();
518 | final int newX = mScroller.getCurrX();
519 | final int newY = mScroller.getCurrY();
520 | mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
521 | }
522 | setDragState(STATE_IDLE);
523 | }
524 |
525 | /**
526 | * Animate the view child to the given (left, top) position.
527 | * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
528 | * on each subsequent frame to continue the motion until it returns false. If this method
529 | * returns false there is no further work to do to complete the movement.
530 | *
531 | *
This operation does not count as a capture event, though {@link #getCapturedView()}
532 | * will still report the sliding view while the slide is in progress.
533 | *
534 | * @param child Child view to capture and animate
535 | * @param finalLeft Final left position of child
536 | * @param finalTop Final top position of child
537 | * @return true if animation should continue through {@link #continueSettling(boolean)} calls
538 | */
539 | public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
540 | mCapturedView = child;
541 | mActivePointerId = INVALID_POINTER;
542 |
543 | return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
544 | }
545 |
546 | /**
547 | * Settle the captured view at the given (left, top) position.
548 | * The appropriate velocity from prior motion will be taken into account.
549 | * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
550 | * on each subsequent frame to continue the motion until it returns false. If this method
551 | * returns false there is no further work to do to complete the movement.
552 | *
553 | * @param finalLeft Settled left edge position for the captured view
554 | * @param finalTop Settled top edge position for the captured view
555 | * @return true if animation should continue through {@link #continueSettling(boolean)} calls
556 | */
557 | public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
558 | if (!mReleaseInProgress) {
559 | throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
560 | "Callback#onViewReleased");
561 | }
562 |
563 | return forceSettleCapturedViewAt(finalLeft, finalTop,
564 | (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
565 | (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
566 | }
567 |
568 | /**
569 | * Settle the captured view at the given (left, top) position.
570 | *
571 | * @param finalLeft Target left position for the captured view
572 | * @param finalTop Target top position for the captured view
573 | * @param xvel Horizontal velocity
574 | * @param yvel Vertical velocity
575 | * @return true if animation should continue through {@link #continueSettling(boolean)} calls
576 | */
577 | private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
578 | final int startLeft = mCapturedView.getLeft();
579 | final int startTop = mCapturedView.getTop();
580 | final int dx = finalLeft - startLeft;
581 | final int dy = finalTop - startTop;
582 |
583 | if (dx == 0 && dy == 0) {
584 | // Nothing to do. Send callbacks, be done.
585 | mScroller.abortAnimation();
586 | setDragState(STATE_IDLE);
587 | return false;
588 | }
589 |
590 | final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
591 | mScroller.startScroll(startLeft, startTop, dx, dy, duration);
592 |
593 | setDragState(STATE_SETTLING);
594 | return true;
595 | }
596 |
597 | private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
598 | xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
599 | yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
600 | final int absDx = Math.abs(dx);
601 | final int absDy = Math.abs(dy);
602 | final int absXVel = Math.abs(xvel);
603 | final int absYVel = Math.abs(yvel);
604 | final int addedVel = absXVel + absYVel;
605 | final int addedDistance = absDx + absDy;
606 |
607 | final float xweight = xvel != 0 ? (float) absXVel / addedVel :
608 | (float) absDx / addedDistance;
609 | final float yweight = yvel != 0 ? (float) absYVel / addedVel :
610 | (float) absDy / addedDistance;
611 |
612 | int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
613 | int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
614 |
615 | return (int) (xduration * xweight + yduration * yweight);
616 | }
617 |
618 | private int computeAxisDuration(int delta, int velocity, int motionRange) {
619 | if (delta == 0) {
620 | return 0;
621 | }
622 |
623 | final int width = mParentView.getWidth();
624 | final int halfWidth = width / 2;
625 | final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
626 | final float distance = halfWidth + halfWidth *
627 | distanceInfluenceForSnapDuration(distanceRatio);
628 |
629 | int duration;
630 | velocity = Math.abs(velocity);
631 | if (velocity > 0) {
632 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
633 | } else {
634 | final float range = (float) Math.abs(delta) / motionRange;
635 | duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
636 | }
637 | return Math.min(duration, MAX_SETTLE_DURATION);
638 | }
639 |
640 | /**
641 | * Clamp the magnitude of value for absMin and absMax.
642 | * If the value is below the minimum, it will be clamped to zero.
643 | * If the value is above the maximum, it will be clamped to the maximum.
644 | *
645 | * @param value Value to clamp
646 | * @param absMin Absolute value of the minimum significant value to return
647 | * @param absMax Absolute value of the maximum value to return
648 | * @return The clamped value with the same sign as value
649 | */
650 | private int clampMag(int value, int absMin, int absMax) {
651 | final int absValue = Math.abs(value);
652 | if (absValue < absMin) return 0;
653 | if (absValue > absMax) return value > 0 ? absMax : -absMax;
654 | return value;
655 | }
656 |
657 | /**
658 | * Clamp the magnitude of value for absMin and absMax.
659 | * If the value is below the minimum, it will be clamped to zero.
660 | * If the value is above the maximum, it will be clamped to the maximum.
661 | *
662 | * @param value Value to clamp
663 | * @param absMin Absolute value of the minimum significant value to return
664 | * @param absMax Absolute value of the maximum value to return
665 | * @return The clamped value with the same sign as value
666 | */
667 | private float clampMag(float value, float absMin, float absMax) {
668 | final float absValue = Math.abs(value);
669 | if (absValue < absMin) return 0;
670 | if (absValue > absMax) return value > 0 ? absMax : -absMax;
671 | return value;
672 | }
673 |
674 | private float distanceInfluenceForSnapDuration(float f) {
675 | f -= 0.5f; // center the values about 0.
676 | f *= 0.3f * Math.PI / 2.0f;
677 | return (float) Math.sin(f);
678 | }
679 |
680 | /**
681 | * Settle the captured view based on standard free-moving fling behavior.
682 | * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
683 | * to continue the motion until it returns false.
684 | *
685 | * @param minLeft Minimum X position for the view's left edge
686 | * @param minTop Minimum Y position for the view's top edge
687 | * @param maxLeft Maximum X position for the view's left edge
688 | * @param maxTop Maximum Y position for the view's top edge
689 | */
690 | public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
691 | if (!mReleaseInProgress) {
692 | throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
693 | "Callback#onViewReleased");
694 | }
695 |
696 | mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
697 | (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
698 | (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
699 | minLeft, maxLeft, minTop, maxTop);
700 |
701 | setDragState(STATE_SETTLING);
702 | }
703 |
704 | /**
705 | * Move the captured settling view by the appropriate amount for the current time.
706 | * If continueSettling returns true, the caller should call it again
707 | * on the next frame to continue.
708 | *
709 | * @param deferCallbacks true if state callbacks should be deferred via posted message.
710 | * Set this to true if you are calling this method from
711 | * {@link android.view.View#computeScroll()} or similar methods
712 | * invoked as part of layout or drawing.
713 | * @return true if settle is still in progress
714 | */
715 | public boolean continueSettling(boolean deferCallbacks) {
716 | // Make sure, there is a captured view
717 | if (mCapturedView == null) {
718 | return false;
719 | }
720 | if (mDragState == STATE_SETTLING) {
721 | boolean keepGoing = mScroller.computeScrollOffset();
722 | final int x = mScroller.getCurrX();
723 | final int y = mScroller.getCurrY();
724 | final int dx = x - mCapturedView.getLeft();
725 | final int dy = y - mCapturedView.getTop();
726 |
727 | if (dx != 0) {
728 | mCapturedView.offsetLeftAndRight(dx);
729 | }
730 | if (dy != 0) {
731 | mCapturedView.offsetTopAndBottom(dy);
732 | }
733 |
734 | if (dx != 0 || dy != 0) {
735 | mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
736 | }
737 |
738 | if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
739 | // Close enough. The interpolator/scroller might think we're still moving
740 | // but the user sure doesn't.
741 | mScroller.abortAnimation();
742 | keepGoing = mScroller.isFinished();
743 | }
744 |
745 | if (!keepGoing) {
746 | if (deferCallbacks) {
747 | mParentView.post(mSetIdleRunnable);
748 | } else {
749 | setDragState(STATE_IDLE);
750 | }
751 | }
752 | }
753 |
754 | return mDragState == STATE_SETTLING;
755 | }
756 |
757 | /**
758 | * Like all callback events this must happen on the UI thread, but release
759 | * involves some extra semantics. During a release (mReleaseInProgress)
760 | * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
761 | * or {@link #flingCapturedView(int, int, int, int)}.
762 | */
763 | private void dispatchViewReleased(float xvel, float yvel) {
764 | mReleaseInProgress = true;
765 | mCallback.onViewReleased(mCapturedView, xvel, yvel);
766 | mReleaseInProgress = false;
767 |
768 | if (mDragState == STATE_DRAGGING) {
769 | // onViewReleased didn't call a method that would have changed this. Go idle.
770 | setDragState(STATE_IDLE);
771 | }
772 | }
773 |
774 | private void clearMotionHistory() {
775 | if (mInitialMotionX == null) {
776 | return;
777 | }
778 | Arrays.fill(mInitialMotionX, 0);
779 | Arrays.fill(mInitialMotionY, 0);
780 | Arrays.fill(mLastMotionX, 0);
781 | Arrays.fill(mLastMotionY, 0);
782 | Arrays.fill(mInitialEdgesTouched, 0);
783 | Arrays.fill(mEdgeDragsInProgress, 0);
784 | Arrays.fill(mEdgeDragsLocked, 0);
785 | mPointersDown = 0;
786 | }
787 |
788 | private void clearMotionHistory(int pointerId) {
789 | if (mInitialMotionX == null) {
790 | return;
791 | }
792 | mInitialMotionX[pointerId] = 0;
793 | mInitialMotionY[pointerId] = 0;
794 | mLastMotionX[pointerId] = 0;
795 | mLastMotionY[pointerId] = 0;
796 | mInitialEdgesTouched[pointerId] = 0;
797 | mEdgeDragsInProgress[pointerId] = 0;
798 | mEdgeDragsLocked[pointerId] = 0;
799 | mPointersDown &= ~(1 << pointerId);
800 | }
801 |
802 | private void ensureMotionHistorySizeForId(int pointerId) {
803 | if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
804 | float[] imx = new float[pointerId + 1];
805 | float[] imy = new float[pointerId + 1];
806 | float[] lmx = new float[pointerId + 1];
807 | float[] lmy = new float[pointerId + 1];
808 | int[] iit = new int[pointerId + 1];
809 | int[] edip = new int[pointerId + 1];
810 | int[] edl = new int[pointerId + 1];
811 |
812 | if (mInitialMotionX != null) {
813 | System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
814 | System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
815 | System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
816 | System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
817 | System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
818 | System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
819 | System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
820 | }
821 |
822 | mInitialMotionX = imx;
823 | mInitialMotionY = imy;
824 | mLastMotionX = lmx;
825 | mLastMotionY = lmy;
826 | mInitialEdgesTouched = iit;
827 | mEdgeDragsInProgress = edip;
828 | mEdgeDragsLocked = edl;
829 | }
830 | }
831 |
832 | private void saveInitialMotion(float x, float y, int pointerId) {
833 | ensureMotionHistorySizeForId(pointerId);
834 | mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
835 | mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
836 | mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
837 | mPointersDown |= 1 << pointerId;
838 | }
839 |
840 | private void saveLastMotion(MotionEvent ev) {
841 | final int pointerCount = MotionEventCompat.getPointerCount(ev);
842 | for (int i = 0; i < pointerCount; i++) {
843 | final int pointerId = MotionEventCompat.getPointerId(ev, i);
844 | final float x = MotionEventCompat.getX(ev, i);
845 | final float y = MotionEventCompat.getY(ev, i);
846 | if (mLastMotionX != null && mLastMotionY != null) {
847 | mLastMotionX[pointerId] = x;
848 | mLastMotionY[pointerId] = y;
849 | }
850 | }
851 | }
852 |
853 | /**
854 | * Check if the given pointer ID represents a pointer that is currently down (to the best
855 | * of the ViewDragHelper's knowledge).
856 | *
857 | *
The state used to report this information is populated by the methods
858 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
859 | * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not
860 | * been called for all relevant MotionEvents to track, the information reported
861 | * by this method may be stale or incorrect.
862 | *
863 | * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent
864 | * @return true if the pointer with the given ID is still down
865 | */
866 | public boolean isPointerDown(int pointerId) {
867 | return (mPointersDown & 1 << pointerId) != 0;
868 | }
869 |
870 | void setDragState(int state) {
871 | if (mDragState != state) {
872 | mDragState = state;
873 | mCallback.onViewDragStateChanged(state);
874 | if (state == STATE_IDLE) {
875 | mCapturedView = null;
876 | }
877 | }
878 | }
879 |
880 | /**
881 | * Attempt to capture the view with the given pointer ID. The callback will be involved.
882 | * This will put us into the "dragging" state. If we've already captured this view with
883 | * this pointer this method will immediately return true without consulting the callback.
884 | *
885 | * @param toCapture View to capture
886 | * @param pointerId Pointer to capture with
887 | * @return true if capture was successful
888 | */
889 | boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
890 | if (toCapture == mCapturedView && mActivePointerId == pointerId) {
891 | // Already done!
892 | return true;
893 | }
894 | if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
895 | mActivePointerId = pointerId;
896 | captureChildView(toCapture, pointerId);
897 | return true;
898 | }
899 | return false;
900 | }
901 |
902 | /**
903 | * Tests scrollability within child views of v given a delta of dx.
904 | *
905 | * @param v View to test for horizontal scrollability
906 | * @param checkV Whether the view v passed should itself be checked for scrollability (true),
907 | * or just its children (false).
908 | * @param dx Delta scrolled in pixels along the X axis
909 | * @param dy Delta scrolled in pixels along the Y axis
910 | * @param x X coordinate of the active touch point
911 | * @param y Y coordinate of the active touch point
912 | * @return true if child views of v can be scrolled by delta of dx.
913 | */
914 | protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
915 | if (v instanceof ViewGroup) {
916 | final ViewGroup group = (ViewGroup) v;
917 | final int scrollX = v.getScrollX();
918 | final int scrollY = v.getScrollY();
919 | final int count = group.getChildCount();
920 | // Count backwards - let topmost views consume scroll distance first.
921 | for (int i = count - 1; i >= 0; i--) {
922 | // TODO: Add versioned support here for transformed views.
923 | // This will not work for transformed views in Honeycomb+
924 | final View child = group.getChildAt(i);
925 | if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
926 | y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
927 | canScroll(child, true, dx, dy, x + scrollX - child.getLeft(),
928 | y + scrollY - child.getTop())) {
929 | return true;
930 | }
931 | }
932 | }
933 |
934 | return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||
935 | ViewCompat.canScrollVertically(v, -dy));
936 | }
937 |
938 | /**
939 | * Check if this event as provided to the parent view's onInterceptTouchEvent should
940 | * cause the parent to intercept the touch event stream.
941 | *
942 | * @param ev MotionEvent provided to onInterceptTouchEvent
943 | * @return true if the parent view should return true from onInterceptTouchEvent
944 | */
945 | public boolean shouldInterceptTouchEvent(MotionEvent ev) {
946 | final int action = MotionEventCompat.getActionMasked(ev);
947 | final int actionIndex = MotionEventCompat.getActionIndex(ev);
948 |
949 | if (action == MotionEvent.ACTION_DOWN) {
950 | // Reset things for a new event stream, just in case we didn't get
951 | // the whole previous stream.
952 | cancel();
953 | }
954 |
955 | if (mVelocityTracker == null) {
956 | mVelocityTracker = VelocityTracker.obtain();
957 | }
958 | mVelocityTracker.addMovement(ev);
959 |
960 | switch (action) {
961 | case MotionEvent.ACTION_DOWN: {
962 | final float x = ev.getX();
963 | final float y = ev.getY();
964 | final int pointerId = MotionEventCompat.getPointerId(ev, 0);
965 | saveInitialMotion(x, y, pointerId);
966 |
967 | final View toCapture = findTopChildUnder((int) x, (int) y);
968 |
969 | // Catch a settling view if possible.
970 | if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
971 | tryCaptureViewForDrag(toCapture, pointerId);
972 | }
973 |
974 | final int edgesTouched = mInitialEdgesTouched[pointerId];
975 | if ((edgesTouched & mTrackingEdges) != 0) {
976 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
977 | }
978 | break;
979 | }
980 |
981 | case MotionEventCompat.ACTION_POINTER_DOWN: {
982 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
983 | final float x = MotionEventCompat.getX(ev, actionIndex);
984 | final float y = MotionEventCompat.getY(ev, actionIndex);
985 |
986 | saveInitialMotion(x, y, pointerId);
987 |
988 | // A ViewDragHelper can only manipulate one view at a time.
989 | if (mDragState == STATE_IDLE) {
990 | final int edgesTouched = mInitialEdgesTouched[pointerId];
991 | if ((edgesTouched & mTrackingEdges) != 0) {
992 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
993 | }
994 | } else if (mDragState == STATE_SETTLING) {
995 | // Catch a settling view if possible.
996 | final View toCapture = findTopChildUnder((int) x, (int) y);
997 | if (toCapture == mCapturedView) {
998 | tryCaptureViewForDrag(toCapture, pointerId);
999 | }
1000 | }
1001 | break;
1002 | }
1003 |
1004 | case MotionEvent.ACTION_MOVE: {
1005 | // First to cross a touch slop over a draggable view wins. Also report edge drags.
1006 | final int pointerCount = MotionEventCompat.getPointerCount(ev);
1007 | for (int i = 0; i < pointerCount && mInitialMotionX != null && mInitialMotionY != null; i++) {
1008 | final int pointerId = MotionEventCompat.getPointerId(ev, i);
1009 | if (pointerId >= mInitialMotionX.length || pointerId >= mInitialMotionY.length) {
1010 | continue;
1011 | }
1012 | final float x = MotionEventCompat.getX(ev, i);
1013 | final float y = MotionEventCompat.getY(ev, i);
1014 | final float dx = x - mInitialMotionX[pointerId];
1015 | final float dy = y - mInitialMotionY[pointerId];
1016 |
1017 | reportNewEdgeDrags(dx, dy, pointerId);
1018 | if (mDragState == STATE_DRAGGING) {
1019 | // Callback might have started an edge drag
1020 | break;
1021 | }
1022 |
1023 | final View toCapture = findTopChildUnder((int)mInitialMotionX[pointerId], (int)mInitialMotionY[pointerId]);
1024 | if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
1025 | tryCaptureViewForDrag(toCapture, pointerId)) {
1026 | break;
1027 | }
1028 | }
1029 | saveLastMotion(ev);
1030 | break;
1031 | }
1032 |
1033 | case MotionEventCompat.ACTION_POINTER_UP: {
1034 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1035 | clearMotionHistory(pointerId);
1036 | break;
1037 | }
1038 |
1039 | case MotionEvent.ACTION_UP:
1040 | case MotionEvent.ACTION_CANCEL: {
1041 | cancel();
1042 | break;
1043 | }
1044 | }
1045 |
1046 | return mDragState == STATE_DRAGGING;
1047 | }
1048 |
1049 | /**
1050 | * Process a touch event received by the parent view. This method will dispatch callback events
1051 | * as needed before returning. The parent view's onTouchEvent implementation should call this.
1052 | *
1053 | * @param ev The touch event received by the parent view
1054 | */
1055 | public void processTouchEvent(MotionEvent ev) {
1056 | final int action = MotionEventCompat.getActionMasked(ev);
1057 | final int actionIndex = MotionEventCompat.getActionIndex(ev);
1058 |
1059 | if (action == MotionEvent.ACTION_DOWN) {
1060 | // Reset things for a new event stream, just in case we didn't get
1061 | // the whole previous stream.
1062 | cancel();
1063 | }
1064 |
1065 | if (mVelocityTracker == null) {
1066 | mVelocityTracker = VelocityTracker.obtain();
1067 | }
1068 | mVelocityTracker.addMovement(ev);
1069 |
1070 | switch (action) {
1071 | case MotionEvent.ACTION_DOWN: {
1072 | final float x = ev.getX();
1073 | final float y = ev.getY();
1074 | final int pointerId = MotionEventCompat.getPointerId(ev, 0);
1075 | final View toCapture = findTopChildUnder((int) x, (int) y);
1076 |
1077 | saveInitialMotion(x, y, pointerId);
1078 |
1079 | // Since the parent is already directly processing this touch event,
1080 | // there is no reason to delay for a slop before dragging.
1081 | // Start immediately if possible.
1082 | tryCaptureViewForDrag(toCapture, pointerId);
1083 |
1084 | final int edgesTouched = mInitialEdgesTouched[pointerId];
1085 | if ((edgesTouched & mTrackingEdges) != 0) {
1086 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1087 | }
1088 | break;
1089 | }
1090 |
1091 | case MotionEventCompat.ACTION_POINTER_DOWN: {
1092 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1093 | final float x = MotionEventCompat.getX(ev, actionIndex);
1094 | final float y = MotionEventCompat.getY(ev, actionIndex);
1095 |
1096 | saveInitialMotion(x, y, pointerId);
1097 |
1098 | // A ViewDragHelper can only manipulate one view at a time.
1099 | if (mDragState == STATE_IDLE) {
1100 | // If we're idle we can do anything! Treat it like a normal down event.
1101 |
1102 | final View toCapture = findTopChildUnder((int) x, (int) y);
1103 | tryCaptureViewForDrag(toCapture, pointerId);
1104 |
1105 | final int edgesTouched = mInitialEdgesTouched[pointerId];
1106 | if ((edgesTouched & mTrackingEdges) != 0) {
1107 | mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1108 | }
1109 | } else if (isCapturedViewUnder((int) x, (int) y)) {
1110 | // We're still tracking a captured view. If the same view is under this
1111 | // point, we'll swap to controlling it with this pointer instead.
1112 | // (This will still work if we're "catching" a settling view.)
1113 |
1114 | tryCaptureViewForDrag(mCapturedView, pointerId);
1115 | }
1116 | break;
1117 | }
1118 |
1119 | case MotionEvent.ACTION_MOVE: {
1120 | if (mDragState == STATE_DRAGGING) {
1121 | final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1122 | final float x = MotionEventCompat.getX(ev, index);
1123 | final float y = MotionEventCompat.getY(ev, index);
1124 | final int idx = (int) (x - mLastMotionX[mActivePointerId]);
1125 | final int idy = (int) (y - mLastMotionY[mActivePointerId]);
1126 |
1127 | dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
1128 |
1129 | saveLastMotion(ev);
1130 | } else {
1131 | // Check to see if any pointer is now over a draggable view.
1132 | final int pointerCount = MotionEventCompat.getPointerCount(ev);
1133 | for (int i = 0; i < pointerCount; i++) {
1134 | final int pointerId = MotionEventCompat.getPointerId(ev, i)
1135 | ;
1136 | final float x = MotionEventCompat.getX(ev, i);
1137 | final float y = MotionEventCompat.getY(ev, i);
1138 | final float dx = x - mInitialMotionX[pointerId];
1139 | final float dy = y - mInitialMotionY[pointerId];
1140 |
1141 | reportNewEdgeDrags(dx, dy, pointerId);
1142 | if (mDragState == STATE_DRAGGING) {
1143 | // Callback might have started an edge drag.
1144 | break;
1145 | }
1146 |
1147 | final View toCapture = findTopChildUnder((int) x, (int) y);
1148 | if (checkTouchSlop(toCapture, dx, dy) &&
1149 | tryCaptureViewForDrag(toCapture, pointerId)) {
1150 | break;
1151 | }
1152 | }
1153 | saveLastMotion(ev);
1154 | }
1155 | break;
1156 | }
1157 |
1158 | case MotionEventCompat.ACTION_POINTER_UP: {
1159 | final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1160 | if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
1161 | // Try to find another pointer that's still holding on to the captured view.
1162 | int newActivePointer = INVALID_POINTER;
1163 | final int pointerCount = MotionEventCompat.getPointerCount(ev);
1164 | for (int i = 0; i < pointerCount; i++) {
1165 | final int id = MotionEventCompat.getPointerId(ev, i);
1166 | if (id == mActivePointerId) {
1167 | // This one's going away, skip.
1168 | continue;
1169 | }
1170 |
1171 | final float x = MotionEventCompat.getX(ev, i);
1172 | final float y = MotionEventCompat.getY(ev, i);
1173 | if (findTopChildUnder((int) x, (int) y) == mCapturedView &&
1174 | tryCaptureViewForDrag(mCapturedView, id)) {
1175 | newActivePointer = mActivePointerId;
1176 | break;
1177 | }
1178 | }
1179 |
1180 | if (newActivePointer == INVALID_POINTER) {
1181 | // We didn't find another pointer still touching the view, release it.
1182 | releaseViewForPointerUp();
1183 | }
1184 | }
1185 | clearMotionHistory(pointerId);
1186 | break;
1187 | }
1188 |
1189 | case MotionEvent.ACTION_UP: {
1190 | if (mDragState == STATE_DRAGGING) {
1191 | releaseViewForPointerUp();
1192 | }
1193 | cancel();
1194 | break;
1195 | }
1196 |
1197 | case MotionEvent.ACTION_CANCEL: {
1198 | if (mDragState == STATE_DRAGGING) {
1199 | dispatchViewReleased(0, 0);
1200 | }
1201 | cancel();
1202 | break;
1203 | }
1204 | }
1205 | }
1206 |
1207 | private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
1208 | int dragsStarted = 0;
1209 | if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
1210 | dragsStarted |= EDGE_LEFT;
1211 | }
1212 | if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
1213 | dragsStarted |= EDGE_TOP;
1214 | }
1215 | if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
1216 | dragsStarted |= EDGE_RIGHT;
1217 | }
1218 | if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
1219 | dragsStarted |= EDGE_BOTTOM;
1220 | }
1221 |
1222 | if (dragsStarted != 0) {
1223 | mEdgeDragsInProgress[pointerId] |= dragsStarted;
1224 | mCallback.onEdgeDragStarted(dragsStarted, pointerId);
1225 | }
1226 | }
1227 |
1228 | private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
1229 | final float absDelta = Math.abs(delta);
1230 | final float absODelta = Math.abs(odelta);
1231 |
1232 | if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 ||
1233 | (mEdgeDragsLocked[pointerId] & edge) == edge ||
1234 | (mEdgeDragsInProgress[pointerId] & edge) == edge ||
1235 | (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
1236 | return false;
1237 | }
1238 | if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
1239 | mEdgeDragsLocked[pointerId] |= edge;
1240 | return false;
1241 | }
1242 | return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
1243 | }
1244 |
1245 | /**
1246 | * Check if we've crossed a reasonable touch slop for the given child view.
1247 | * If the child cannot be dragged along the horizontal or vertical axis, motion
1248 | * along that axis will not count toward the slop check.
1249 | *
1250 | * @param child Child to check
1251 | * @param dx Motion since initial position along X axis
1252 | * @param dy Motion since initial position along Y axis
1253 | * @return true if the touch slop has been crossed
1254 | */
1255 | private boolean checkTouchSlop(View child, float dx, float dy) {
1256 | if (child == null) {
1257 | return false;
1258 | }
1259 | final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
1260 | final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
1261 |
1262 | if (checkHorizontal && checkVertical) {
1263 | return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
1264 | } else if (checkHorizontal) {
1265 | return Math.abs(dx) > mTouchSlop;
1266 | } else if (checkVertical) {
1267 | return Math.abs(dy) > mTouchSlop;
1268 | }
1269 | return false;
1270 | }
1271 |
1272 | /**
1273 | * Check if any pointer tracked in the current gesture has crossed
1274 | * the required slop threshold.
1275 | *
1276 | *
This depends on internal state populated by
1277 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
1278 | * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
1279 | * the results of this method after all currently available touch data
1280 | * has been provided to one of these two methods.
1281 | *
1282 | * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
1283 | * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
1284 | * @return true if the slop threshold has been crossed, false otherwise
1285 | */
1286 | public boolean checkTouchSlop(int directions) {
1287 | final int count = mInitialMotionX.length;
1288 | for (int i = 0; i < count; i++) {
1289 | if (checkTouchSlop(directions, i)) {
1290 | return true;
1291 | }
1292 | }
1293 | return false;
1294 | }
1295 |
1296 | /**
1297 | * Check if the specified pointer tracked in the current gesture has crossed
1298 | * the required slop threshold.
1299 | *
1300 | *
This depends on internal state populated by
1301 | * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
1302 | * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
1303 | * the results of this method after all currently available touch data
1304 | * has been provided to one of these two methods.
1305 | *
1306 | * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
1307 | * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
1308 | * @param pointerId ID of the pointer to slop check as specified by MotionEvent
1309 | * @return true if the slop threshold has been crossed, false otherwise
1310 | */
1311 | public boolean checkTouchSlop(int directions, int pointerId) {
1312 | if (!isPointerDown(pointerId)) {
1313 | return false;
1314 | }
1315 |
1316 | final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL;
1317 | final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL;
1318 |
1319 | final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId];
1320 | final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId];
1321 |
1322 | if (checkHorizontal && checkVertical) {
1323 | return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
1324 | } else if (checkHorizontal) {
1325 | return Math.abs(dx) > mTouchSlop;
1326 | } else if (checkVertical) {
1327 | return Math.abs(dy) > mTouchSlop;
1328 | }
1329 | return false;
1330 | }
1331 |
1332 | /**
1333 | * Check if any of the edges specified were initially touched in the currently active gesture.
1334 | * If there is no currently active gesture this method will return false.
1335 | *
1336 | * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
1337 | * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
1338 | * {@link #EDGE_ALL}
1339 | * @return true if any of the edges specified were initially touched in the current gesture
1340 | */
1341 | public boolean isEdgeTouched(int edges) {
1342 | final int count = mInitialEdgesTouched.length;
1343 | for (int i = 0; i < count; i++) {
1344 | if (isEdgeTouched(edges, i)) {
1345 | return true;
1346 | }
1347 | }
1348 | return false;
1349 | }
1350 |
1351 | /**
1352 | * Check if any of the edges specified were initially touched by the pointer with
1353 | * the specified ID. If there is no currently active gesture or if there is no pointer with
1354 | * the given ID currently down this method will return false.
1355 | *
1356 | * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
1357 | * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
1358 | * {@link #EDGE_ALL}
1359 | * @return true if any of the edges specified were initially touched in the current gesture
1360 | */
1361 | public boolean isEdgeTouched(int edges, int pointerId) {
1362 | return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0;
1363 | }
1364 |
1365 | public boolean isDragging() {
1366 | return mDragState == STATE_DRAGGING;
1367 | }
1368 |
1369 | private void releaseViewForPointerUp() {
1370 | mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
1371 | final float xvel = clampMag(
1372 | VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
1373 | mMinVelocity, mMaxVelocity);
1374 | final float yvel = clampMag(
1375 | VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
1376 | mMinVelocity, mMaxVelocity);
1377 | dispatchViewReleased(xvel, yvel);
1378 | }
1379 |
1380 | private void dragTo(int left, int top, int dx, int dy) {
1381 | int clampedX = left;
1382 | int clampedY = top;
1383 | final int oldLeft = mCapturedView.getLeft();
1384 | final int oldTop = mCapturedView.getTop();
1385 | if (dx != 0) {
1386 | clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
1387 | mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
1388 | }
1389 | if (dy != 0) {
1390 | clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
1391 | mCapturedView.offsetTopAndBottom(clampedY - oldTop);
1392 | }
1393 |
1394 | if (dx != 0 || dy != 0) {
1395 | final int clampedDx = clampedX - oldLeft;
1396 | final int clampedDy = clampedY - oldTop;
1397 | mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
1398 | clampedDx, clampedDy);
1399 | }
1400 | }
1401 |
1402 | /**
1403 | * Determine if the currently captured view is under the given point in the
1404 | * parent view's coordinate system. If there is no captured view this method
1405 | * will return false.
1406 | *
1407 | * @param x X position to test in the parent's coordinate system
1408 | * @param y Y position to test in the parent's coordinate system
1409 | * @return true if the captured view is under the given point, false otherwise
1410 | */
1411 | public boolean isCapturedViewUnder(int x, int y) {
1412 | return isViewUnder(mCapturedView, x, y);
1413 | }
1414 |
1415 | /**
1416 | * Determine if the supplied view is under the given point in the
1417 | * parent view's coordinate system.
1418 | *
1419 | * @param view Child view of the parent to hit test
1420 | * @param x X position to test in the parent's coordinate system
1421 | * @param y Y position to test in the parent's coordinate system
1422 | * @return true if the supplied view is under the given point, false otherwise
1423 | */
1424 | public boolean isViewUnder(View view, int x, int y) {
1425 | if (view == null) {
1426 | return false;
1427 | }
1428 | return x >= view.getLeft() &&
1429 | x < view.getRight() &&
1430 | y >= view.getTop() &&
1431 | y < view.getBottom();
1432 | }
1433 |
1434 | /**
1435 | * Find the topmost child under the given point within the parent view's coordinate system.
1436 | * The child order is determined using {@link Callback#getOrderedChildIndex(int)}.
1437 | *
1438 | * @param x X position to test in the parent's coordinate system
1439 | * @param y Y position to test in the parent's coordinate system
1440 | * @return The topmost child view under (x, y) or null if none found.
1441 | */
1442 | public View findTopChildUnder(int x, int y) {
1443 | final int childCount = mParentView.getChildCount();
1444 | for (int i = childCount - 1; i >= 0; i--) {
1445 | final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
1446 | if (x >= child.getLeft() && x < child.getRight() &&
1447 | y >= child.getTop() && y < child.getBottom()) {
1448 | return child;
1449 | }
1450 | }
1451 | return null;
1452 | }
1453 |
1454 | private int getEdgesTouched(int x, int y) {
1455 | int result = 0;
1456 |
1457 | if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
1458 | if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
1459 | if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
1460 | if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;
1461 |
1462 | return result;
1463 | }
1464 | }
--------------------------------------------------------------------------------
/maven_push.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 | apply plugin: 'signing'
3 |
4 | def sonatypeRepositoryUrl
5 | if (isReleaseBuild()) {
6 | println 'RELEASE BUILD'
7 | sonatypeRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
8 | } else {
9 | println 'DEBUG BUILD'
10 | sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
11 | }
12 |
13 | afterEvaluate { project ->
14 | uploadArchives {
15 | repositories {
16 | mavenDeployer {
17 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
18 |
19 | pom.artifactId = POM_ARTIFACT_ID
20 |
21 | repository(url: sonatypeRepositoryUrl) {
22 | authentication(userName: nexusUsername, password: nexusPassword)
23 | }
24 |
25 | pom.project {
26 | name POM_NAME
27 | packaging POM_PACKAGING
28 | description POM_DESCRIPTION
29 | url POM_URL
30 |
31 | scm {
32 | url POM_SCM_URL
33 | connection POM_SCM_CONNECTION
34 | developerConnection POM_SCM_DEV_CONNECTION
35 | }
36 |
37 | licenses {
38 | license {
39 | name POM_LICENCE_NAME
40 | url POM_LICENCE_URL
41 | distribution POM_LICENCE_DIST
42 | }
43 | }
44 |
45 | developers {
46 | developer {
47 | id POM_DEVELOPER_ID
48 | name POM_DEVELOPER_NAME
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
56 | signing {
57 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
58 | sign configurations.archives
59 | }
60 |
61 | task androidJavadocs(type: Javadoc) {
62 | source = android.sourceSets.main.java.srcDirs
63 | }
64 |
65 | task androidJavadocsJar(type: Jar) {
66 | classifier = 'javadoc'
67 | //basename = artifact_id
68 | from androidJavadocs.destinationDir
69 | }
70 |
71 | task androidSourcesJar(type: Jar) {
72 | classifier = 'sources'
73 | //basename = artifact_id
74 | from android.sourceSets.main.java.srcDirs
75 | }
76 |
77 | artifacts {
78 | //archives packageReleaseJar
79 | archives androidSourcesJar
80 | archives androidJavadocsJar
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':library'
2 | include ':demo'
3 |
--------------------------------------------------------------------------------
/slidinguppanel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvesh523/AndroidSlidingUpPanel/e0e18acf43029d297b2f2753534ec1517b1145ee/slidinguppanel.png
--------------------------------------------------------------------------------