Repository: umano/AndroidSlidingUpPanel
Branch: master
Commit: 45a460435b07
Files: 29
Total size: 166.6 KB
Directory structure:
gitextract_is6jifad/
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── demo/
│ ├── build.gradle
│ ├── settings.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── sothree/
│ │ └── slidinguppanel/
│ │ └── demo/
│ │ └── DemoActivity.java
│ └── res/
│ ├── layout/
│ │ └── activity_demo.xml
│ ├── menu/
│ │ └── demo.xml
│ └── values/
│ ├── strings.xml
│ └── styles.xml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── sothree/
│ │ └── slidinguppanel/
│ │ ├── ScrollableViewHelper.java
│ │ ├── SlidingUpPanelLayout.java
│ │ └── ViewDragHelper.java
│ └── res/
│ ├── drawable/
│ │ ├── above_shadow.xml
│ │ └── below_shadow.xml
│ └── values/
│ └── attrs.xml
├── maven_push.gradle
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
#Android generated
bin
gen
gen*
#Eclipse
.project
.classpath
.settings
#IntelliJ IDEA
.idea
*.iml
*.ipr
*.iws
out
#Maven
target
release.properties
pom.xml.*
#Ant
build.xml
local.properties
proguard.cfg
#Gradle
.gradle
build
#OSX
.DS_Store
#Personal Files
signing.properties
================================================
FILE: .travis.yml
================================================
language: android
jdk: oraclejdk7
android:
components:
- tools
- platform-tools
- extra-android-m2repository
- extra-google-m2repository
- build-tools-26.0.0
- android-26
# Enable travis container based infrastructure
sudo: false
before_script:
- chmod +x gradlew
script:
- ./gradlew clean assemble
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Anton Lopyrev
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
[](https://maven-badges.herokuapp.com/maven-central/com.sothree.slidinguppanel/library)
[](http://www.libtastic.com/technology/30/)
**Note:** we are **not** actively responding to issues right now. If you find a bug, please submit a PR.
Android Sliding Up Panel
=========================
This library provides a simple way to add a draggable sliding up panel (popularized by Google Music and Google Maps) to your Android application.
As seen in Umano Android App (now acquired by Dropbox):

### Known Uses in Popular Apps
* [Soundcloud] (https://play.google.com/store/apps/details?id=com.soundcloud.android)
* [Dropbox Paper] (https://play.google.com/store/apps/details?id=com.dropbox.paper)
* [Snaptee] (https://play.google.com/store/apps/details?id=co.snaptee.android)
If you are using the library and you would like to have your app listed, simply let us know.
### Importing the Library
Simply add the following dependency to your `build.gradle` file to use the latest version:
```groovy
dependencies {
repositories {
mavenCentral()
}
compile 'com.sothree.slidinguppanel:library:3.4.0'
}
```
### Usage
* Include `com.sothree.slidinguppanel.SlidingUpPanelLayout` as the root element in your activity layout.
* The layout must have `gravity` set to either `top` or `bottom`.
* 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.
* The main layout should have the width and the height set to `match_parent`.
* 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. If you would like to define the height as the percetange of the screen, set it to `match_parent` and also define a `layout_weight` attribute for the sliding view.
* 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.
For more information, please refer to the sample code.
```xml
<com.sothree.slidinguppanel.SlidingUpPanelLayout
xmlns:sothree="http://schemas.android.com/apk/res-auto"
android:id="@+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
sothree:umanoPanelHeight="68dp"
sothree:umanoShadowHeight="4dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Main Content"
android:textSize="16sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|top"
android:text="The Awesome Sliding Up Panel"
android:textSize="16sp" />
</com.sothree.slidinguppanel.SlidingUpPanelLayout>
```
For smooth interaction with the ActionBar, make sure that `windowActionBarOverlay` is set to `true` in your styles:
```xml
<style name="AppTheme">
<item name="android:windowActionBarOverlay">true</item>
</style>
```
However, in this case you would likely want to add a top margin to your main layout of `?android:attr/actionBarSize`
or `?attr/actionBarSize` to support older API versions.
### Caveats, Additional Features and Customization
* 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.
* You can change the panel height by using the `setPanelHeight` method or `umanoPanelHeight` attribute.
* If you would like to hide the shadow above the sliding panel, set `shadowHeight` attribute to 0.
* Use `setEnabled(false)` to completely disable the sliding panel (including touch and programmatic sliding)
* Use `setTouchEnabled(false)` to disables panel's touch responsiveness (drag and click), you can still control the panel programatically
* Use `getPanelState` to get the current panel state
* Use `setPanelState` to set the current panel state
* You can add parallax to the main view by setting `umanoParallaxOffset` attribute (see demo for the example).
* 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).
* You can set a `PanelSlideListener` to monitor events about sliding panes.
* You can also make the panel slide from the top by changing the `layout_gravity` attribute of the layout to `top`.
* You can provide a scroll interpolator for the panel movement by setting `umanoScrollInterpolator` attribute. For instance, if you want a bounce or overshoot effect for the panel.
* 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.
* 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.
### Scrollable Sliding Views
If you have a scrollable view inside of the sliding panel, make sure to set `umanoScrollableView` attribute on the panel to supported nested scrolling. The panel supports `ListView`, `ScrollView` and `RecyclerView` out of the box, but you can add support for any type of a scrollable view by setting a custom `ScrollableViewHelper`. Here is an example for `NestedScrollView`
```
public class NestedScrollableViewHelper extends ScrollableViewHelper {
public int getScrollableViewScrollPosition(View scrollableView, boolean isSlidingUp) {
if (mScrollableView instanceof NestedScrollView) {
if(isSlidingUp){
return mScrollableView.getScrollY();
} else {
NestedScrollView nsv = ((NestedScrollView) mScrollableView);
View child = nsv.getChildAt(0);
return (child.getBottom() - (nsv.getHeight() + nsv.getScrollY()));
}
} else {
return 0;
}
}
}
```
Once you define your helper, you can set it using `setScrollableViewHelper` on the sliding panel.
### Implementation
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!
### Requirements
Tested on Android 2.2+
### Other Contributors
* Nov 23, 15 - [@kiyeonk](https://github.com/kiyeonk) - umanoScrollInterpolator support
* Jan 21, 14 - ChaYoung You ([@yous](https://github.com/yous)) - Slide from the top support
* Aug 20, 13 - [@gipi](https://github.com/gipi) - Android Studio Support
* Jul 24, 13 - Philip Schiffer ([@hameno](https://github.com/hameno)) - Maven Support
* Oct 20, 13 - Irina Preșa ([@iriina](https://github.com/iriina)) - Anchor Support
* Dec 1, 13 - ([@youchy](https://github.com/youchy)) - XML Attributes Support
* Dec 22, 13 - Vladimir Mironov ([@MironovNsk](https://github.com/nsk-mironov)) - Custom Expanded Panel Height
If you have an awesome pull request, send it over!
### Changelog
* 3.4.0
* Use the latest support library 26 and update the min version to 14.
* Bug fixes
* 3.3.1
* Lots of bug fixes from various pull requests.
* Removed the nineoldandroids dependency. Use ViewCompat instead.
* 3.3.0
* You can now set a `FadeOnClickListener`, for when the faded area of the main content is clicked.
* `PanelSlideListener` has a new format (multiple of them can be set now
* Fixed the setTouchEnabled bug
* 3.2.1
* Add support for `umanoScrollInterpolator`
* Add support for percentage-based sliding panel height using `layout_weight` attribute
* Add `ScrollableViewHelper` to allow users extend support for new types of scrollable views.
* 3.2.0
* Rename `umanoParalaxOffset` to `umanoParallaxOffset`
* RecyclerView support.
* 3.1.0
* Added `umanoScrollableView` to supported nested scrolling in children (only ScrollView and ListView are supported for now)
* 3.0.0
* Added `umano` prefix for all attributes
* Added `clipPanel` attribute for supporting transparent panels in non-overlay mode.
* `setEnabled(false)` - now completely disables the sliding panel (touch and programmatic sliding)
* `setTouchEnabled(false)` - disables panel's touch responsiveness (drag and click), you can still control the panel programatically
* `getPanelState` - is now the only method to get the current panel state
* `setPanelState` - is now the only method to modify the panel state from code
* 2.0.2 - Allow `wrap_content` for sliding view height attribute. Bug fixes.
* 2.0.1 - Bug fixes.
* 2.0.0 - Cleaned up various public method calls. Added animated `showPanel`/`hidePanel` methods.
* 1.0.1 - Initial Release
### Licence
> Licensed under the Apache License, Version 2.0 (the "License");
> you may not use this work except in compliance with the License.
> You may obtain a copy of the License in the LICENSE file, or at:
>
> [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
>
> Unless required by applicable law or agreed to in writing, software
> distributed under the License is distributed on an "AS IS" BASIS,
> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> See the License for the specific language governing permissions and
> limitations under the License.
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
}
}
def isReleaseBuild() {
return version.contains("SNAPSHOT") == false
}
allprojects {
version = VERSION_NAME
group = GROUP
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '3.3'
distributionUrl = "https://services.gradle.org/distributions/gradle-${gradleVersion}-all.zip"
}
================================================
FILE: demo/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
lintOptions {
abortOnError false
}
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
}
}
dependencies {
compile 'com.android.support:support-v4:26.0.2'
compile 'com.android.support:appcompat-v7:26.0.2'
compile project(':library')
}
================================================
FILE: demo/settings.gradle
================================================
include ':Tnt2'
include 'androidslidingup'
project(':androidslidingup').projectDir = new File(settingsDir, '../library/')
================================================
FILE: demo/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sothree.slidinguppanel.demo"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="17"
android:versionName="3.4.0">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="17"
tools:overrideLibrary="android.support.v7.appcompat,android.support.graphics.drawavle"
/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="com.sothree.slidinguppanel.demo.DemoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: demo/src/main/java/com/sothree/slidinguppanel/demo/DemoActivity.java
================================================
package com.sothree.slidinguppanel.demo;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelSlideListener;
import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelState;
import java.util.Arrays;
import java.util.List;
public class DemoActivity extends AppCompatActivity {
private static final String TAG = "DemoActivity";
private SlidingUpPanelLayout mLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
setSupportActionBar((Toolbar) findViewById(R.id.main_toolbar));
ListView lv = (ListView) findViewById(R.id.list);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(DemoActivity.this, "onItemClick", Toast.LENGTH_SHORT).show();
}
});
List<String> your_array_list = Arrays.asList(
"This",
"Is",
"An",
"Example",
"ListView",
"That",
"You",
"Can",
"Scroll",
".",
"It",
"Shows",
"How",
"Any",
"Scrollable",
"View",
"Can",
"Be",
"Included",
"As",
"A",
"Child",
"Of",
"SlidingUpPanelLayout"
);
// This is the array adapter, it takes the context of the activity as a
// first parameter, the type of list view as a second parameter and your
// array as a third parameter.
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
your_array_list );
lv.setAdapter(arrayAdapter);
mLayout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
mLayout.addPanelSlideListener(new PanelSlideListener() {
@Override
public void onPanelSlide(View panel, float slideOffset) {
Log.i(TAG, "onPanelSlide, offset " + slideOffset);
}
@Override
public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
Log.i(TAG, "onPanelStateChanged " + newState);
}
});
mLayout.setFadeOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mLayout.setPanelState(PanelState.COLLAPSED);
}
});
TextView t = (TextView) findViewById(R.id.name);
t.setText(Html.fromHtml(getString(R.string.hello)));
Button f = (Button) findViewById(R.id.follow);
f.setText(Html.fromHtml(getString(R.string.follow)));
f.setMovementMethod(LinkMovementMethod.getInstance());
f.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://www.twitter.com/umanoapp"));
startActivity(i);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.demo, menu);
MenuItem item = menu.findItem(R.id.action_toggle);
if (mLayout != null) {
if (mLayout.getPanelState() == PanelState.HIDDEN) {
item.setTitle(R.string.action_show);
} else {
item.setTitle(R.string.action_hide);
}
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.action_toggle: {
if (mLayout != null) {
if (mLayout.getPanelState() != PanelState.HIDDEN) {
mLayout.setPanelState(PanelState.HIDDEN);
item.setTitle(R.string.action_show);
} else {
mLayout.setPanelState(PanelState.COLLAPSED);
item.setTitle(R.string.action_hide);
}
}
return true;
}
case R.id.action_anchor: {
if (mLayout != null) {
if (mLayout.getAnchorPoint() == 1.0f) {
mLayout.setAnchorPoint(0.7f);
mLayout.setPanelState(PanelState.ANCHORED);
item.setTitle(R.string.action_anchor_disable);
} else {
mLayout.setAnchorPoint(1.0f);
mLayout.setPanelState(PanelState.COLLAPSED);
item.setTitle(R.string.action_anchor_enable);
}
}
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (mLayout != null &&
(mLayout.getPanelState() == PanelState.EXPANDED || mLayout.getPanelState() == PanelState.ANCHORED)) {
mLayout.setPanelState(PanelState.COLLAPSED);
} else {
super.onBackPressed();
}
}
}
================================================
FILE: demo/src/main/res/layout/activity_demo.xml
================================================
<com.sothree.slidinguppanel.SlidingUpPanelLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:sothree="http://schemas.android.com/apk/res-auto"
android:id="@+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
sothree:umanoPanelHeight="68dp"
sothree:umanoShadowHeight="4dp"
sothree:umanoParallaxOffset="100dp"
sothree:umanoDragView="@+id/dragView"
sothree:umanoOverlay="true"
sothree:umanoScrollableView="@+id/list">
<!-- MAIN CONTENT -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
xmlns:sothree="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_toolbar"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
sothree:theme="@style/ActionBar"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:gravity="center"
android:text="Main Content"
android:clickable="true"
android:focusable="false"
android:focusableInTouchMode="true"
android:textSize="16sp" />
</FrameLayout>
<!-- SLIDING LAYOUT -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical"
android:clickable="true"
android:focusable="false"
android:id="@+id/dragView">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="68dp"
android:orientation="horizontal">
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textSize="14sp"
android:gravity="center_vertical"
android:paddingLeft="10dp"/>
<Button
android:id="@+id/follow"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textSize="14sp"
android:gravity="center_vertical|right"
android:paddingRight="10dp"
android:paddingLeft="10dp"/>
</LinearLayout>
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</ListView>
<!--<ScrollView-->
<!--android:id="@+id/sv"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="0dp"-->
<!--android:layout_weight="1"-->
<!-->-->
<!--<TextView-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="The standard Lorem Ipsum passage, used since the 1500Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Section 1.10.32 of written by Cicero in 45 t perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?1914 translation by H. RackhamBut I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete accouof the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."/>-->
<!--</ScrollView>-->
</LinearLayout>
</com.sothree.slidinguppanel.SlidingUpPanelLayout>
================================================
FILE: demo/src/main/res/menu/demo.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sothree="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_toggle"
android:orderInCategory="100"
sothree:showAsAction="never"
android:title="@string/action_hide"/>
<item
android:id="@+id/action_anchor"
android:orderInCategory="200"
sothree:showAsAction="never"
android:title="@string/action_anchor_enable"/>
</menu>
================================================
FILE: demo/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Sliding Up Panel Demo</string>
<string name="action_settings">Settings</string>
<string name="action_hide">Hide Panel</string>
<string name="action_show">Show Panel</string>
<string name="action_anchor_enable">Enable Anchor Point</string>
<string name="action_anchor_disable">Disable Anchor Point</string>
<string name="hello"><![CDATA[<b>The Awesome Sliding Up Panel</b><br/> Brought to you by<br/><a href="http://umanoapp.com">http://umanoapp.com</a>]]></string>
<string name="follow"><![CDATA[Follow us<br/>on <a href="http://twitter.com/umanoapp">Twitter</a>]]></string>
</resources>
================================================
FILE: demo/src/main/res/values/styles.xml
================================================
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#ffff9431</item>
<item name="colorPrimaryDark">#ffD56600</item>
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
<style name="ActionBar" parent="ThemeOverlay.AppCompat.ActionBar">
<item name="android:displayOptions">homeAsUp</item>
</style>
</resources>
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Tue Aug 23 22:44:32 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
================================================
FILE: gradle.properties
================================================
VERSION_NAME=3.4.0
VERSION_CODE=17
GROUP=com.sothree.slidinguppanel
POM_DESCRIPTION=Android Sliding Up Panel Library
POM_URL=https://github.com/umano/AndroidSlidingUpPanel
POM_SCM_URL=https://github.com/umano/AndroidSlidingUpPanel
POM_SCM_CONNECTION=scm:git@github.com:umano/AndroidSlidingUpPanel.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:umano/AndroidSlidingUpPanel.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=tokudu
POM_DEVELOPER_NAME=Anton Lopyrev
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: library/build.gradle
================================================
apply plugin: 'com.android.library'
repositories {
jcenter()
}
dependencies {
compile 'com.android.support:support-v4:26.0.2'
compile 'com.android.support:support-annotations:26.0.2'
compile 'com.android.support:recyclerview-v7:26.0.2'
}
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
lintOptions {
abortOnError false
}
}
apply from: '../maven_push.gradle'
================================================
FILE: library/gradle.properties
================================================
POM_NAME=Android Sliding Up Panel Library
POM_ARTIFACT_ID=library
POM_PACKAGING=aar
================================================
FILE: library/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sothree.slidinguppanel.library"
android:versionCode="17"
android:versionName="3.4.0">
<uses-sdk
android:minSdkVersion="14" />
<application />
</manifest>
================================================
FILE: library/src/main/java/com/sothree/slidinguppanel/ScrollableViewHelper.java
================================================
package com.sothree.slidinguppanel;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ListView;
import android.widget.ScrollView;
/**
* Helper class for determining the current scroll positions for scrollable views. Currently works
* for ListView, ScrollView and RecyclerView, but the library users can override it to add support
* for other views.
*/
public class ScrollableViewHelper {
/**
* Returns the current scroll position of the scrollable view. If this method returns zero or
* less, it means at the scrollable view is in a position such as the panel should handle
* scrolling. If the method returns anything above zero, then the panel will let the scrollable
* view handle the scrolling
*
* @param scrollableView the scrollable view
* @param isSlidingUp whether or not the panel is sliding up or down
* @return the scroll position
*/
public int getScrollableViewScrollPosition(View scrollableView, boolean isSlidingUp) {
if (scrollableView == null) return 0;
if (scrollableView instanceof ScrollView) {
if (isSlidingUp) {
return scrollableView.getScrollY();
} else {
ScrollView sv = ((ScrollView) scrollableView);
View child = sv.getChildAt(0);
return (child.getBottom() - (sv.getHeight() + sv.getScrollY()));
}
} else if (scrollableView instanceof ListView && ((ListView) scrollableView).getChildCount() > 0) {
ListView lv = ((ListView) scrollableView);
if (lv.getAdapter() == null) return 0;
if (isSlidingUp) {
View firstChild = lv.getChildAt(0);
// Approximate the scroll position based on the top child and the first visible item
return lv.getFirstVisiblePosition() * firstChild.getHeight() - firstChild.getTop();
} else {
View lastChild = lv.getChildAt(lv.getChildCount() - 1);
// Approximate the scroll position based on the bottom child and the last visible item
return (lv.getAdapter().getCount() - lv.getLastVisiblePosition() - 1) * lastChild.getHeight() + lastChild.getBottom() - lv.getBottom();
}
} else if (scrollableView instanceof RecyclerView && ((RecyclerView) scrollableView).getChildCount() > 0) {
RecyclerView rv = ((RecyclerView) scrollableView);
RecyclerView.LayoutManager lm = rv.getLayoutManager();
if (rv.getAdapter() == null) return 0;
if (isSlidingUp) {
View firstChild = rv.getChildAt(0);
// Approximate the scroll position based on the top child and the first visible item
return rv.getChildLayoutPosition(firstChild) * lm.getDecoratedMeasuredHeight(firstChild) - lm.getDecoratedTop(firstChild);
} else {
View lastChild = rv.getChildAt(rv.getChildCount() - 1);
// Approximate the scroll position based on the bottom child and the last visible item
return (rv.getAdapter().getItemCount() - 1) * lm.getDecoratedMeasuredHeight(lastChild) + lm.getDecoratedBottom(lastChild) - rv.getBottom();
}
} else {
return 0;
}
}
}
================================================
FILE: library/src/main/java/com/sothree/slidinguppanel/SlidingUpPanelLayout.java
================================================
package com.sothree.slidinguppanel;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.sothree.slidinguppanel.library.R;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class SlidingUpPanelLayout extends ViewGroup {
private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
/**
* Default peeking out panel height
*/
private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
/**
* Default anchor point height
*/
private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %
/**
* Default initial state for the component
*/
private static PanelState DEFAULT_SLIDE_STATE = PanelState.COLLAPSED;
/**
* Default height of the shadow above the peeking out panel
*/
private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
/**
* If no fade color is given by default it will fade to 80% gray.
*/
private static final int DEFAULT_FADE_COLOR = 0x99000000;
/**
* Default Minimum velocity that will be detected as a fling
*/
private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
/**
* Default is set to false because that is how it was written
*/
private static final boolean DEFAULT_OVERLAY_FLAG = false;
/**
* Default is set to true for clip panel for performance reasons
*/
private static final boolean DEFAULT_CLIP_PANEL_FLAG = true;
/**
* Default attributes for layout
*/
private static final int[] DEFAULT_ATTRS = new int[]{
android.R.attr.gravity
};
/**
* Tag for the sliding state stored inside the bundle
*/
public static final String SLIDING_STATE = "sliding_state";
/**
* Minimum velocity that will be detected as a fling
*/
private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
/**
* The fade color used for the panel covered by the slider. 0 = no fading.
*/
private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
/**
* Default parallax length of the main view
*/
private static final int DEFAULT_PARALLAX_OFFSET = 0;
/**
* The paint used to dim the main layout when sliding
*/
private final Paint mCoveredFadePaint = new Paint();
/**
* Drawable used to draw the shadow between panes.
*/
private final Drawable mShadowDrawable;
/**
* The size of the overhang in pixels.
*/
private int mPanelHeight = -1;
/**
* The size of the shadow in pixels.
*/
private int mShadowHeight = -1;
/**
* Parallax offset
*/
private int mParallaxOffset = -1;
/**
* True if the collapsed panel should be dragged up.
*/
private boolean mIsSlidingUp;
/**
* Panel overlays the windows instead of putting it underneath it.
*/
private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
/**
* The main view is clipped to the main top border
*/
private boolean mClipPanel = DEFAULT_CLIP_PANEL_FLAG;
/**
* If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
* used for dragging.
*/
private View mDragView;
/**
* If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
* used for dragging.
*/
private int mDragViewResId = -1;
/**
* If provided, the panel will transfer the scroll from this view to itself when needed.
*/
private View mScrollableView;
private int mScrollableViewResId;
private ScrollableViewHelper mScrollableViewHelper = new ScrollableViewHelper();
/**
* The child view that can slide, if any.
*/
private View mSlideableView;
/**
* The main view
*/
private View mMainView;
/**
* Current state of the slideable view.
*/
public enum PanelState {
EXPANDED,
COLLAPSED,
ANCHORED,
HIDDEN,
DRAGGING
}
private PanelState mSlideState = DEFAULT_SLIDE_STATE;
/**
* If the current slide state is DRAGGING, this will store the last non dragging state
*/
private PanelState mLastNotDraggingSlideState = DEFAULT_SLIDE_STATE;
/**
* How far the panel is offset from its expanded position.
* range [0, 1] where 0 = collapsed, 1 = expanded.
*/
private float mSlideOffset;
/**
* How far in pixels the slideable panel may move.
*/
private int mSlideRange;
/**
* An anchor point where the panel can stop during sliding
*/
private float mAnchorPoint = 1.f;
/**
* A panel view is locked into internal scrolling or another condition that
* is preventing a drag.
*/
private boolean mIsUnableToDrag;
/**
* Flag indicating that sliding feature is enabled\disabled
*/
private boolean mIsTouchEnabled;
private float mPrevMotionX;
private float mPrevMotionY;
private float mInitialMotionX;
private float mInitialMotionY;
private boolean mIsScrollableViewHandlingTouch = false;
private final List<PanelSlideListener> mPanelSlideListeners = new CopyOnWriteArrayList<>();
private View.OnClickListener mFadeOnClickListener;
private final ViewDragHelper mDragHelper;
/**
* Stores whether or not the pane was expanded the last time it was slideable.
* If expand/collapse operations are invoked this state is modified. Used by
* instance state save/restore.
*/
private boolean mFirstLayout = true;
private final Rect mTmpRect = new Rect();
/**
* Listener for monitoring events about sliding panes.
*/
public interface PanelSlideListener {
/**
* Called when a sliding pane's position changes.
*
* @param panel The child view that was moved
* @param slideOffset The new offset of this sliding pane within its range, from 0-1
*/
public void onPanelSlide(View panel, float slideOffset);
/**
* Called when a sliding panel state changes
*
* @param panel The child view that was slid to an collapsed position
*/
public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState);
}
/**
* No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
* of the listener methods you can extend this instead of implement the full interface.
*/
public static class SimplePanelSlideListener implements PanelSlideListener {
@Override
public void onPanelSlide(View panel, float slideOffset) {
}
@Override
public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
}
}
public SlidingUpPanelLayout(Context context) {
this(context, null);
}
public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) {
mShadowDrawable = null;
mDragHelper = null;
return;
}
Interpolator scrollerInterpolator = null;
if (attrs != null) {
TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
if (defAttrs != null) {
int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
setGravity(gravity);
defAttrs.recycle();
}
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
if (ta != null) {
mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoPanelHeight, -1);
mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoShadowHeight, -1);
mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoParallaxOffset, -1);
mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_umanoFlingVelocity, DEFAULT_MIN_FLING_VELOCITY);
mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_umanoFadeColor, DEFAULT_FADE_COLOR);
mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoDragView, -1);
mScrollableViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoScrollableView, -1);
mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoOverlay, DEFAULT_OVERLAY_FLAG);
mClipPanel = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoClipPanel, DEFAULT_CLIP_PANEL_FLAG);
mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_umanoAnchorPoint, DEFAULT_ANCHOR_POINT);
mSlideState = PanelState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_umanoInitialState, DEFAULT_SLIDE_STATE.ordinal())];
int interpolatorResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoScrollInterpolator, -1);
if (interpolatorResId != -1) {
scrollerInterpolator = AnimationUtils.loadInterpolator(context, interpolatorResId);
}
ta.recycle();
}
}
final float density = context.getResources().getDisplayMetrics().density;
if (mPanelHeight == -1) {
mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
}
if (mShadowHeight == -1) {
mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
}
if (mParallaxOffset == -1) {
mParallaxOffset = (int) (DEFAULT_PARALLAX_OFFSET * density);
}
// If the shadow height is zero, don't show the shadow
if (mShadowHeight > 0) {
if (mIsSlidingUp) {
mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
} else {
mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
}
} else {
mShadowDrawable = null;
}
setWillNotDraw(false);
mDragHelper = ViewDragHelper.create(this, 0.5f, scrollerInterpolator, new DragHelperCallback());
mDragHelper.setMinVelocity(mMinFlingVelocity * density);
mIsTouchEnabled = true;
}
/**
* Set the Drag View after the view is inflated
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (mDragViewResId != -1) {
setDragView(findViewById(mDragViewResId));
}
if (mScrollableViewResId != -1) {
setScrollableView(findViewById(mScrollableViewResId));
}
}
public void setGravity(int gravity) {
if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
throw new IllegalArgumentException("gravity must be set to either top or bottom");
}
mIsSlidingUp = gravity == Gravity.BOTTOM;
if (!mFirstLayout) {
requestLayout();
}
}
/**
* Set the color used to fade the pane covered by the sliding pane out when the pane
* will become fully covered in the expanded state.
*
* @param color An ARGB-packed color value
*/
public void setCoveredFadeColor(int color) {
mCoveredFadeColor = color;
requestLayout();
}
/**
* @return The ARGB-packed color value used to fade the fixed pane
*/
public int getCoveredFadeColor() {
return mCoveredFadeColor;
}
/**
* Set sliding enabled flag
*
* @param enabled flag value
*/
public void setTouchEnabled(boolean enabled) {
mIsTouchEnabled = enabled;
}
public boolean isTouchEnabled() {
return mIsTouchEnabled && mSlideableView != null && mSlideState != PanelState.HIDDEN;
}
/**
* Set the collapsed panel height in pixels
*
* @param val A height in pixels
*/
public void setPanelHeight(int val) {
if (getPanelHeight() == val) {
return;
}
mPanelHeight = val;
if (!mFirstLayout) {
requestLayout();
}
if (getPanelState() == PanelState.COLLAPSED) {
smoothToBottom();
invalidate();
return;
}
}
protected void smoothToBottom() {
smoothSlideTo(0, 0);
}
/**
* @return The current shadow height
*/
public int getShadowHeight() {
return mShadowHeight;
}
/**
* Set the shadow height
*
* @param val A height in pixels
*/
public void setShadowHeight(int val) {
mShadowHeight = val;
if (!mFirstLayout) {
invalidate();
}
}
/**
* @return The current collapsed panel height
*/
public int getPanelHeight() {
return mPanelHeight;
}
/**
* @return The current parallax offset
*/
public int getCurrentParallaxOffset() {
// Clamp slide offset at zero for parallax computation;
int offset = (int) (mParallaxOffset * Math.max(mSlideOffset, 0));
return mIsSlidingUp ? -offset : offset;
}
/**
* Set parallax offset for the panel
*
* @param val A height in pixels
*/
public void setParallaxOffset(int val) {
mParallaxOffset = val;
if (!mFirstLayout) {
requestLayout();
}
}
/**
* @return The current minimin fling velocity
*/
public int getMinFlingVelocity() {
return mMinFlingVelocity;
}
/**
* Sets the minimum fling velocity for the panel
*
* @param val the new value
*/
public void setMinFlingVelocity(int val) {
mMinFlingVelocity = val;
}
/**
* Adds a panel slide listener
*
* @param listener
*/
public void addPanelSlideListener(PanelSlideListener listener) {
synchronized (mPanelSlideListeners) {
mPanelSlideListeners.add(listener);
}
}
/**
* Removes a panel slide listener
*
* @param listener
*/
public void removePanelSlideListener(PanelSlideListener listener) {
synchronized (mPanelSlideListeners) {
mPanelSlideListeners.remove(listener);
}
}
/**
* Provides an on click for the portion of the main view that is dimmed. The listener is not
* triggered if the panel is in a collapsed or a hidden position. If the on click listener is
* not provided, the clicks on the dimmed area are passed through to the main layout.
*
* @param listener
*/
public void setFadeOnClickListener(View.OnClickListener listener) {
mFadeOnClickListener = listener;
}
/**
* Set the draggable view portion. Use to null, to allow the whole panel to be draggable
*
* @param dragView A view that will be used to drag the panel.
*/
public void setDragView(View dragView) {
if (mDragView != null) {
mDragView.setOnClickListener(null);
}
mDragView = dragView;
if (mDragView != null) {
mDragView.setClickable(true);
mDragView.setFocusable(false);
mDragView.setFocusableInTouchMode(false);
mDragView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!isEnabled() || !isTouchEnabled()) return;
if (mSlideState != PanelState.EXPANDED && mSlideState != PanelState.ANCHORED) {
if (mAnchorPoint < 1.0f) {
setPanelState(PanelState.ANCHORED);
} else {
setPanelState(PanelState.EXPANDED);
}
} else {
setPanelState(PanelState.COLLAPSED);
}
}
});
;
}
}
/**
* Set the draggable view portion. Use to null, to allow the whole panel to be draggable
*
* @param dragViewResId The resource ID of the new drag view
*/
public void setDragView(int dragViewResId) {
mDragViewResId = dragViewResId;
setDragView(findViewById(dragViewResId));
}
/**
* Set the scrollable child of the sliding layout. If set, scrolling will be transfered between
* the panel and the view when necessary
*
* @param scrollableView The scrollable view
*/
public void setScrollableView(View scrollableView) {
mScrollableView = scrollableView;
}
/**
* Sets the current scrollable view helper. See ScrollableViewHelper description for details.
*
* @param helper
*/
public void setScrollableViewHelper(ScrollableViewHelper helper) {
mScrollableViewHelper = helper;
}
/**
* Set an anchor point where the panel can stop during sliding
*
* @param anchorPoint A value between 0 and 1, determining the position of the anchor point
* starting from the top of the layout.
*/
public void setAnchorPoint(float anchorPoint) {
if (anchorPoint > 0 && anchorPoint <= 1) {
mAnchorPoint = anchorPoint;
mFirstLayout = true;
requestLayout();
}
}
/**
* Gets the currently set anchor point
*
* @return the currently set anchor point
*/
public float getAnchorPoint() {
return mAnchorPoint;
}
/**
* Sets whether or not the panel overlays the content
*
* @param overlayed
*/
public void setOverlayed(boolean overlayed) {
mOverlayContent = overlayed;
}
/**
* Check if the panel is set as an overlay.
*/
public boolean isOverlayed() {
return mOverlayContent;
}
/**
* Sets whether or not the main content is clipped to the top of the panel
*
* @param clip
*/
public void setClipPanel(boolean clip) {
mClipPanel = clip;
}
/**
* Check whether or not the main content is clipped to the top of the panel
*/
public boolean isClipPanel() {
return mClipPanel;
}
void dispatchOnPanelSlide(View panel) {
synchronized (mPanelSlideListeners) {
for (PanelSlideListener l : mPanelSlideListeners) {
l.onPanelSlide(panel, mSlideOffset);
}
}
}
void dispatchOnPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
synchronized (mPanelSlideListeners) {
for (PanelSlideListener l : mPanelSlideListeners) {
l.onPanelStateChanged(panel, previousState, newState);
}
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
void updateObscuredViewVisibility() {
if (getChildCount() == 0) {
return;
}
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - getPaddingRight();
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - getPaddingBottom();
final int left;
final int right;
final int top;
final int bottom;
if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
left = mSlideableView.getLeft();
right = mSlideableView.getRight();
top = mSlideableView.getTop();
bottom = mSlideableView.getBottom();
} else {
left = right = top = bottom = 0;
}
View child = getChildAt(0);
final int clampedChildLeft = Math.max(leftBound, child.getLeft());
final int clampedChildTop = Math.max(topBound, child.getTop());
final int clampedChildRight = Math.min(rightBound, child.getRight());
final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
final int vis;
if (clampedChildLeft >= left && clampedChildTop >= top &&
clampedChildRight <= right && clampedChildBottom <= bottom) {
vis = INVISIBLE;
} else {
vis = VISIBLE;
}
child.setVisibility(vis);
}
void setAllChildrenVisible() {
for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == INVISIBLE) {
child.setVisibility(VISIBLE);
}
}
}
private static boolean hasOpaqueBackground(View v) {
final Drawable bg = v.getBackground();
return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mFirstLayout = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY && widthMode != MeasureSpec.AT_MOST) {
throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
} else if (heightMode != MeasureSpec.EXACTLY && heightMode != MeasureSpec.AT_MOST) {
throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
}
final int childCount = getChildCount();
if (childCount != 2) {
throw new IllegalStateException("Sliding up panel layout must have exactly 2 children!");
}
mMainView = getChildAt(0);
mSlideableView = getChildAt(1);
if (mDragView == null) {
setDragView(mSlideableView);
}
// If the sliding panel is not visible, then put the whole view in the hidden state
if (mSlideableView.getVisibility() != VISIBLE) {
mSlideState = PanelState.HIDDEN;
}
int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
// First pass. Measure based on child LayoutParams width/height.
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// We always measure the sliding panel in order to know it's height (needed for show panel)
if (child.getVisibility() == GONE && i == 0) {
continue;
}
int height = layoutHeight;
int width = layoutWidth;
if (child == mMainView) {
if (!mOverlayContent && mSlideState != PanelState.HIDDEN) {
height -= mPanelHeight;
}
width -= lp.leftMargin + lp.rightMargin;
} else if (child == mSlideableView) {
// The slideable view should be aware of its top margin.
// See https://github.com/umano/AndroidSlidingUpPanel/issues/412.
height -= lp.topMargin;
}
int childWidthSpec;
if (lp.width == LayoutParams.WRAP_CONTENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
} else if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
} else {
childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
}
int childHeightSpec;
if (lp.height == LayoutParams.WRAP_CONTENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
} else {
// Modify the height based on the weight.
if (lp.weight > 0 && lp.weight < 1) {
height = (int) (height * lp.weight);
} else if (lp.height != LayoutParams.MATCH_PARENT) {
height = lp.height;
}
childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
child.measure(childWidthSpec, childHeightSpec);
if (child == mSlideableView) {
mSlideRange = mSlideableView.getMeasuredHeight() - mPanelHeight;
}
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int childCount = getChildCount();
if (mFirstLayout) {
switch (mSlideState) {
case EXPANDED:
mSlideOffset = 1.0f;
break;
case ANCHORED:
mSlideOffset = mAnchorPoint;
break;
case HIDDEN:
int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
mSlideOffset = computeSlideOffset(newTop);
break;
default:
mSlideOffset = 0.f;
break;
}
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Always layout the sliding view on the first layout
if (child.getVisibility() == GONE && (i == 0 || mFirstLayout)) {
continue;
}
final int childHeight = child.getMeasuredHeight();
int childTop = paddingTop;
if (child == mSlideableView) {
childTop = computePanelTopPosition(mSlideOffset);
}
if (!mIsSlidingUp) {
if (child == mMainView && !mOverlayContent) {
childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight();
}
}
final int childBottom = childTop + childHeight;
final int childLeft = paddingLeft + lp.leftMargin;
final int childRight = childLeft + child.getMeasuredWidth();
child.layout(childLeft, childTop, childRight, childBottom);
}
if (mFirstLayout) {
updateObscuredViewVisibility();
}
applyParallaxForCurrentSlideOffset();
mFirstLayout = false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Recalculate sliding panes and their details
if (h != oldh) {
mFirstLayout = true;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// If the scrollable view is handling touch, never intercept
if (mIsScrollableViewHandlingTouch || !isTouchEnabled()) {
mDragHelper.abort();
return false;
}
final int action = MotionEventCompat.getActionMasked(ev);
final float x = ev.getX();
final float y = ev.getY();
final float adx = Math.abs(x - mInitialMotionX);
final float ady = Math.abs(y - mInitialMotionY);
final int dragSlop = mDragHelper.getTouchSlop();
switch (action) {
case MotionEvent.ACTION_DOWN: {
mIsUnableToDrag = false;
mInitialMotionX = x;
mInitialMotionY = y;
if (!isViewUnder(mDragView, (int) x, (int) y)) {
mDragHelper.cancel();
mIsUnableToDrag = true;
return false;
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (ady > dragSlop && adx > ady) {
mDragHelper.cancel();
mIsUnableToDrag = true;
return false;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// If the dragView is still dragging when we get here, we need to call processTouchEvent
// so that the view is settled
// Added to make scrollable views work (tokudu)
if (mDragHelper.isDragging()) {
mDragHelper.processTouchEvent(ev);
return true;
}
// Check if this was a click on the faded part of the screen, and fire off the listener if there is one.
if (ady <= dragSlop
&& adx <= dragSlop
&& mSlideOffset > 0 && !isViewUnder(mSlideableView, (int) mInitialMotionX, (int) mInitialMotionY) && mFadeOnClickListener != null) {
playSoundEffect(android.view.SoundEffectConstants.CLICK);
mFadeOnClickListener.onClick(this);
return true;
}
break;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled() || !isTouchEnabled()) {
return super.onTouchEvent(ev);
}
try {
mDragHelper.processTouchEvent(ev);
return true;
} catch (Exception ex) {
// Ignore the pointer out of range exception
return false;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (!isEnabled() || !isTouchEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
mDragHelper.abort();
return super.dispatchTouchEvent(ev);
}
final float x = ev.getX();
final float y = ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
mIsScrollableViewHandlingTouch = false;
mPrevMotionX = x;
mPrevMotionY = y;
} else if (action == MotionEvent.ACTION_MOVE) {
float dx = x - mPrevMotionX;
float dy = y - mPrevMotionY;
mPrevMotionX = x;
mPrevMotionY = y;
if (Math.abs(dx) > Math.abs(dy)) {
// Scrolling horizontally, so ignore
return super.dispatchTouchEvent(ev);
}
// If the scroll view isn't under the touch, pass the
// event along to the dragView.
if (!isViewUnder(mScrollableView, (int) mInitialMotionX, (int) mInitialMotionY)) {
return super.dispatchTouchEvent(ev);
}
// Which direction (up or down) is the drag moving?
if (dy * (mIsSlidingUp ? 1 : -1) > 0) { // Collapsing
// Is the child less than fully scrolled?
// Then let the child handle it.
if (mScrollableViewHelper.getScrollableViewScrollPosition(mScrollableView, mIsSlidingUp) > 0) {
mIsScrollableViewHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
// Was the child handling the touch previously?
// Then we need to rejigger things so that the
// drag panel gets a proper down event.
if (mIsScrollableViewHandlingTouch) {
// Send an 'UP' event to the child.
MotionEvent up = MotionEvent.obtain(ev);
up.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(up);
up.recycle();
// Send a 'DOWN' event to the panel. (We'll cheat
// and hijack this one)
ev.setAction(MotionEvent.ACTION_DOWN);
}
mIsScrollableViewHandlingTouch = false;
return this.onTouchEvent(ev);
} else if (dy * (mIsSlidingUp ? 1 : -1) < 0) { // Expanding
// Is the panel less than fully expanded?
// Then we'll handle the drag here.
if (mSlideOffset < 1.0f) {
mIsScrollableViewHandlingTouch = false;
return this.onTouchEvent(ev);
}
// Was the panel handling the touch previously?
// Then we need to rejigger things so that the
// child gets a proper down event.
if (!mIsScrollableViewHandlingTouch && mDragHelper.isDragging()) {
mDragHelper.cancel();
ev.setAction(MotionEvent.ACTION_DOWN);
}
mIsScrollableViewHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
} else if (action == MotionEvent.ACTION_UP) {
// If the scrollable view was handling the touch and we receive an up
// we want to clear any previous dragging state so we don't intercept a touch stream accidentally
if (mIsScrollableViewHandlingTouch) {
mDragHelper.setDragState(ViewDragHelper.STATE_IDLE);
}
}
// In all other cases, just let the default behavior take over.
return super.dispatchTouchEvent(ev);
}
private boolean isViewUnder(View view, int x, int y) {
if (view == null) return false;
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
}
/*
* Computes the top position of the panel based on the slide offset.
*/
private int computePanelTopPosition(float slideOffset) {
int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0;
int slidePixelOffset = (int) (slideOffset * mSlideRange);
// Compute the top of the panel if its collapsed
return mIsSlidingUp
? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset
: getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset;
}
/*
* Computes the slide offset based on the top position of the panel
*/
private float computeSlideOffset(int topPosition) {
// Compute the panel top position if the panel is collapsed (offset 0)
final int topBoundCollapsed = computePanelTopPosition(0);
// Determine the new slide offset based on the collapsed top position and the new required
// top position
return (mIsSlidingUp
? (float) (topBoundCollapsed - topPosition) / mSlideRange
: (float) (topPosition - topBoundCollapsed) / mSlideRange);
}
/**
* Returns the current state of the panel as an enum.
*
* @return the current panel state
*/
public PanelState getPanelState() {
return mSlideState;
}
/**
* Change panel state to the given state with
*
* @param state - new panel state
*/
public void setPanelState(PanelState state) {
// Abort any running animation, to allow state change
if(mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING){
Log.d(TAG, "View is settling. Aborting animation.");
mDragHelper.abort();
}
if (state == null || state == PanelState.DRAGGING) {
throw new IllegalArgumentException("Panel state cannot be null or DRAGGING.");
}
if (!isEnabled()
|| (!mFirstLayout && mSlideableView == null)
|| state == mSlideState
|| mSlideState == PanelState.DRAGGING) return;
if (mFirstLayout) {
setPanelStateInternal(state);
} else {
if (mSlideState == PanelState.HIDDEN) {
mSlideableView.setVisibility(View.VISIBLE);
requestLayout();
}
switch (state) {
case ANCHORED:
smoothSlideTo(mAnchorPoint, 0);
break;
case COLLAPSED:
smoothSlideTo(0, 0);
break;
case EXPANDED:
smoothSlideTo(1.0f, 0);
break;
case HIDDEN:
int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
smoothSlideTo(computeSlideOffset(newTop), 0);
break;
}
}
}
private void setPanelStateInternal(PanelState state) {
if (mSlideState == state) return;
PanelState oldState = mSlideState;
mSlideState = state;
dispatchOnPanelStateChanged(this, oldState, state);
}
/**
* Update the parallax based on the current slide offset.
*/
@SuppressLint("NewApi")
private void applyParallaxForCurrentSlideOffset() {
if (mParallaxOffset > 0) {
int mainViewOffset = getCurrentParallaxOffset();
ViewCompat.setTranslationY(mMainView, mainViewOffset);
}
}
private void onPanelDragged(int newTop) {
if (mSlideState != PanelState.DRAGGING) {
mLastNotDraggingSlideState = mSlideState;
}
setPanelStateInternal(PanelState.DRAGGING);
// Recompute the slide offset based on the new top position
mSlideOffset = computeSlideOffset(newTop);
applyParallaxForCurrentSlideOffset();
// Dispatch the slide event
dispatchOnPanelSlide(mSlideableView);
// If the slide offset is negative, and overlay is not on, we need to increase the
// height of the main content
LayoutParams lp = (LayoutParams) mMainView.getLayoutParams();
int defaultHeight = getHeight() - getPaddingBottom() - getPaddingTop() - mPanelHeight;
if (mSlideOffset <= 0 && !mOverlayContent) {
// expand the main view
lp.height = mIsSlidingUp ? (newTop - getPaddingBottom()) : (getHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() - newTop);
if (lp.height == defaultHeight) {
lp.height = LayoutParams.MATCH_PARENT;
}
mMainView.requestLayout();
} else if (lp.height != LayoutParams.MATCH_PARENT && !mOverlayContent) {
lp.height = LayoutParams.MATCH_PARENT;
mMainView.requestLayout();
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean result;
final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
if (mSlideableView != null && mSlideableView != child) { // if main view
// Clip against the slider; no sense drawing what will immediately be covered,
// Unless the panel is set to overlay content
canvas.getClipBounds(mTmpRect);
if (!mOverlayContent) {
if (mIsSlidingUp) {
mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
} else {
mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
}
}
if (mClipPanel) {
canvas.clipRect(mTmpRect);
}
result = super.drawChild(canvas, child, drawingTime);
if (mCoveredFadeColor != 0 && mSlideOffset > 0) {
final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
final int imag = (int) (baseAlpha * mSlideOffset);
final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
mCoveredFadePaint.setColor(color);
canvas.drawRect(mTmpRect, mCoveredFadePaint);
}
} else {
result = super.drawChild(canvas, child, drawingTime);
}
canvas.restoreToCount(save);
return result;
}
/**
* Smoothly animate mDraggingPane to the target X position within its range.
*
* @param slideOffset position to animate to
* @param velocity initial velocity in case of fling, or 0.
*/
boolean smoothSlideTo(float slideOffset, int velocity) {
if (!isEnabled() || mSlideableView == null) {
// Nothing to do.
return false;
}
int panelTop = computePanelTopPosition(slideOffset);
if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {
setAllChildrenVisible();
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
@Override
public void computeScroll() {
if (mDragHelper != null && mDragHelper.continueSettling(true)) {
if (!isEnabled()) {
mDragHelper.abort();
return;
}
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public void draw(Canvas c) {
super.draw(c);
// draw the shadow
if (mShadowDrawable != null && mSlideableView != null) {
final int right = mSlideableView.getRight();
final int top;
final int bottom;
if (mIsSlidingUp) {
top = mSlideableView.getTop() - mShadowHeight;
bottom = mSlideableView.getTop();
} else {
top = mSlideableView.getBottom();
bottom = mSlideableView.getBottom() + mShadowHeight;
}
final int left = mSlideableView.getLeft();
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(c);
}
}
/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view v passed should itself be checked for scrollability (true),
* or just its children (false).
* @param dx Delta scrolled in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
final int scrollY = v.getScrollY();
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
final View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
canScroll(child, true, dx, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
}
}
return checkV && ViewCompat.canScrollHorizontally(v, -dx);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams
? new LayoutParams((MarginLayoutParams) p)
: new LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams && super.checkLayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("superState", super.onSaveInstanceState());
bundle.putSerializable(SLIDING_STATE, mSlideState != PanelState.DRAGGING ? mSlideState : mLastNotDraggingSlideState);
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mSlideState = (PanelState) bundle.getSerializable(SLIDING_STATE);
mSlideState = mSlideState == null ? DEFAULT_SLIDE_STATE : mSlideState;
state = bundle.getParcelable("superState");
}
super.onRestoreInstanceState(state);
}
private class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return !mIsUnableToDrag && child == mSlideableView;
}
@Override
public void onViewDragStateChanged(int state) {
if (mDragHelper != null && mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
mSlideOffset = computeSlideOffset(mSlideableView.getTop());
applyParallaxForCurrentSlideOffset();
if (mSlideOffset == 1) {
updateObscuredViewVisibility();
setPanelStateInternal(PanelState.EXPANDED);
} else if (mSlideOffset == 0) {
setPanelStateInternal(PanelState.COLLAPSED);
} else if (mSlideOffset < 0) {
setPanelStateInternal(PanelState.HIDDEN);
mSlideableView.setVisibility(View.INVISIBLE);
} else {
updateObscuredViewVisibility();
setPanelStateInternal(PanelState.ANCHORED);
}
}
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
setAllChildrenVisible();
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
onPanelDragged(top);
invalidate();
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int target = 0;
// direction is always positive if we are sliding in the expanded direction
float direction = mIsSlidingUp ? -yvel : yvel;
if (direction > 0 && mSlideOffset <= mAnchorPoint) {
// swipe up -> expand and stop at anchor point
target = computePanelTopPosition(mAnchorPoint);
} else if (direction > 0 && mSlideOffset > mAnchorPoint) {
// swipe up past anchor -> expand
target = computePanelTopPosition(1.0f);
} else if (direction < 0 && mSlideOffset >= mAnchorPoint) {
// swipe down -> collapse and stop at anchor point
target = computePanelTopPosition(mAnchorPoint);
} else if (direction < 0 && mSlideOffset < mAnchorPoint) {
// swipe down past anchor -> collapse
target = computePanelTopPosition(0.0f);
} else if (mSlideOffset >= (1.f + mAnchorPoint) / 2) {
// zero velocity, and far enough from anchor point => expand to the top
target = computePanelTopPosition(1.0f);
} else if (mSlideOffset >= mAnchorPoint / 2) {
// zero velocity, and close enough to anchor point => go to anchor
target = computePanelTopPosition(mAnchorPoint);
} else {
// settle at the bottom
target = computePanelTopPosition(0.0f);
}
if (mDragHelper != null) {
mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);
}
invalidate();
}
@Override
public int getViewVerticalDragRange(View child) {
return mSlideRange;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int collapsedTop = computePanelTopPosition(0.f);
final int expandedTop = computePanelTopPosition(1.0f);
if (mIsSlidingUp) {
return Math.min(Math.max(top, expandedTop), collapsedTop);
} else {
return Math.min(Math.max(top, collapsedTop), expandedTop);
}
}
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
private static final int[] ATTRS = new int[]{
android.R.attr.layout_weight
};
public float weight = 0;
public LayoutParams() {
super(MATCH_PARENT, MATCH_PARENT);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(int width, int height, float weight) {
super(width, height);
this.weight = weight;
}
public LayoutParams(android.view.ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(LayoutParams source) {
super(source);
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
final TypedArray ta = c.obtainStyledAttributes(attrs, ATTRS);
if (ta != null) {
this.weight = ta.getFloat(0, 0);
ta.recycle();
}
}
}
}
================================================
FILE: library/src/main/java/com/sothree/slidinguppanel/ViewDragHelper.java
================================================
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sothree.slidinguppanel;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ScrollerCompat;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import java.util.Arrays;
/**
* ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
* of useful operations and state tracking for allowing a user to drag and reposition
* views within their parent ViewGroup.
*/
public class ViewDragHelper {
private static final String TAG = "ViewDragHelper";
/**
* A null/invalid pointer ID.
*/
public static final int INVALID_POINTER = -1;
/**
* A view is not currently being dragged or animating as a result of a fling/snap.
*/
public static final int STATE_IDLE = 0;
/**
* A view is currently being dragged. The position is currently changing as a result
* of user input or simulated user input.
*/
public static final int STATE_DRAGGING = 1;
/**
* A view is currently settling into place as a result of a fling or
* predefined non-interactive motion.
*/
public static final int STATE_SETTLING = 2;
/**
* Edge flag indicating that the left edge should be affected.
*/
public static final int EDGE_LEFT = 1 << 0;
/**
* Edge flag indicating that the right edge should be affected.
*/
public static final int EDGE_RIGHT = 1 << 1;
/**
* Edge flag indicating that the top edge should be affected.
*/
public static final int EDGE_TOP = 1 << 2;
/**
* Edge flag indicating that the bottom edge should be affected.
*/
public static final int EDGE_BOTTOM = 1 << 3;
/**
* Edge flag set indicating all edges should be affected.
*/
public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
/**
* Indicates that a check should occur along the horizontal axis
*/
public static final int DIRECTION_HORIZONTAL = 1 << 0;
/**
* Indicates that a check should occur along the vertical axis
*/
public static final int DIRECTION_VERTICAL = 1 << 1;
/**
* Indicates that a check should occur along all axes
*/
public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
private static final int EDGE_SIZE = 20; // dp
private static final int BASE_SETTLE_DURATION = 256; // ms
private static final int MAX_SETTLE_DURATION = 600; // ms
// Current drag state; idle, dragging or settling
private int mDragState;
// Distance to travel before a drag may begin
private int mTouchSlop;
// Last known position/pointer tracking
private int mActivePointerId = INVALID_POINTER;
private float[] mInitialMotionX;
private float[] mInitialMotionY;
private float[] mLastMotionX;
private float[] mLastMotionY;
private int[] mInitialEdgesTouched;
private int[] mEdgeDragsInProgress;
private int[] mEdgeDragsLocked;
private int mPointersDown;
private VelocityTracker mVelocityTracker;
private float mMaxVelocity;
private float mMinVelocity;
private int mEdgeSize;
private int mTrackingEdges;
private ScrollerCompat mScroller;
private final Callback mCallback;
private View mCapturedView;
private boolean mReleaseInProgress;
private final ViewGroup mParentView;
/**
* A Callback is used as a communication channel with the ViewDragHelper back to the
* parent view using it. <code>on*</code>methods are invoked on siginficant events and several
* accessor methods are expected to provide the ViewDragHelper with more information
* about the state of the parent view upon request. The callback also makes decisions
* governing the range and draggability of child views.
*/
public static abstract class Callback {
/**
* Called when the drag state changes. See the <code>STATE_*</code> constants
* for more information.
*
* @param state The new drag state
*
* @see #STATE_IDLE
* @see #STATE_DRAGGING
* @see #STATE_SETTLING
*/
public void onViewDragStateChanged(int state) {}
/**
* Called when the captured view's position changes as the result of a drag or settle.
*
* @param changedView View whose position changed
* @param left New X coordinate of the left edge of the view
* @param top New Y coordinate of the top edge of the view
* @param dx Change in X position from the last call
* @param dy Change in Y position from the last call
*/
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
/**
* Called when a child view is captured for dragging or settling. The ID of the pointer
* currently dragging the captured view is supplied. If activePointerId is
* identified as {@link #INVALID_POINTER} the capture is programmatic instead of
* pointer-initiated.
*
* @param capturedChild Child view that was captured
* @param activePointerId Pointer id tracking the child capture
*/
public void onViewCaptured(View capturedChild, int activePointerId) {}
/**
* Called when the child view is no longer being actively dragged.
* The fling velocity is also supplied, if relevant. The velocity values may
* be clamped to system minimums or maximums.
*
* <p>Calling code may decide to fling or otherwise release the view to let it
* settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}
* or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes
* one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}
* and the view capture will not fully end until it comes to a complete stop.
* If neither of these methods is invoked before <code>onViewReleased</code> returns,
* the view will stop in place and the ViewDragHelper will return to
* {@link #STATE_IDLE}.</p>
*
* @param releasedChild The captured child view now being released
* @param xvel X velocity of the pointer as it left the screen in pixels per second.
* @param yvel Y velocity of the pointer as it left the screen in pixels per second.
*/
public void onViewReleased(View releasedChild, float xvel, float yvel) {}
/**
* Called when one of the subscribed edges in the parent view has been touched
* by the user while no child view is currently captured.
*
* @param edgeFlags A combination of edge flags describing the edge(s) currently touched
* @param pointerId ID of the pointer touching the described edge(s)
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void onEdgeTouched(int edgeFlags, int pointerId) {}
/**
* Called when the given edge may become locked. This can happen if an edge drag
* was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}
* was called. This method should return true to lock this edge or false to leave it
* unlocked. The default behavior is to leave edges unlocked.
*
* @param edgeFlags A combination of edge flags describing the edge(s) locked
* @return true to lock the edge, false to leave it unlocked
*/
public boolean onEdgeLock(int edgeFlags) {
return false;
}
/**
* Called when the user has started a deliberate drag away from one
* of the subscribed edges in the parent view while no child view is currently captured.
*
* @param edgeFlags A combination of edge flags describing the edge(s) dragged
* @param pointerId ID of the pointer touching the described edge(s)
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
/**
* Called to determine the Z-order of child views.
*
* @param index the ordered position to query for
* @return index of the view that should be ordered at position <code>index</code>
*/
public int getOrderedChildIndex(int index) {
return index;
}
/**
* Return the magnitude of a draggable child view's horizontal range of motion in pixels.
* This method should return 0 for views that cannot move horizontally.
*
* @param child Child view to check
* @return range of horizontal motion in pixels
*/
public int getViewHorizontalDragRange(View child) {
return 0;
}
/**
* Return the magnitude of a draggable child view's vertical range of motion in pixels.
* This method should return 0 for views that cannot move vertically.
*
* @param child Child view to check
* @return range of vertical motion in pixels
*/
public int getViewVerticalDragRange(View child) {
return 0;
}
/**
* Called when the user's input indicates that they want to capture the given child view
* with the pointer indicated by pointerId. The callback should return true if the user
* is permitted to drag the given view with the indicated pointer.
*
* <p>ViewDragHelper may call this method multiple times for the same view even if
* the view is already captured; this indicates that a new pointer is trying to take
* control of the view.</p>
*
* <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}
* will follow if the capture is successful.</p>
*
* @param child Child the user is attempting to capture
* @param pointerId ID of the pointer attempting the capture
* @return true if capture should be allowed, false otherwise
*/
public abstract boolean tryCaptureView(View child, int pointerId);
/**
* Restrict the motion of the dragged child view along the horizontal axis.
* The default implementation does not allow horizontal motion; the extending
* class must override this method and provide the desired clamping.
*
*
* @param child Child view being dragged
* @param left Attempted motion along the X axis
* @param dx Proposed change in position for left
* @return The new clamped position for left
*/
public int clampViewPositionHorizontal(View child, int left, int dx) {
return 0;
}
/**
* Restrict the motion of the dragged child view along the vertical axis.
* The default implementation does not allow vertical motion; the extending
* class must override this method and provide the desired clamping.
*
*
* @param child Child view being dragged
* @param top Attempted motion along the Y axis
* @param dy Proposed change in position for top
* @return The new clamped position for top
*/
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
}
/**
* Interpolator defining the animation curve for mScroller
*/
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private final Runnable mSetIdleRunnable = new Runnable() {
public void run() {
setDragState(STATE_IDLE);
}
};
/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, null, cb);
}
/**
* Factory method to create a new ViewDragHelper with the specified interpolator.
*
* @param forParent Parent view to monitor
* @param interpolator interpolator for scroller
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
public static ViewDragHelper create(ViewGroup forParent, Interpolator interpolator, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, interpolator, cb);
}
/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param sensitivity Multiplier for how sensitive the helper should be about detecting
* the start of a drag. Larger values are more sensitive. 1.0f is normal.
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
final ViewDragHelper helper = create(forParent, cb);
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
return helper;
}
/**
* Factory method to create a new ViewDragHelper with the specified interpolator.
*
* @param forParent Parent view to monitor
* @param sensitivity Multiplier for how sensitive the helper should be about detecting
* the start of a drag. Larger values are more sensitive. 1.0f is normal.
* @param interpolator interpolator for scroller
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Interpolator interpolator, Callback cb) {
final ViewDragHelper helper = create(forParent, interpolator, cb);
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
return helper;
}
/**
* Apps should use ViewDragHelper.create() to get a new instance.
* This will allow VDH to use internal compatibility implementations for different
* platform versions.
* If the interpolator is null, the default interpolator will be used.
*
* @param context Context to initialize config-dependent params from
* @param forParent Parent view to monitor
* @param interpolator interpolator for scroller
*/
private ViewDragHelper(Context context, ViewGroup forParent, Interpolator interpolator, Callback cb) {
if (forParent == null) {
throw new IllegalArgumentException("Parent view may not be null");
}
if (cb == null) {
throw new IllegalArgumentException("Callback may not be null");
}
mParentView = forParent;
mCallback = cb;
final ViewConfiguration vc = ViewConfiguration.get(context);
final float density = context.getResources().getDisplayMetrics().density;
mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
mTouchSlop = vc.getScaledTouchSlop();
mMaxVelocity = vc.getScaledMaximumFlingVelocity();
mMinVelocity = vc.getScaledMinimumFlingVelocity();
mScroller = ScrollerCompat.create(context, interpolator != null ? interpolator : sInterpolator);
}
/**
* Set the minimum velocity that will be detected as having a magnitude greater than zero
* in pixels per second. Callback methods accepting a velocity will be clamped appropriately.
*
* @param minVel Minimum velocity to detect
*/
public void setMinVelocity(float minVel) {
mMinVelocity = minVel;
}
/**
* Return the currently configured minimum velocity. Any flings with a magnitude less
* than this value in pixels per second. Callback methods accepting a velocity will receive
* zero as a velocity value if the real detected velocity was below this threshold.
*
* @return the minimum velocity that will be detected
*/
public float getMinVelocity() {
return mMinVelocity;
}
/**
* Retrieve the current drag state of this helper. This will return one of
* {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
* @return The current drag state
*/
public int getViewDragState() {
return mDragState;
}
/**
* Enable edge tracking for the selected edges of the parent view.
* The callback's {@link Callback#onEdgeTouched(int, int)} and
* {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
* for edges for which edge tracking has been enabled.
*
* @param edgeFlags Combination of edge flags describing the edges to watch
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void setEdgeTrackingEnabled(int edgeFlags) {
mTrackingEdges = edgeFlags;
}
/**
* Return the size of an edge. This is the range in pixels along the edges of this view
* that will actively detect edge touches or drags if edge tracking is enabled.
*
* @return The size of an edge in pixels
* @see #setEdgeTrackingEnabled(int)
*/
public int getEdgeSize() {
return mEdgeSize;
}
/**
* Capture a specific child view for dragging within the parent. The callback will be notified
* but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
* capture this view.
*
* @param childView Child view to capture
* @param activePointerId ID of the pointer that is dragging the captured child view
*/
public void captureChildView(View childView, int activePointerId) {
if (childView.getParent() != mParentView) {
throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
"of the ViewDragHelper's tracked parent view (" + mParentView + ")");
}
mCapturedView = childView;
mActivePointerId = activePointerId;
mCallback.onViewCaptured(childView, activePointerId);
setDragState(STATE_DRAGGING);
}
/**
* @return The currently captured view, or null if no view has been captured.
*/
public View getCapturedView() {
return mCapturedView;
}
/**
* @return The ID of the pointer currently dragging the captured view,
* or {@link #INVALID_POINTER}.
*/
public int getActivePointerId() {
return mActivePointerId;
}
/**
* @return The minimum distance in pixels that the user must travel to initiate a drag
*/
public int getTouchSlop() {
return mTouchSlop;
}
/**
* The result of a call to this method is equivalent to
* {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.
*/
public void cancel() {
mActivePointerId = INVALID_POINTER;
clearMotionHistory();
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* {@link #cancel()}, but also abort all motion in progress and snap to the end of any
* animation.
*/
public void abort() {
cancel();
if (mDragState == STATE_SETTLING) {
final int oldX = mScroller.getCurrX();
final int oldY = mScroller.getCurrY();
mScroller.abortAnimation();
final int newX = mScroller.getCurrX();
final int newY = mScroller.getCurrY();
mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
}
setDragState(STATE_IDLE);
}
/**
* Animate the view <code>child</code> to the given (left, top) position.
* If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
* on each subsequent frame to continue the motion until it returns false. If this method
* returns false there is no further work to do to complete the movement.
*
* <p>This operation does not count as a capture event, though {@link #getCapturedView()}
* will still report the sliding view while the slide is in progress.</p>
*
* @param child Child view to capture and animate
* @param finalLeft Final left position of child
* @param finalTop Final top position of child
* @return true if animation should continue through {@link #continueSettling(boolean)} calls
*/
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
mCapturedView = child;
mActivePointerId = INVALID_POINTER;
return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
}
/**
* Settle the captured view at the given (left, top) position.
* The appropriate velocity from prior motion will be taken into account.
* If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
* on each subsequent frame to continue the motion until it returns false. If this method
* returns false there is no further work to do to complete the movement.
*
* @param finalLeft Settled left edge position for the captured view
* @param finalTop Settled top edge position for the captured view
* @return true if animation should continue through {@link #continueSettling(boolean)} calls
*/
public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
if (!mReleaseInProgress) {
throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +
"Callback#onViewReleased");
}
return forceSettleCapturedViewAt(finalLeft, finalTop,
(int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
(int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
}
/**
* Settle the captured view at the given (left, top) position.
*
* @param finalLeft Target left position for the captured view
* @param finalTop Target top position for the captured view
* @param xvel Horizontal velocity
* @param yvel Vertical velocity
* @return true if animation should continue through {@link #continueSettling(boolean)} calls
*/
private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
final int startLeft = mCapturedView.getLeft();
final int startTop = mCapturedView.getTop();
final int dx = finalLeft - startLeft;
final int dy = finalTop - startTop;
if (dx == 0 && dy == 0) {
// Nothing to do. Send callbacks, be done.
mScroller.abortAnimation();
setDragState(STATE_IDLE);
return false;
}
final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
mScroller.startScroll(startLeft, startTop, dx, dy, duration);
setDragState(STATE_SETTLING);
return true;
}
private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
final int absDx = Math.abs(dx);
final int absDy = Math.abs(dy);
final int absXVel = Math.abs(xvel);
final int absYVel = Math.abs(yvel);
final int addedVel = absXVel + absYVel;
final int addedDistance = absDx + absDy;
final float xweight = xvel != 0 ? (float) absXVel / addedVel :
(float) absDx / addedDistance;
final float yweight = yvel != 0 ? (float) absYVel / addedVel :
(float) absDy / addedDistance;
int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
return (int) (xduration * xweight + yduration * yweight);
}
private int computeAxisDuration(int delta, int velocity, int motionRange) {
if (delta == 0) {
return 0;
}
final int width = mParentView.getWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
final float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float range = (float) Math.abs(delta) / motionRange;
duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
}
return Math.min(duration, MAX_SETTLE_DURATION);
}
/**
* Clamp the magnitude of value for absMin and absMax.
* If the value is below the minimum, it will be clamped to zero.
* If the value is above the maximum, it will be clamped to the maximum.
*
* @param value Value to clamp
* @param absMin Absolute value of the minimum significant value to return
* @param absMax Absolute value of the maximum value to return
* @return The clamped value with the same sign as <code>value</code>
*/
private int clampMag(int value, int absMin, int absMax) {
final int absValue = Math.abs(value);
if (absValue < absMin) return 0;
if (absValue > absMax) return value > 0 ? absMax : -absMax;
return value;
}
/**
* Clamp the magnitude of value for absMin and absMax.
* If the value is below the minimum, it will be clamped to zero.
* If the value is above the maximum, it will be clamped to the maximum.
*
* @param value Value to clamp
* @param absMin Absolute value of the minimum significant value to return
* @param absMax Absolute value of the maximum value to return
* @return The clamped value with the same sign as <code>value</code>
*/
private float clampMag(float value, float absMin, float absMax) {
final float absValue = Math.abs(value);
if (absValue < absMin) return 0;
if (absValue > absMax) return value > 0 ? absMax : -absMax;
return value;
}
private float distanceInfluenceForSnapDuration(float f) {
f -= 0.5f; // center the values about 0.
f *= 0.3f * Math.PI / 2.0f;
return (float) Math.sin(f);
}
/**
* Settle the captured view based on standard free-moving fling behavior.
* The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame
* to continue the motion until it returns false.
*
* @param minLeft Minimum X position for the view's left edge
* @param minTop Minimum Y position for the view's top edge
* @param maxLeft Maximum X position for the view's left edge
* @param maxTop Maximum Y position for the view's top edge
*/
public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
if (!mReleaseInProgress) {
throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +
"Callback#onViewReleased");
}
mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
(int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
(int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
minLeft, maxLeft, minTop, maxTop);
setDragState(STATE_SETTLING);
}
/**
* Move the captured settling view by the appropriate amount for the current time.
* If <code>continueSettling</code> returns true, the caller should call it again
* on the next frame to continue.
*
* @param deferCallbacks true if state callbacks should be deferred via posted message.
* Set this to true if you are calling this method from
* {@link android.view.View#computeScroll()} or similar methods
* invoked as part of layout or drawing.
* @return true if settle is still in progress
*/
public boolean continueSettling(boolean deferCallbacks) {
// Make sure, there is a captured view
if (mCapturedView == null) {
return false;
}
if (mDragState == STATE_SETTLING) {
boolean keepGoing = mScroller.computeScrollOffset();
final int x = mScroller.getCurrX();
final int y = mScroller.getCurrY();
final int dx = x - mCapturedView.getLeft();
final int dy = y - mCapturedView.getTop();
if(!keepGoing && dy != 0) { //fix #525
//Invalid drag state
mCapturedView.setTop(0);
return true;
}
if (dx != 0) {
mCapturedView.offsetLeftAndRight(dx);
}
if (dy != 0) {
mCapturedView.offsetTopAndBottom(dy);
}
if (dx != 0 || dy != 0) {
mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
}
if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
// Close enough. The interpolator/scroller might think we're still moving
// but the user sure doesn't.
mScroller.abortAnimation();
keepGoing = mScroller.isFinished();
}
if (!keepGoing) {
if (deferCallbacks) {
mParentView.post(mSetIdleRunnable);
} else {
setDragState(STATE_IDLE);
}
}
}
return mDragState == STATE_SETTLING;
}
/**
* Like all callback events this must happen on the UI thread, but release
* involves some extra semantics. During a release (mReleaseInProgress)
* is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
* or {@link #flingCapturedView(int, int, int, int)}.
*/
private void dispatchViewReleased(float xvel, float yvel) {
mReleaseInProgress = true;
mCallback.onViewReleased(mCapturedView, xvel, yvel);
mReleaseInProgress = false;
if (mDragState == STATE_DRAGGING) {
// onViewReleased didn't call a method that would have changed this. Go idle.
setDragState(STATE_IDLE);
}
}
private void clearMotionHistory() {
if (mInitialMotionX == null) {
return;
}
Arrays.fill(mInitialMotionX, 0);
Arrays.fill(mInitialMotionY, 0);
Arrays.fill(mLastMotionX, 0);
Arrays.fill(mLastMotionY, 0);
Arrays.fill(mInitialEdgesTouched, 0);
Arrays.fill(mEdgeDragsInProgress, 0);
Arrays.fill(mEdgeDragsLocked, 0);
mPointersDown = 0;
}
private void clearMotionHistory(int pointerId) {
if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
return;
}
mInitialMotionX[pointerId] = 0;
mInitialMotionY[pointerId] = 0;
mLastMotionX[pointerId] = 0;
mLastMotionY[pointerId] = 0;
mInitialEdgesTouched[pointerId] = 0;
mEdgeDragsInProgress[pointerId] = 0;
mEdgeDragsLocked[pointerId] = 0;
mPointersDown &= ~(1 << pointerId);
}
private void ensureMotionHistorySizeForId(int pointerId) {
if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
float[] imx = new float[pointerId + 1];
float[] imy = new float[pointerId + 1];
float[] lmx = new float[pointerId + 1];
float[] lmy = new float[pointerId + 1];
int[] iit = new int[pointerId + 1];
int[] edip = new int[pointerId + 1];
int[] edl = new int[pointerId + 1];
if (mInitialMotionX != null) {
System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);
System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
}
mInitialMotionX = imx;
mInitialMotionY = imy;
mLastMotionX = lmx;
mLastMotionY = lmy;
mInitialEdgesTouched = iit;
mEdgeDragsInProgress = edip;
mEdgeDragsLocked = edl;
}
}
private void saveInitialMotion(float x, float y, int pointerId) {
ensureMotionHistorySizeForId(pointerId);
mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
mPointersDown |= 1 << pointerId;
}
private void saveLastMotion(MotionEvent ev) {
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
// Sometimes we can try and save last motion for a pointer never recorded in initial motion. In this case we just discard it.
if (mLastMotionX != null && mLastMotionY != null
&& mLastMotionX.length > pointerId && mLastMotionY.length > pointerId) {
mLastMotionX[pointerId] = x;
mLastMotionY[pointerId] = y;
}
}
}
/**
* Check if the given pointer ID represents a pointer that is currently down (to the best
* of the ViewDragHelper's knowledge).
*
* <p>The state used to report this information is populated by the methods
* {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
* {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not
* been called for all relevant MotionEvents to track, the information reported
* by this method may be stale or incorrect.</p>
*
* @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent
* @return true if the pointer with the given ID is still down
*/
public boolean isPointerDown(int pointerId) {
return (mPointersDown & 1 << pointerId) != 0;
}
void setDragState(int state) {
if (mDragState != state) {
mDragState = state;
mCallback.onViewDragStateChanged(state);
if (mDragState == STATE_IDLE) {
mCapturedView = null;
}
}
}
/**
* Attempt to capture the view with the given pointer ID. The callback will be involved.
* This will put us into the "dragging" state. If we've already captured this view with
* this pointer this method will immediately return true without consulting the callback.
*
* @param toCapture View to capture
* @param pointerId Pointer to capture with
* @return true if capture was successful
*/
boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
if (toCapture == mCapturedView && mActivePointerId == pointerId) {
// Already done!
return true;
}
if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
mActivePointerId = pointerId;
captureChildView(toCapture, pointerId);
return true;
}
return false;
}
/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view v passed should itself be checked for scrollability (true),
* or just its children (false).
* @param dx Delta scrolled in pixels along the X axis
* @param dy Delta scrolled in pixels along the Y axis
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
final int scrollY = v.getScrollY();
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
// TODO: Add versioned support here for transformed views.
// This will not work for transformed views in Honeycomb+
final View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
canScroll(child, true, dx, dy, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
}
}
return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||
ViewCompat.canScrollVertically(v, -dy));
}
/**
* Check if this event as provided to the parent view's onInterceptTouchEvent should
* cause the parent to intercept the touch event stream.
*
* @param ev MotionEvent provided to onInterceptTouchEvent
* @return true if the parent view should return true from onInterceptTouchEvent
*/
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
final int actionIndex = MotionEventCompat.getActionIndex(ev);
if (action == MotionEvent.ACTION_DOWN) {
// Reset things for a new event stream, just in case we didn't get
// the whole previous stream.
cancel();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = MotionEventCompat.getPointerId(ev, 0);
saveInitialMotion(x, y, pointerId);
final View toCapture = findTopChildUnder((int) x, (int) y);
// Catch a settling view if possible.
if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
tryCaptureViewForDrag(toCapture, pointerId);
}
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
final float x = MotionEventCompat.getX(ev, actionIndex);
final float y = MotionEventCompat.getY(ev, actionIndex);
saveInitialMotion(x, y, pointerId);
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (mDragState == STATE_SETTLING) {
// Catch a settling view if possible.
final View toCapture = findTopChildUnder((int) x, (int) y);
if (toCapture == mCapturedView) {
tryCaptureViewForDrag(toCapture, pointerId);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
// First to cross a touch slop over a draggable view wins. Also report edge drags.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount && mInitialMotionX != null && mInitialMotionY != null; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
if (pointerId >= mInitialMotionX.length || pointerId >= mInitialMotionY.length) {
continue;
}
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break;
}
final View toCapture = findTopChildUnder((int)mInitialMotionX[pointerId], (int)mInitialMotionY[pointerId]);
if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
break;
}
case MotionEventCompat.ACTION_POINTER_UP: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
clearMotionHistory(pointerId);
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
cancel();
break;
}
}
return mDragState == STATE_DRAGGING;
}
/**
* Process a touch event received by the parent view. This method will dispatch callback events
* as needed before returning. The parent view's onTouchEvent implementation should call this.
*
* @param ev The touch event received by the parent view
*/
public void processTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
final int actionIndex = MotionEventCompat.getActionIndex(ev);
if (action == MotionEvent.ACTION_DOWN) {
// Reset things for a new event stream, just in case we didn't get
// the whole previous stream.
cancel();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = MotionEventCompat.getPointerId(ev, 0);
final View toCapture = findTopChildUnder((int) x, (int) y);
saveInitialMotion(x, y, pointerId);
// Since the parent is already directly processing this touch event,
// there is no reason to delay for a slop before dragging.
// Start immediately if possible.
tryCaptureViewForDrag(toCapture, pointerId);
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
final float x = MotionEventCompat.getX(ev, actionIndex);
final float y = MotionEventCompat.getY(ev, actionIndex);
saveInitialMotion(x, y, pointerId);
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
// If we're idle we can do anything! Treat it like a normal down event.
final View toCapture = findTopChildUnder((int) x, (int) y);
tryCaptureViewForDrag(toCapture, pointerId);
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (isCapturedViewUnder((int) x, (int) y)) {
// We're still tracking a captured view. If the same view is under this
// point, we'll swap to controlling it with this pointer instead.
// (This will still work if we're "catching" a settling view.)
tryCaptureViewForDrag(mCapturedView, pointerId);
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, index);
final float y = MotionEventCompat.getY(ev, index);
final int idx = (int) (x - mLastMotionX[mActivePointerId]);
final int idy = (int) (y - mLastMotionY[mActivePointerId]);
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
saveLastMotion(ev);
} else {
// Check to see if any pointer is now over a draggable view.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i)
;
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag.
break;
}
final View toCapture = findTopChildUnder((int) mInitialMotionX[pointerId], (int) mInitialMotionY[pointerId]);
if (checkTouchSlop(toCapture, dx, dy) &&
tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
}
break;
}
case MotionEventCompat.ACTION_POINTER_UP: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
// Try to find another pointer that's still holding on to the captured view.
int newActivePointer = INVALID_POINTER;
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int id = MotionEventCompat.getPointerId(ev, i);
if (id == mActivePointerId) {
// This one's going away, skip.
continue;
}
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
if (findTopChildUnder((int) x, (int) y) == mCapturedView &&
tryCaptureViewForDrag(mCapturedView, id)) {
newActivePointer = mActivePointerId;
break;
}
}
if (newActivePointer == INVALID_POINTER) {
// We didn't find another pointer still touching the view, release it.
releaseViewForPointerUp();
}
}
clearMotionHistory(pointerId);
break;
}
case MotionEvent.ACTION_UP: {
if (mDragState == STATE_DRAGGING) {
releaseViewForPointerUp();
}
cancel();
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mDragState == STATE_DRAGGING) {
dispatchViewReleased(0, 0);
}
cancel();
break;
}
}
}
private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
int dragsStarted = 0;
if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
dragsStarted |= EDGE_LEFT;
}
if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
dragsStarted |= EDGE_TOP;
}
if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
dragsStarted |= EDGE_RIGHT;
}
if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
dragsStarted |= EDGE_BOTTOM;
}
if (dragsStarted != 0) {
mEdgeDragsInProgress[pointerId] |= dragsStarted;
mCallback.onEdgeDragStarted(dragsStarted, pointerId);
}
}
private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
final float absDelta = Math.abs(delta);
final float absODelta = Math.abs(odelta);
if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 ||
(mEdgeDragsLocked[pointerId] & edge) == edge ||
(mEdgeDragsInProgress[pointerId] & edge) == edge ||
(absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
return false;
}
if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
mEdgeDragsLocked[pointerId] |= edge;
return false;
}
return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
}
/**
* Check if we've crossed a reasonable touch slop for the given child view.
* If the child cannot be dragged along the horizontal or vertical axis, motion
* along that axis will not count toward the slop check.
*
* @param child Child to check
* @param dx Motion since initial position along X axis
* @param dy Motion since initial position along Y axis
* @return true if the touch slop has been crossed
*/
private boolean checkTouchSlop(View child, float dx, float dy) {
if (child == null) {
return false;
}
final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
if (checkHorizontal && checkVertical) {
return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
} else if (checkHorizontal) {
return Math.abs(dx) > mTouchSlop;
} else if (checkVertical) {
return Math.abs(dy) > mTouchSlop;
}
return false;
}
/**
* Check if any pointer tracked in the current gesture has crossed
* the required slop threshold.
*
* <p>This depends on internal state populated by
* {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
* {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
* the results of this method after all currently available touch data
* has been provided to one of these two methods.</p>
*
* @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
* {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
* @return true if the slop threshold has been crossed, false otherwise
*/
public boolean checkTouchSlop(int directions) {
final int count = mInitialMotionX.length;
for (int i = 0; i < count; i++) {
if (checkTouchSlop(directions, i)) {
return true;
}
}
return false;
}
/**
* Check if the specified pointer tracked in the current gesture has crossed
* the required slop threshold.
*
* <p>This depends on internal state populated by
* {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or
* {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on
* the results of this method after all currently available touch data
* has been provided to one of these two methods.</p>
*
* @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},
* {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}
* @param pointerId ID of the pointer to slop check as specified by MotionEvent
* @return true if the slop threshold has been crossed, false otherwise
*/
public boolean checkTouchSlop(int directions, int pointerId) {
if (!isPointerDown(pointerId)) {
return false;
}
final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL;
final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL;
final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId];
final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId];
if (checkHorizontal && checkVertical) {
return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
} else if (checkHorizontal) {
return Math.abs(dx) > mTouchSlop;
} else if (checkVertical) {
return Math.abs(dy) > mTouchSlop;
}
return false;
}
/**
* Check if any of the edges specified were initially touched in the currently active gesture.
* If there is no currently active gesture this method will return false.
*
* @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
* {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
* {@link #EDGE_ALL}
* @return true if any of the edges specified were initially touched in the current gesture
*/
public boolean isEdgeTouched(int edges) {
final int count = mInitialEdgesTouched.length;
for (int i = 0; i < count; i++) {
if (isEdgeTouched(edges, i)) {
return true;
}
}
return false;
}
/**
* Check if any of the edges specified were initially touched by the pointer with
* the specified ID. If there is no currently active gesture or if there is no pointer with
* the given ID currently down this method will return false.
*
* @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},
* {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and
* {@link #EDGE_ALL}
* @return true if any of the edges specified were initially touched in the current gesture
*/
public boolean isEdgeTouched(int edges, int pointerId) {
return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0;
}
public boolean isDragging() {
return mDragState == STATE_DRAGGING;
}
private void releaseViewForPointerUp() {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final float xvel = clampMag(
VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
mMinVelocity, mMaxVelocity);
final float yvel = clampMag(
VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
mMinVelocity, mMaxVelocity);
dispatchViewReleased(xvel, yvel);
}
private void dragTo(int left, int top, int dx, int dy) {
int clampedX = left;
int clampedY = top;
final int oldLeft = mCapturedView.getLeft();
final int oldTop = mCapturedView.getTop();
if (dx != 0) {
clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
}
if (dy != 0) {
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
mCapturedView.offsetTopAndBottom(clampedY - oldTop);
}
if (dx != 0 || dy != 0) {
final int clampedDx = clampedX - oldLeft;
final int clampedDy = clampedY - oldTop;
mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
clampedDx, clampedDy);
}
}
/**
* Determine if the currently captured view is under the given point in the
* parent view's coordinate system. If there is no captured view this method
* will return false.
*
* @param x X position to test in the parent's coordinate system
* @param y Y position to test in the parent's coordinate system
* @return true if the captured view is under the given point, false otherwise
*/
public boolean isCapturedViewUnder(int x, int y) {
return isViewUnder(mCapturedView, x, y);
}
/**
* Determine if the supplied view is under the given point in the
* parent view's coordinate system.
*
* @param view Child view of the parent to hit test
* @param x X position to test in the parent's coordinate system
* @param y Y position to test in the parent's coordinate system
* @return true if the supplied view is under the given point, false otherwise
*/
public boolean isViewUnder(View view, int x, int y) {
if (view == null) {
return false;
}
return x >= view.getLeft() &&
x < view.getRight() &&
y >= view.getTop() &&
y < view.getBottom();
}
/**
* Find the topmost child under the given point within the parent view's coordinate system.
* The child order is determined using {@link Callback#getOrderedChildIndex(int)}.
*
* @param x X position to test in the parent's coordinate system
* @param y Y position to test in the parent's coordinate system
* @return The topmost child view under (x, y) or null if none found.
*/
public View findTopChildUnder(int x, int y) {
final int childCount = mParentView.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
if (x >= child.getLeft() && x < child.getRight() &&
y >= child.getTop() && y < child.getBottom()) {
return child;
}
}
return null;
}
private int getEdgesTouched(int x, int y) {
int result = 0;
if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;
return result;
}
}
================================================
FILE: library/src/main/res/drawable/above_shadow.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#20000000"
android:endColor="@android:color/transparent"
android:angle="90" >
</gradient>
</shape>
================================================
FILE: library/src/main/res/drawable/below_shadow.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#20000000"
android:endColor="@android:color/transparent"
android:angle="270" >
</gradient>
</shape>
================================================
FILE: library/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SlidingUpPanelLayout">
<attr name="umanoPanelHeight" format="dimension" />
<attr name="umanoShadowHeight" format="dimension" />
<attr name="umanoParallaxOffset" format="dimension" />
<attr name="umanoFadeColor" format="color" />
<attr name="umanoFlingVelocity" format="integer" />
<attr name="umanoDragView" format="reference" />
<attr name="umanoScrollableView" format="reference" />
<attr name="umanoOverlay" format="boolean"/>
<attr name="umanoClipPanel" format="boolean"/>
<attr name="umanoAnchorPoint" format="float" />
<attr name="umanoInitialState" format="enum">
<enum name="expanded" value="0" />
<enum name="collapsed" value="1" />
<enum name="anchored" value="2" />
<enum name="hidden" value="3" />
</attr>
<attr name="umanoScrollInterpolator" format="reference" />
</declare-styleable>
</resources>
================================================
FILE: maven_push.gradle
================================================
/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'maven'
apply plugin: 'signing'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
}
================================================
FILE: settings.gradle
================================================
include ':library'
include ':demo'
gitextract_is6jifad/ ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.gradle ├── demo/ │ ├── build.gradle │ ├── settings.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── sothree/ │ │ └── slidinguppanel/ │ │ └── demo/ │ │ └── DemoActivity.java │ └── res/ │ ├── layout/ │ │ └── activity_demo.xml │ ├── menu/ │ │ └── demo.xml │ └── values/ │ ├── strings.xml │ └── styles.xml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── library/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── sothree/ │ │ └── slidinguppanel/ │ │ ├── ScrollableViewHelper.java │ │ ├── SlidingUpPanelLayout.java │ │ └── ViewDragHelper.java │ └── res/ │ ├── drawable/ │ │ ├── above_shadow.xml │ │ └── below_shadow.xml │ └── values/ │ └── attrs.xml ├── maven_push.gradle └── settings.gradle
SYMBOL INDEX (164 symbols across 4 files)
FILE: demo/src/main/java/com/sothree/slidinguppanel/demo/DemoActivity.java
class DemoActivity (line 29) | public class DemoActivity extends AppCompatActivity {
method onCreate (line 34) | @Override
method onCreateOptionsMenu (line 120) | @Override
method onPrepareOptionsMenu (line 135) | @Override
method onOptionsItemSelected (line 140) | @Override
method onBackPressed (line 173) | @Override
FILE: library/src/main/java/com/sothree/slidinguppanel/ScrollableViewHelper.java
class ScrollableViewHelper (line 13) | public class ScrollableViewHelper {
method getScrollableViewScrollPosition (line 24) | public int getScrollableViewScrollPosition(View scrollableView, boolea...
FILE: library/src/main/java/com/sothree/slidinguppanel/SlidingUpPanelLayout.java
class SlidingUpPanelLayout (line 30) | public class SlidingUpPanelLayout extends ViewGroup {
type PanelState (line 169) | public enum PanelState {
type PanelSlideListener (line 234) | public interface PanelSlideListener {
method onPanelSlide (line 241) | public void onPanelSlide(View panel, float slideOffset);
method onPanelStateChanged (line 248) | public void onPanelStateChanged(View panel, PanelState previousState...
class SimplePanelSlideListener (line 255) | public static class SimplePanelSlideListener implements PanelSlideList...
method onPanelSlide (line 256) | @Override
method onPanelStateChanged (line 260) | @Override
method SlidingUpPanelLayout (line 265) | public SlidingUpPanelLayout(Context context) {
method SlidingUpPanelLayout (line 269) | public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
method SlidingUpPanelLayout (line 273) | public SlidingUpPanelLayout(Context context, AttributeSet attrs, int d...
method onFinishInflate (line 353) | @Override
method setGravity (line 364) | public void setGravity(int gravity) {
method setCoveredFadeColor (line 380) | public void setCoveredFadeColor(int color) {
method getCoveredFadeColor (line 388) | public int getCoveredFadeColor() {
method setTouchEnabled (line 397) | public void setTouchEnabled(boolean enabled) {
method isTouchEnabled (line 401) | public boolean isTouchEnabled() {
method setPanelHeight (line 410) | public void setPanelHeight(int val) {
method smoothToBottom (line 427) | protected void smoothToBottom() {
method getShadowHeight (line 434) | public int getShadowHeight() {
method setShadowHeight (line 443) | public void setShadowHeight(int val) {
method getPanelHeight (line 453) | public int getPanelHeight() {
method getCurrentParallaxOffset (line 460) | public int getCurrentParallaxOffset() {
method setParallaxOffset (line 471) | public void setParallaxOffset(int val) {
method getMinFlingVelocity (line 481) | public int getMinFlingVelocity() {
method setMinFlingVelocity (line 490) | public void setMinFlingVelocity(int val) {
method addPanelSlideListener (line 499) | public void addPanelSlideListener(PanelSlideListener listener) {
method removePanelSlideListener (line 510) | public void removePanelSlideListener(PanelSlideListener listener) {
method setFadeOnClickListener (line 523) | public void setFadeOnClickListener(View.OnClickListener listener) {
method setDragView (line 532) | public void setDragView(View dragView) {
method setDragView (line 565) | public void setDragView(int dragViewResId) {
method setScrollableView (line 576) | public void setScrollableView(View scrollableView) {
method setScrollableViewHelper (line 585) | public void setScrollableViewHelper(ScrollableViewHelper helper) {
method setAnchorPoint (line 595) | public void setAnchorPoint(float anchorPoint) {
method getAnchorPoint (line 608) | public float getAnchorPoint() {
method setOverlayed (line 617) | public void setOverlayed(boolean overlayed) {
method isOverlayed (line 624) | public boolean isOverlayed() {
method setClipPanel (line 633) | public void setClipPanel(boolean clip) {
method isClipPanel (line 640) | public boolean isClipPanel() {
method dispatchOnPanelSlide (line 645) | void dispatchOnPanelSlide(View panel) {
method dispatchOnPanelStateChanged (line 654) | void dispatchOnPanelStateChanged(View panel, PanelState previousState,...
method updateObscuredViewVisibility (line 663) | void updateObscuredViewVisibility() {
method setAllChildrenVisible (line 698) | void setAllChildrenVisible() {
method hasOpaqueBackground (line 707) | private static boolean hasOpaqueBackground(View v) {
method onAttachedToWindow (line 712) | @Override
method onDetachedFromWindow (line 718) | @Override
method onMeasure (line 724) | @Override
method onLayout (line 813) | @Override
method onSizeChanged (line 874) | @Override
method onInterceptTouchEvent (line 883) | @Override
method onTouchEvent (line 943) | @Override
method dispatchTouchEvent (line 957) | @Override
method isViewUnder (line 1047) | private boolean isViewUnder(View view, int x, int y) {
method computePanelTopPosition (line 1062) | private int computePanelTopPosition(float slideOffset) {
method computeSlideOffset (line 1074) | private float computeSlideOffset(int topPosition) {
method getPanelState (line 1090) | public PanelState getPanelState() {
method setPanelState (line 1099) | public void setPanelState(PanelState state) {
method setPanelStateInternal (line 1140) | private void setPanelStateInternal(PanelState state) {
method applyParallaxForCurrentSlideOffset (line 1150) | @SuppressLint("NewApi")
method onPanelDragged (line 1158) | private void onPanelDragged(int newTop) {
method drawChild (line 1186) | @Override
method smoothSlideTo (line 1230) | boolean smoothSlideTo(float slideOffset, int velocity) {
method computeScroll (line 1246) | @Override
method draw (line 1258) | @Override
method canScroll (line 1291) | protected boolean canScroll(View v, boolean checkV, int dx, int x, int...
method generateDefaultLayoutParams (line 1312) | @Override
method generateLayoutParams (line 1317) | @Override
method checkLayoutParams (line 1324) | @Override
method generateLayoutParams (line 1329) | @Override
method onSaveInstanceState (line 1334) | @Override
method onRestoreInstanceState (line 1342) | @Override
class DragHelperCallback (line 1353) | private class DragHelperCallback extends ViewDragHelper.Callback {
method tryCaptureView (line 1355) | @Override
method onViewDragStateChanged (line 1361) | @Override
method onViewCaptured (line 1382) | @Override
method onViewPositionChanged (line 1387) | @Override
method onViewReleased (line 1393) | @Override
method getViewVerticalDragRange (line 1429) | @Override
method clampViewPositionVertical (line 1434) | @Override
class LayoutParams (line 1446) | public static class LayoutParams extends ViewGroup.MarginLayoutParams {
method LayoutParams (line 1453) | public LayoutParams() {
method LayoutParams (line 1457) | public LayoutParams(int width, int height) {
method LayoutParams (line 1461) | public LayoutParams(int width, int height, float weight) {
method LayoutParams (line 1466) | public LayoutParams(android.view.ViewGroup.LayoutParams source) {
method LayoutParams (line 1470) | public LayoutParams(MarginLayoutParams source) {
method LayoutParams (line 1474) | public LayoutParams(LayoutParams source) {
method LayoutParams (line 1478) | public LayoutParams(Context c, AttributeSet attrs) {
FILE: library/src/main/java/com/sothree/slidinguppanel/ViewDragHelper.java
class ViewDragHelper (line 39) | public class ViewDragHelper {
class Callback (line 149) | public static abstract class Callback {
method onViewDragStateChanged (line 160) | public void onViewDragStateChanged(int state) {}
method onViewPositionChanged (line 171) | public void onViewPositionChanged(View changedView, int left, int to...
method onViewCaptured (line 182) | public void onViewCaptured(View capturedChild, int activePointerId) {}
method onViewReleased (line 202) | public void onViewReleased(View releasedChild, float xvel, float yve...
method onEdgeTouched (line 215) | public void onEdgeTouched(int edgeFlags, int pointerId) {}
method onEdgeLock (line 226) | public boolean onEdgeLock(int edgeFlags) {
method onEdgeDragStarted (line 241) | public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
method getOrderedChildIndex (line 249) | public int getOrderedChildIndex(int index) {
method getViewHorizontalDragRange (line 260) | public int getViewHorizontalDragRange(View child) {
method getViewVerticalDragRange (line 271) | public int getViewVerticalDragRange(View child) {
method tryCaptureView (line 291) | public abstract boolean tryCaptureView(View child, int pointerId);
method clampViewPositionHorizontal (line 304) | public int clampViewPositionHorizontal(View child, int left, int dx) {
method clampViewPositionVertical (line 319) | public int clampViewPositionVertical(View child, int top, int dy) {
method getInterpolation (line 328) | public float getInterpolation(float t) {
method run (line 335) | public void run() {
method create (line 347) | public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
method create (line 359) | public static ViewDragHelper create(ViewGroup forParent, Interpolator ...
method create (line 372) | public static ViewDragHelper create(ViewGroup forParent, float sensiti...
method create (line 388) | public static ViewDragHelper create(ViewGroup forParent, float sensiti...
method ViewDragHelper (line 404) | private ViewDragHelper(Context context, ViewGroup forParent, Interpola...
method setMinVelocity (line 431) | public void setMinVelocity(float minVel) {
method getMinVelocity (line 442) | public float getMinVelocity() {
method getViewDragState (line 451) | public int getViewDragState() {
method setEdgeTrackingEnabled (line 467) | public void setEdgeTrackingEnabled(int edgeFlags) {
method getEdgeSize (line 478) | public int getEdgeSize() {
method captureChildView (line 490) | public void captureChildView(View childView, int activePointerId) {
method getCapturedView (line 505) | public View getCapturedView() {
method getActivePointerId (line 513) | public int getActivePointerId() {
method getTouchSlop (line 520) | public int getTouchSlop() {
method cancel (line 528) | public void cancel() {
method abort (line 542) | public void abort() {
method smoothSlideViewTo (line 569) | public boolean smoothSlideViewTo(View child, int finalLeft, int finalT...
method settleCapturedViewAt (line 587) | public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
method forceSettleCapturedViewAt (line 607) | private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop,...
method computeSettleDuration (line 627) | private int computeSettleDuration(View child, int dx, int dy, int xvel...
method computeAxisDuration (line 648) | private int computeAxisDuration(int delta, int velocity, int motionRan...
method clampMag (line 680) | private int clampMag(int value, int absMin, int absMax) {
method clampMag (line 697) | private float clampMag(float value, float absMin, float absMax) {
method distanceInfluenceForSnapDuration (line 704) | private float distanceInfluenceForSnapDuration(float f) {
method flingCapturedView (line 720) | public void flingCapturedView(int minLeft, int minTop, int maxLeft, in...
method continueSettling (line 745) | public boolean continueSettling(boolean deferCallbacks) {
method dispatchViewReleased (line 799) | private void dispatchViewReleased(float xvel, float yvel) {
method clearMotionHistory (line 810) | private void clearMotionHistory() {
method clearMotionHistory (line 824) | private void clearMotionHistory(int pointerId) {
method ensureMotionHistorySizeForId (line 838) | private void ensureMotionHistorySizeForId(int pointerId) {
method saveInitialMotion (line 868) | private void saveInitialMotion(float x, float y, int pointerId) {
method saveLastMotion (line 876) | private void saveLastMotion(MotionEvent ev) {
method isPointerDown (line 904) | public boolean isPointerDown(int pointerId) {
method setDragState (line 908) | void setDragState(int state) {
method tryCaptureViewForDrag (line 927) | boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
method canScroll (line 952) | protected boolean canScroll(View v, boolean checkV, int dx, int dy, in...
method shouldInterceptTouchEvent (line 983) | public boolean shouldInterceptTouchEvent(MotionEvent ev) {
method processTouchEvent (line 1093) | public void processTouchEvent(MotionEvent ev) {
method reportNewEdgeDrags (line 1245) | private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
method checkNewEdgeDrag (line 1266) | private boolean checkNewEdgeDrag(float delta, float odelta, int pointe...
method checkTouchSlop (line 1293) | private boolean checkTouchSlop(View child, float dx, float dy) {
method checkTouchSlop (line 1324) | public boolean checkTouchSlop(int directions) {
method checkTouchSlop (line 1349) | public boolean checkTouchSlop(int directions, int pointerId) {
method isEdgeTouched (line 1379) | public boolean isEdgeTouched(int edges) {
method isEdgeTouched (line 1399) | public boolean isEdgeTouched(int edges, int pointerId) {
method isDragging (line 1403) | public boolean isDragging() {
method releaseViewForPointerUp (line 1407) | private void releaseViewForPointerUp() {
method dragTo (line 1418) | private void dragTo(int left, int top, int dx, int dy) {
method isCapturedViewUnder (line 1449) | public boolean isCapturedViewUnder(int x, int y) {
method isViewUnder (line 1462) | public boolean isViewUnder(View view, int x, int y) {
method findTopChildUnder (line 1480) | public View findTopChildUnder(int x, int y) {
method getEdgesTouched (line 1492) | private int getEdgesTouched(int x, int y) {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (178K chars).
[
{
"path": ".gitignore",
"chars": 280,
"preview": "#Android generated\nbin\ngen\ngen*\n\n#Eclipse\n.project\n.classpath\n.settings\n\n#IntelliJ IDEA\n.idea\n*.iml\n*.ipr\n*.iws\nout\n\n#Ma"
},
{
"path": ".travis.yml",
"chars": 337,
"preview": "language: android\njdk: oraclejdk7\n\nandroid:\n components:\n - tools\n - platform-tools\n - extra-android-m2reposit"
},
{
"path": "LICENSE.txt",
"chars": 11343,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 9878,
"preview": "[](https:"
},
{
"path": "build.gradle",
"chars": 615,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n repo"
},
{
"path": "demo/build.gradle",
"chars": 367,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion 26\n buildToolsVersion \"26.0.1\"\n\n lintOptions {\n"
},
{
"path": "demo/settings.gradle",
"chars": 124,
"preview": "include ':Tnt2'\n\ninclude 'androidslidingup'\n\nproject(':androidslidingup').projectDir = new File(settingsDir, '../library"
},
{
"path": "demo/src/main/AndroidManifest.xml",
"chars": 1033,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "demo/src/main/java/com/sothree/slidinguppanel/demo/DemoActivity.java",
"chars": 6384,
"preview": "package com.sothree.slidinguppanel.demo;\n\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Bundl"
},
{
"path": "demo/src/main/res/layout/activity_demo.xml",
"chars": 6823,
"preview": "<com.sothree.slidinguppanel.SlidingUpPanelLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "demo/src/main/res/menu/demo.xml",
"chars": 496,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:sothree=\"http://schemas.android.com/apk/res-a"
},
{
"path": "demo/src/main/res/values/strings.xml",
"chars": 695,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\">Sliding Up Panel Demo</string>\n <strin"
},
{
"path": "demo/src/main/res/values/styles.xml",
"chars": 953,
"preview": "<resources>\n\n <!--\n Base application theme, dependent on API level. This theme is replaced\n by AppBaseT"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 231,
"preview": "#Tue Aug 23 22:44:32 CEST 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
},
{
"path": "gradle.properties",
"chars": 576,
"preview": "VERSION_NAME=3.4.0\nVERSION_CODE=17\nGROUP=com.sothree.slidinguppanel\n\nPOM_DESCRIPTION=Android Sliding Up Panel Library\nPO"
},
{
"path": "gradlew",
"chars": 5046,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "library/build.gradle",
"chars": 411,
"preview": "apply plugin: 'com.android.library'\n\nrepositories {\n jcenter()\n}\n\ndependencies {\n compile 'com.android.support:supp"
},
{
"path": "library/gradle.properties",
"chars": 84,
"preview": "POM_NAME=Android Sliding Up Panel Library\nPOM_ARTIFACT_ID=library\nPOM_PACKAGING=aar\n"
},
{
"path": "library/src/main/AndroidManifest.xml",
"chars": 306,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "library/src/main/java/com/sothree/slidinguppanel/ScrollableViewHelper.java",
"chars": 3345,
"preview": "package com.sothree.slidinguppanel;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\nimport and"
},
{
"path": "library/src/main/java/com/sothree/slidinguppanel/SlidingUpPanelLayout.java",
"chars": 51491,
"preview": "package com.sothree.slidinguppanel;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport andr"
},
{
"path": "library/src/main/java/com/sothree/slidinguppanel/ViewDragHelper.java",
"chars": 62149,
"preview": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "library/src/main/res/drawable/above_shadow.xml",
"chars": 269,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"> \n <gradie"
},
{
"path": "library/src/main/res/drawable/below_shadow.xml",
"chars": 270,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"> \n <gradie"
},
{
"path": "library/src/main/res/values/attrs.xml",
"chars": 1049,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <declare-styleable name=\"SlidingUpPanelLayout\">\n <attr na"
},
{
"path": "maven_push.gradle",
"chars": 3635,
"preview": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not us"
},
{
"path": "settings.gradle",
"chars": 35,
"preview": "include ':library'\ninclude ':demo'\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the umano/AndroidSlidingUpPanel GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (166.6 KB), approximately 39.1k tokens, and a symbol index with 164 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.