constans rename: remove Library suffix; add slidableactivity
This commit is contained in:
+1
-1
Submodule MediaServiceCore updated: d98d78264a...9364d97660
+1
-1
Submodule SharedModules updated: da040cfec7...477bdb1170
+2
-2
@@ -50,8 +50,8 @@ allprojects {
|
||||
resolutionStrategy.force 'org.jetbrains.kotlin:kotlin-stdlib:' + kotlinVersion
|
||||
resolutionStrategy.force 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + kotlinxVersion
|
||||
resolutionStrategy.force 'androidx.core:core-ktx:' + kotlinCoreXVersion
|
||||
resolutionStrategy.force 'androidx.core:core:' + coreXLibraryVersion
|
||||
resolutionStrategy.force 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
resolutionStrategy.force 'androidx.core:core:' + coreXVersion
|
||||
resolutionStrategy.force 'androidx.annotation:annotation:' + annotationXVersion
|
||||
// Downgrade Cronet version for cronet-okhttp
|
||||
resolutionStrategy.force 'org.chromium.net:cronet-api:' + cronetApiVersion
|
||||
resolutionStrategy.force 'androidx.multidex:multidex:' + multiDexVersion
|
||||
|
||||
@@ -29,8 +29,8 @@ dependencies {
|
||||
|
||||
implementation project(':sharedutils')
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXLibraryVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXVersion
|
||||
implementation 'com.google.android.material:material:' + materialVersion
|
||||
implementation "com.google.android.flexbox:flexbox:" + flexboxVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXVersion
|
||||
}
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXLibraryVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXVersion
|
||||
testImplementation 'junit:junit:' + junitVersion
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
|
||||
@@ -77,7 +77,7 @@ dependencies {
|
||||
|
||||
//////// END EXOPLAYER //////////
|
||||
|
||||
implementation 'androidx.media:media:' + mediaXLibraryVersion // exoplayer fix
|
||||
implementation 'androidx.media:media:' + mediaXVersion // exoplayer fix
|
||||
|
||||
implementation 'com.github.bumptech.glide:glide:' + glideVersion
|
||||
|
||||
|
||||
+3
-1
@@ -132,7 +132,9 @@ public class VideoLoaderController extends BasePlayerController {
|
||||
&& getPlayer().getDurationMs() - getPlayer().getPositionMs() < STREAM_END_THRESHOLD_MS) {
|
||||
getMainController().onPlayEnd();
|
||||
} else if (!getVideo().isLive && !getVideo().isLiveEnd) {
|
||||
YouTubeServiceManager.instance().applyNoPlaybackFix();
|
||||
//YouTubeServiceManager.instance().applyNoPlaybackFix();
|
||||
// Long loading subtitles cause hangs
|
||||
disableSubtitles();
|
||||
reloadVideo();
|
||||
}
|
||||
//} else if (!getVideo().isLive && !getVideo().isLiveEnd && !getPlayerTweaksData().isNetworkErrorFixingDisabled()) {
|
||||
|
||||
@@ -38,7 +38,7 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + kotlinVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXLibraryVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXVersion
|
||||
implementation 'androidx.core:core-ktx:' + kotlinCoreXVersion
|
||||
implementation 'androidx.constraintlayout:constraintlayout:' + constraintLayoutXVersion
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ project.ext {
|
||||
// MOD: use versions from sharedutils
|
||||
// robolectricVersion = '4.2'
|
||||
robolectricVersion = project.properties.robolectricVersion
|
||||
annotationXLibraryVersion = project.properties.annotationXLibraryVersion
|
||||
annotationXVersion = project.properties.annotationXVersion
|
||||
autoValueVersion = '1.6'
|
||||
checkerframeworkVersion = '2.5.0'
|
||||
// MOD: use versions from sharedutils
|
||||
// androidXTestVersion = '1.1.0'
|
||||
//protobufVersion = '4.32.1'
|
||||
protobufVersion = '3.17.3' // the latest for Android 4
|
||||
androidXTestVersion = project.properties.testXSupportLibraryVersion
|
||||
androidXTestVersion = project.properties.testXSupportVersion
|
||||
modulePrefix = ':'
|
||||
if (gradle.ext.has('exoplayerModulePrefix')) {
|
||||
modulePrefix += gradle.ext.exoplayerModulePrefix
|
||||
|
||||
@@ -61,7 +61,7 @@ android {
|
||||
dependencies {
|
||||
// MODIFIED: add custom dns resolver
|
||||
implementation project(':sharedutils')
|
||||
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
implementation 'androidx.annotation:annotation:' + annotationXVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
|
||||
androidTestImplementation 'androidx.test:runner:' + androidXTestVersion
|
||||
|
||||
@@ -30,9 +30,9 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXVersion
|
||||
implementation 'com.google.android.material:material:' + materialVersion
|
||||
implementation 'androidx.fragment:fragment:' + annotationXLibraryVersion
|
||||
implementation 'androidx.fragment:fragment:' + annotationXVersion
|
||||
|
||||
implementation project(':fragment-1.1.0')
|
||||
}
|
||||
@@ -33,9 +33,9 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
api 'androidx.activity:activity:' + leanbackXLibraryVersion // used inside other app modules
|
||||
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
implementation 'androidx.core:core:' + coreXLibraryVersion
|
||||
implementation 'androidx.viewpager:viewpager:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.loader:loader:' + leanbackXLibraryVersion
|
||||
api 'androidx.activity:activity:' + activityXVersion // used inside other app modules
|
||||
implementation 'androidx.annotation:annotation:' + annotationXVersion
|
||||
implementation 'androidx.core:core:' + coreXVersion
|
||||
implementation 'androidx.viewpager:viewpager:' + viewpagerXVersion
|
||||
implementation 'androidx.loader:loader:' + loaderXVersion
|
||||
}
|
||||
|
||||
@@ -33,12 +33,11 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
implementation 'androidx.core:core:' + coreXLibraryVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion
|
||||
implementation 'androidx.fragment:fragment:' + annotationXLibraryVersion
|
||||
implementation 'androidx.media:media:' + mediaXLibraryVersion
|
||||
implementation 'androidx.interpolator:interpolator:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.annotation:annotation:' + annotationXVersion
|
||||
implementation 'androidx.core:core:' + coreXVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXVersion
|
||||
implementation 'androidx.media:media:' + mediaXVersion
|
||||
implementation 'androidx.interpolator:interpolator:' + interpolatorXVersion
|
||||
|
||||
implementation project(':fragment-1.1.0')
|
||||
implementation project(':sharedutils')
|
||||
|
||||
@@ -46,9 +46,9 @@ dependencies {
|
||||
implementation project(':youtubeapi')
|
||||
|
||||
//implementation 'com.android.support:leanback-v17:' + supportLibraryVersion
|
||||
implementation 'androidx.leanback:leanback:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.tvprovider:tvprovider:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.recommendation:recommendation:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.leanback:leanback:' + leanbackXVersion
|
||||
implementation 'androidx.tvprovider:tvprovider:' + leanbackXVersion
|
||||
implementation 'androidx.recommendation:recommendation:' + leanbackXVersion
|
||||
implementation 'com.github.bumptech.glide:glide:' + glideVersion
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:' + rxAndroidVersion
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
include ':smarttubetv', ':common', ':chatkit', ':leanbackassistant', ':leanback-1.0.0', ':fragment-1.1.0', ':filepicker-lib', ':doubletapplayerview'
|
||||
include ':smarttubetv', ':common', ':chatkit', ':leanbackassistant', ':leanback-1.0.0', ':fragment-1.1.0', ':filepicker-lib', ':doubletapplayerview', ':slidableactivity'
|
||||
|
||||
def rootDir = settingsDir
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
*.iml
|
||||
@@ -0,0 +1,201 @@
|
||||
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 {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,42 @@
|
||||
apply from: gradle.ext.sharedModulesConstants
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.properties.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion project.properties.minSdkVersion
|
||||
targetSdkVersion project.properties.targetSdkVersion
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode 11
|
||||
versionName '2.1.0'
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.returnDefaultValues = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.legacy:legacy-support-v4:' + legacySupportV4XVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXVersion
|
||||
implementation 'androidx.cardview:cardview:' + cardviewXVersion
|
||||
implementation 'com.52inc:52Kit-core:0.6.2'
|
||||
|
||||
implementation project(':fragment-1.1.0')
|
||||
|
||||
testImplementation 'junit:junit:' + junitVersion
|
||||
testImplementation 'org.mockito:mockito-core:' + mockitoVersion
|
||||
testImplementation 'org.hamcrest:hamcrest-library:' + hamcrestVersion
|
||||
testImplementation 'org.powermock:powermock-api-mockito:' + powermockVersion
|
||||
testImplementation 'org.powermock:powermock-module-junit4:' + powermockVersion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
POM_NAME=Slidable Activity
|
||||
POM_ARTIFACT_ID=slidableactivity
|
||||
POM_PACKAGING=aar
|
||||
@@ -0,0 +1,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.r0adkll.slidr">
|
||||
|
||||
<!-- Override min API 21 -->
|
||||
<uses-sdk tools:overrideLibrary="
|
||||
com.ftinc.kit"/>
|
||||
|
||||
<application />
|
||||
</manifest>
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.r0adkll.slidr;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
import com.r0adkll.slidr.widget.SliderPanel;
|
||||
|
||||
|
||||
class ColorPanelSlideListener implements SliderPanel.OnPanelSlideListener {
|
||||
|
||||
private final Activity activity;
|
||||
private final int primaryColor;
|
||||
private final int secondaryColor;
|
||||
private final ArgbEvaluator evaluator = new ArgbEvaluator();
|
||||
|
||||
|
||||
ColorPanelSlideListener(Activity activity, @ColorInt int primaryColor, @ColorInt int secondaryColor) {
|
||||
this.activity = activity;
|
||||
this.primaryColor = primaryColor;
|
||||
this.secondaryColor = secondaryColor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSlideChange(float percent) {
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && areColorsValid()){
|
||||
int newColor = (int) evaluator.evaluate(percent, getPrimaryColor(), getSecondaryColor());
|
||||
activity.getWindow().setStatusBarColor(newColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected int getPrimaryColor() {
|
||||
return primaryColor;
|
||||
}
|
||||
|
||||
|
||||
protected int getSecondaryColor() {
|
||||
return secondaryColor;
|
||||
}
|
||||
|
||||
|
||||
protected boolean areColorsValid() {
|
||||
return getPrimaryColor() != -1 && getSecondaryColor() != -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.r0adkll.slidr;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.r0adkll.slidr.model.SlidrConfig;
|
||||
|
||||
|
||||
class ConfigPanelSlideListener extends ColorPanelSlideListener {
|
||||
|
||||
private final SlidrConfig config;
|
||||
|
||||
|
||||
ConfigPanelSlideListener(@NonNull Activity activity, @NonNull SlidrConfig config) {
|
||||
super(activity, -1, -1);
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
if(config.getListener() != null){
|
||||
config.getListener().onSlideStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
if(config.getListener() != null){
|
||||
if(config.getListener().onSlideClosed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onClosed();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
if(config.getListener() != null){
|
||||
config.getListener().onSlideOpened();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSlideChange(float percent) {
|
||||
super.onSlideChange(percent);
|
||||
if(config.getListener() != null){
|
||||
config.getListener().onSlideChange(percent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int getPrimaryColor() {
|
||||
return config.getPrimaryColor();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int getSecondaryColor() {
|
||||
return config.getSecondaryColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.r0adkll.slidr;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import android.view.View;
|
||||
|
||||
import com.r0adkll.slidr.model.SlidrConfig;
|
||||
import com.r0adkll.slidr.widget.SliderPanel;
|
||||
|
||||
|
||||
class FragmentPanelSlideListener implements SliderPanel.OnPanelSlideListener {
|
||||
|
||||
private final View view;
|
||||
private final SlidrConfig config;
|
||||
|
||||
|
||||
FragmentPanelSlideListener(@NonNull View view, @NonNull SlidrConfig config) {
|
||||
this.view = view;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
if (config.getListener() != null) {
|
||||
config.getListener().onSlideStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
if (config.getListener() != null) {
|
||||
if(config.getListener().onSlideClosed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that we are attached to a FragmentActivity
|
||||
if (view.getContext() instanceof FragmentActivity) {
|
||||
final FragmentActivity activity = (FragmentActivity) view.getContext();
|
||||
if (activity.getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(0, 0);
|
||||
} else {
|
||||
activity.getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
if (config.getListener() != null) {
|
||||
config.getListener().onSlideOpened();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSlideChange(float percent) {
|
||||
if (config.getListener() != null) {
|
||||
config.getListener().onSlideChange(percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.r0adkll.slidr;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.r0adkll.slidr.model.SlidrConfig;
|
||||
import com.r0adkll.slidr.model.SlidrInterface;
|
||||
import com.r0adkll.slidr.widget.SliderPanel;
|
||||
|
||||
|
||||
/**
|
||||
* This attacher class is used to attach the sliding mechanism to any {@link android.app.Activity}
|
||||
* that lets the user slide (or swipe) the activity away as a form of back or up action. The action
|
||||
* causes {@link android.app.Activity#finish()} to be called.
|
||||
*/
|
||||
public final class Slidr {
|
||||
|
||||
/**
|
||||
* Attach a slideable mechanism to an activity that adds the slide to dismiss functionality
|
||||
*
|
||||
* @param activity the activity to attach the slider to
|
||||
* @return a {@link com.r0adkll.slidr.model.SlidrInterface} that allows
|
||||
* the user to lock/unlock the sliding mechanism for whatever purpose.
|
||||
*/
|
||||
@NonNull
|
||||
public static SlidrInterface attach(@NonNull Activity activity) {
|
||||
return attach(activity, -1, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach a slideable mechanism to an activity that adds the slide to dismiss functionality
|
||||
* and allows for the statusbar to transition between colors
|
||||
*
|
||||
* @param activity the activity to attach the slider to
|
||||
* @param statusBarColor1 the primaryDark status bar color of the interface that this will slide back to
|
||||
* @param statusBarColor2 the primaryDark status bar color of the activity this is attaching to that will transition
|
||||
* back to the statusBarColor1 color
|
||||
* @return a {@link com.r0adkll.slidr.model.SlidrInterface} that allows
|
||||
* the user to lock/unlock the sliding mechanism for whatever purpose.
|
||||
*/
|
||||
@NonNull
|
||||
public static SlidrInterface attach(@NonNull Activity activity, @ColorInt int statusBarColor1,
|
||||
@ColorInt int statusBarColor2) {
|
||||
|
||||
// Setup the slider panel and attach it to the decor
|
||||
final SliderPanel panel = attachSliderPanel(activity, null);
|
||||
|
||||
// Set the panel slide listener for when it becomes closed or opened
|
||||
panel.setOnPanelSlideListener(new ColorPanelSlideListener(activity, statusBarColor1, statusBarColor2));
|
||||
|
||||
// Return the lock interface
|
||||
return panel.getDefaultInterface();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach a slider mechanism to an activity based on the passed {@link com.r0adkll.slidr.model.SlidrConfig}
|
||||
*
|
||||
* @param activity the activity to attach the slider to
|
||||
* @param config the slider configuration to make
|
||||
* @return a {@link com.r0adkll.slidr.model.SlidrInterface} that allows
|
||||
* the user to lock/unlock the sliding mechanism for whatever purpose.
|
||||
*/
|
||||
@NonNull
|
||||
public static SlidrInterface attach(@NonNull Activity activity, @NonNull SlidrConfig config) {
|
||||
|
||||
// Setup the slider panel and attach it to the decor
|
||||
final SliderPanel panel = attachSliderPanel(activity, config);
|
||||
|
||||
// Set the panel slide listener for when it becomes closed or opened
|
||||
panel.setOnPanelSlideListener(new ConfigPanelSlideListener(activity, config));
|
||||
|
||||
// Return the lock interface
|
||||
return panel.getDefaultInterface();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach a new {@link SliderPanel} to the root of the activity's content
|
||||
*/
|
||||
@NonNull
|
||||
private static SliderPanel attachSliderPanel(@NonNull Activity activity, @NonNull SlidrConfig config) {
|
||||
// Hijack the decorview
|
||||
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
|
||||
View oldScreen = decorView.getChildAt(0);
|
||||
decorView.removeViewAt(0);
|
||||
|
||||
// Setup the slider panel and attach it to the decor
|
||||
SliderPanel panel = new SliderPanel(activity, oldScreen, config);
|
||||
panel.setId(R.id.slidable_panel);
|
||||
oldScreen.setId(R.id.slidable_content);
|
||||
panel.addView(oldScreen);
|
||||
decorView.addView(panel, 0);
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach a slider mechanism to a fragment view replacing an internal view
|
||||
*
|
||||
* @param oldScreen the view within a fragment to replace
|
||||
* @param config the slider configuration to attach with
|
||||
* @return a {@link com.r0adkll.slidr.model.SlidrInterface} that allows
|
||||
* the user to lock/unlock the sliding mechanism for whatever purpose.
|
||||
*/
|
||||
@NonNull
|
||||
public static SlidrInterface replace(@NonNull final View oldScreen, @NonNull final SlidrConfig config) {
|
||||
ViewGroup parent = (ViewGroup) oldScreen.getParent();
|
||||
ViewGroup.LayoutParams params = oldScreen.getLayoutParams();
|
||||
parent.removeView(oldScreen);
|
||||
|
||||
// Setup the slider panel and attach it
|
||||
final SliderPanel panel = new SliderPanel(oldScreen.getContext(), oldScreen, config);
|
||||
panel.setId(R.id.slidable_panel);
|
||||
oldScreen.setId(R.id.slidable_content);
|
||||
|
||||
panel.addView(oldScreen);
|
||||
parent.addView(panel, 0, params);
|
||||
|
||||
// Set the panel slide listener for when it becomes closed or opened
|
||||
panel.setOnPanelSlideListener(new FragmentPanelSlideListener(oldScreen, config));
|
||||
|
||||
// Return the lock interface
|
||||
return panel.getDefaultInterface();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package com.r0adkll.slidr.model;
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
|
||||
|
||||
/**
|
||||
* This class contains the configuration information for all the options available in
|
||||
* this library
|
||||
*/
|
||||
public class SlidrConfig {
|
||||
|
||||
private int colorPrimary = -1;
|
||||
private int colorSecondary = -1;
|
||||
private float touchSize = -1f;
|
||||
private float sensitivity = 1f;
|
||||
private int scrimColor = Color.BLACK;
|
||||
private float scrimStartAlpha = 0.8f;
|
||||
private float scrimEndAlpha = 0f;
|
||||
private float velocityThreshold = 5f;
|
||||
private float distanceThreshold = 0.25f;
|
||||
private boolean edgeOnly = false;
|
||||
private float edgeSize = 0.18f;
|
||||
|
||||
private SlidrPosition position = SlidrPosition.LEFT;
|
||||
private SlidrListener listener;
|
||||
|
||||
|
||||
private SlidrConfig() {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
*
|
||||
* Getters
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the primary color that the slider will interpolate. That is this color is the color
|
||||
* of the status bar of the Activity you are returning to
|
||||
*
|
||||
* @return the primary status bar color
|
||||
*/
|
||||
public int getPrimaryColor(){
|
||||
return colorPrimary;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the secondary color that the slider will interpolatel That is the color of the Activity
|
||||
* that you are making slidable
|
||||
*
|
||||
* @return the secondary status bar color
|
||||
*/
|
||||
public int getSecondaryColor(){
|
||||
return colorSecondary;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the color of the background scrim
|
||||
*
|
||||
* @return the scrim color integer
|
||||
*/
|
||||
@ColorInt
|
||||
public int getScrimColor(){
|
||||
return scrimColor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get teh start alpha value for when the activity is not swiped at all
|
||||
*
|
||||
* @return the start alpha value (0.0 to 1.0)
|
||||
*/
|
||||
public float getScrimStartAlpha(){
|
||||
return scrimStartAlpha;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the end alpha value for when the user almost swipes the activity off the screen
|
||||
*
|
||||
* @return the end alpha value (0.0 to 1.0)
|
||||
*/
|
||||
public float getScrimEndAlpha(){
|
||||
return scrimEndAlpha;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the position of the slidable mechanism for this configuration. This is the position on
|
||||
* the screen that the user can swipe the activity away from
|
||||
*
|
||||
* @return the slider position
|
||||
*/
|
||||
public SlidrPosition getPosition(){
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the touch 'width' to be used in the gesture detection. This value should incorporate with
|
||||
* the device's touch slop
|
||||
*
|
||||
* @return the touch area size
|
||||
*/
|
||||
public float getTouchSize(){
|
||||
return touchSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the velocity threshold at which the slide action is completed regardless of offset
|
||||
* distance of the drag
|
||||
*
|
||||
* @return the velocity threshold
|
||||
*/
|
||||
public float getVelocityThreshold(){
|
||||
return velocityThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get at what % of the screen is the minimum viable distance the activity has to be dragged
|
||||
* in-order to be slinged off the screen
|
||||
*
|
||||
* @return the distant threshold as a percentage of the screen size (width or height)
|
||||
*/
|
||||
public float getDistanceThreshold(){
|
||||
return distanceThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the touch sensitivity set in the {@link ViewDragHelper} when
|
||||
* creating it.
|
||||
*
|
||||
* @return the touch sensitivity
|
||||
*/
|
||||
public float getSensitivity(){
|
||||
return sensitivity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the slidr listener set by the user to respond to certain events in the sliding
|
||||
* mechanism.
|
||||
*
|
||||
* @return the slidr listener
|
||||
*/
|
||||
public SlidrListener getListener(){
|
||||
return listener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Has the user configured slidr to only catch at the edge of the screen ?
|
||||
*
|
||||
* @return true if is edge capture only
|
||||
*/
|
||||
public boolean isEdgeOnly() {
|
||||
return edgeOnly;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the size of the edge field that is catchable
|
||||
*
|
||||
* @see #isEdgeOnly()
|
||||
* @return the size of the edge that is grabable
|
||||
*/
|
||||
public float getEdgeSize(float size) {
|
||||
return edgeSize * size;
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
*
|
||||
* Setters
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
public void setColorPrimary(int colorPrimary) {
|
||||
this.colorPrimary = colorPrimary;
|
||||
}
|
||||
|
||||
|
||||
public void setColorSecondary(int colorSecondary) {
|
||||
this.colorSecondary = colorSecondary;
|
||||
}
|
||||
|
||||
|
||||
public void setTouchSize(float touchSize) {
|
||||
this.touchSize = touchSize;
|
||||
}
|
||||
|
||||
|
||||
public void setSensitivity(float sensitivity) {
|
||||
this.sensitivity = sensitivity;
|
||||
}
|
||||
|
||||
|
||||
public void setScrimColor(@ColorInt int scrimColor) {
|
||||
this.scrimColor = scrimColor;
|
||||
}
|
||||
|
||||
|
||||
public void setScrimStartAlpha(float scrimStartAlpha) {
|
||||
this.scrimStartAlpha = scrimStartAlpha;
|
||||
}
|
||||
|
||||
|
||||
public void setScrimEndAlpha(float scrimEndAlpha) {
|
||||
this.scrimEndAlpha = scrimEndAlpha;
|
||||
}
|
||||
|
||||
|
||||
public void setVelocityThreshold(float velocityThreshold) {
|
||||
this.velocityThreshold = velocityThreshold;
|
||||
}
|
||||
|
||||
|
||||
public void setDistanceThreshold(float distanceThreshold) {
|
||||
this.distanceThreshold = distanceThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Builder for this configuration class. This is the only way to create a
|
||||
* configuration
|
||||
*/
|
||||
public static class Builder{
|
||||
|
||||
private SlidrConfig config;
|
||||
|
||||
public Builder(){
|
||||
config = new SlidrConfig();
|
||||
}
|
||||
|
||||
public Builder primaryColor(@ColorInt int color){
|
||||
config.colorPrimary = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder secondaryColor(@ColorInt int color){
|
||||
config.colorSecondary = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder position(SlidrPosition position){
|
||||
config.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder touchSize(float size){
|
||||
config.touchSize = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder sensitivity(float sensitivity){
|
||||
config.sensitivity = sensitivity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder scrimColor(@ColorInt int color){
|
||||
config.scrimColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder scrimStartAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha){
|
||||
config.scrimStartAlpha = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder scrimEndAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha){
|
||||
config.scrimEndAlpha = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder velocityThreshold(float threshold){
|
||||
config.velocityThreshold = threshold;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder distanceThreshold(@FloatRange(from = .1f, to = .9f) float threshold){
|
||||
config.distanceThreshold = threshold;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder edge(boolean flag){
|
||||
config.edgeOnly = flag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder edgeSize(@FloatRange(from = 0f, to = 1f) float edgeSize){
|
||||
config.edgeSize = edgeSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder listener(SlidrListener listener){
|
||||
config.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SlidrConfig build(){
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.r0adkll.slidr.model;
|
||||
|
||||
|
||||
public interface SlidrInterface {
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.r0adkll.slidr.model;
|
||||
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
|
||||
/**
|
||||
* This listener interface is for receiving events from the sliding panel such as state changes
|
||||
* and slide progress
|
||||
*/
|
||||
public interface SlidrListener {
|
||||
|
||||
/**
|
||||
* This is called when the {@link ViewDragHelper} calls it's
|
||||
* state change callback.
|
||||
*
|
||||
* @see ViewDragHelper#STATE_IDLE
|
||||
* @see ViewDragHelper#STATE_DRAGGING
|
||||
* @see ViewDragHelper#STATE_SETTLING
|
||||
*
|
||||
* @param state the {@link ViewDragHelper} state
|
||||
*/
|
||||
void onSlideStateChanged(int state);
|
||||
|
||||
void onSlideChange(float percent);
|
||||
|
||||
void onSlideOpened();
|
||||
|
||||
/**
|
||||
* @return <code>true</code> than event was processed in the callback.
|
||||
*/
|
||||
boolean onSlideClosed();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.r0adkll.slidr.model;
|
||||
|
||||
|
||||
public class SlidrListenerAdapter implements SlidrListener {
|
||||
|
||||
@Override
|
||||
public void onSlideStateChanged(int state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlideChange(float percent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlideOpened() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSlideClosed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.r0adkll.slidr.model;
|
||||
|
||||
|
||||
public enum SlidrPosition {
|
||||
|
||||
LEFT,
|
||||
RIGHT,
|
||||
TOP,
|
||||
BOTTOM,
|
||||
VERTICAL,
|
||||
HORIZONTAL
|
||||
}
|
||||
@@ -0,0 +1,1403 @@
|
||||
/**
|
||||
* Created by Gabriele Guerrisi on 24/11/2017.
|
||||
*/
|
||||
package com.r0adkll.slidr.util;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.core.view.MotionEventCompat;
|
||||
import androidx.core.view.VelocityTrackerCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.widget.ScrollerCompat;
|
||||
import android.util.Log;
|
||||
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>
|
||||
* <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>
|
||||
* <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>
|
||||
* <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, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apps should use ViewDragHelper.create() to get a new instance.
|
||||
* This will allow VDH to use internal compatibility implementations for different
|
||||
* platform versions.
|
||||
*
|
||||
* @param context Context to initialize config-dependent params from
|
||||
* @param forParent Parent view to monitor
|
||||
*/
|
||||
private ViewDragHelper(Context context, ViewGroup forParent, 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, 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>
|
||||
* <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;
|
||||
boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
|
||||
if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
|
||||
// If we're in an IDLE state to begin with and aren't moving anywhere, we
|
||||
// end up having a non-null capturedView with an IDLE dragState
|
||||
mCapturedView = null;
|
||||
}
|
||||
return continueSliding;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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 (dx != 0) {
|
||||
ViewCompat.offsetLeftAndRight(mCapturedView, dx);
|
||||
}
|
||||
if (dy != 0) {
|
||||
ViewCompat.offsetTopAndBottom(mCapturedView, 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 = false;
|
||||
}
|
||||
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 || !isPointerDown(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);
|
||||
// If pointer is invalid then skip saving on ACTION_MOVE.
|
||||
if (!isValidPointerForActionMove(pointerId)) {
|
||||
continue;
|
||||
}
|
||||
final float x = MotionEventCompat.getX(ev, i);
|
||||
final float y = MotionEventCompat.getY(ev, i);
|
||||
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>
|
||||
* <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) {
|
||||
mParentView.removeCallbacks(mSetIdleRunnable);
|
||||
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: {
|
||||
if (mInitialMotionX == null || mInitialMotionY == null) break;
|
||||
// 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; i++) {
|
||||
final int pointerId = MotionEventCompat.getPointerId(ev, i);
|
||||
// If pointer is invalid then skip the ACTION_MOVE.
|
||||
if (!isValidPointerForActionMove(pointerId)) 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];
|
||||
final View toCapture = findTopChildUnder((int) x, (int) y);
|
||||
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
|
||||
if (pastSlop) {
|
||||
// check the callback's
|
||||
// getView[Horizontal|Vertical]DragRange methods to know
|
||||
// if you can move at all along an axis, then see if it
|
||||
// would clamp to the same value. If you can't move at
|
||||
// all in every dimension with a nonzero range, bail.
|
||||
final int oldLeft = toCapture.getLeft();
|
||||
final int targetLeft = oldLeft + (int) dx;
|
||||
final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
|
||||
targetLeft, (int) dx);
|
||||
final int oldTop = toCapture.getTop();
|
||||
final int targetTop = oldTop + (int) dy;
|
||||
final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
|
||||
(int) dy);
|
||||
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
|
||||
toCapture);
|
||||
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
|
||||
if ((horizontalDragRange == 0 || horizontalDragRange > 0
|
||||
&& newLeft == oldLeft) && (verticalDragRange == 0
|
||||
|| verticalDragRange > 0 && newTop == oldTop)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
reportNewEdgeDrags(dx, dy, pointerId);
|
||||
if (mDragState == STATE_DRAGGING) {
|
||||
// Callback might have started an edge drag
|
||||
break;
|
||||
}
|
||||
if (pastSlop && 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) {
|
||||
// If pointer is invalid then skip the ACTION_MOVE.
|
||||
if (!isValidPointerForActionMove(mActivePointerId)) break;
|
||||
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);
|
||||
// If pointer is invalid then skip the ACTION_MOVE.
|
||||
if (!isValidPointerForActionMove(pointerId)) 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) x, (int) y);
|
||||
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;
|
||||
|
||||
float temp_dy = dy;
|
||||
if (temp_dy < 0)
|
||||
temp_dy = -temp_dy;
|
||||
|
||||
if (checkHorizontal && checkVertical) {
|
||||
return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
|
||||
} else if (checkVertical) {
|
||||
return Math.abs(dy) > mTouchSlop;
|
||||
} else if (checkHorizontal && 3 * temp_dy < dx) {
|
||||
return Math.abs(dx) > mTouchSlop;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any pointer tracked in the current gesture has crossed
|
||||
* the required slop threshold.
|
||||
* <p>
|
||||
* <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>
|
||||
* <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;
|
||||
}
|
||||
|
||||
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);
|
||||
ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
|
||||
}
|
||||
if (dy != 0) {
|
||||
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
|
||||
ViewCompat.offsetTopAndBottom(mCapturedView, 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;
|
||||
}
|
||||
|
||||
private boolean isValidPointerForActionMove(int pointerId) {
|
||||
if (!isPointerDown(pointerId)) {
|
||||
Log.e(TAG, "Ignoring pointerId=" + pointerId + " because ACTION_DOWN was not received "
|
||||
+ "for this pointer before ACTION_MOVE. It likely happened because "
|
||||
+ " ViewDragHelper did not receive all the events in the event stream.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.r0adkll.slidr.widget;
|
||||
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
|
||||
import com.r0adkll.slidr.model.SlidrPosition;
|
||||
|
||||
|
||||
final class ScrimRenderer {
|
||||
|
||||
private final View rootView;
|
||||
private final View decorView;
|
||||
private final Rect dirtyRect;
|
||||
|
||||
|
||||
ScrimRenderer(@NonNull View rootView, @NonNull View decorView) {
|
||||
this.rootView = rootView;
|
||||
this.decorView = decorView;
|
||||
dirtyRect = new Rect();
|
||||
}
|
||||
|
||||
|
||||
void render(Canvas canvas, SlidrPosition position, Paint paint) {
|
||||
switch (position) {
|
||||
case LEFT:
|
||||
renderLeft(canvas, paint);
|
||||
break;
|
||||
case RIGHT:
|
||||
renderRight(canvas, paint);
|
||||
break;
|
||||
case TOP:
|
||||
renderTop(canvas, paint);
|
||||
break;
|
||||
case BOTTOM:
|
||||
renderBottom(canvas, paint);
|
||||
break;
|
||||
case VERTICAL:
|
||||
renderVertical(canvas, paint);
|
||||
break;
|
||||
case HORIZONTAL:
|
||||
renderHorizontal(canvas, paint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rect getDirtyRect(SlidrPosition position) {
|
||||
switch (position) {
|
||||
case LEFT:
|
||||
dirtyRect.set(0, 0, decorView.getLeft(), rootView.getMeasuredHeight());
|
||||
break;
|
||||
case RIGHT:
|
||||
dirtyRect.set(decorView.getRight(), 0, rootView.getMeasuredWidth(), rootView.getMeasuredHeight());
|
||||
break;
|
||||
case TOP:
|
||||
dirtyRect.set(0, 0, rootView.getMeasuredWidth(), decorView.getTop());
|
||||
break;
|
||||
case BOTTOM:
|
||||
dirtyRect.set(0, decorView.getBottom(), rootView.getMeasuredWidth(), rootView.getMeasuredHeight());
|
||||
break;
|
||||
case VERTICAL:
|
||||
if (decorView.getTop() > 0) {
|
||||
dirtyRect.set(0, 0, rootView.getMeasuredWidth(), decorView.getTop());
|
||||
}
|
||||
else {
|
||||
dirtyRect.set(0, decorView.getBottom(), rootView.getMeasuredWidth(), rootView.getMeasuredHeight());
|
||||
}
|
||||
break;
|
||||
case HORIZONTAL:
|
||||
if (decorView.getLeft() > 0) {
|
||||
dirtyRect.set(0, 0, decorView.getLeft(), rootView.getMeasuredHeight());
|
||||
}
|
||||
else {
|
||||
dirtyRect.set(decorView.getRight(), 0, rootView.getMeasuredWidth(), rootView.getMeasuredHeight());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return dirtyRect;
|
||||
}
|
||||
|
||||
|
||||
private void renderLeft(Canvas canvas, Paint paint) {
|
||||
canvas.drawRect(0, 0, decorView.getLeft(), rootView.getMeasuredHeight(), paint);
|
||||
}
|
||||
|
||||
|
||||
private void renderRight(Canvas canvas, Paint paint) {
|
||||
canvas.drawRect(decorView.getRight(), 0, rootView.getMeasuredWidth(), rootView.getMeasuredHeight(), paint);
|
||||
}
|
||||
|
||||
|
||||
private void renderTop(Canvas canvas, Paint paint) {
|
||||
canvas.drawRect(0, 0, rootView.getMeasuredWidth(), decorView.getTop(), paint);
|
||||
}
|
||||
|
||||
|
||||
private void renderBottom(Canvas canvas, Paint paint) {
|
||||
canvas.drawRect(0, decorView.getBottom(), rootView.getMeasuredWidth(), rootView.getMeasuredHeight(), paint);
|
||||
}
|
||||
|
||||
|
||||
private void renderVertical(Canvas canvas, Paint paint) {
|
||||
if (decorView.getTop() > 0) {
|
||||
renderTop(canvas, paint);
|
||||
}
|
||||
else {
|
||||
renderBottom(canvas, paint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void renderHorizontal(Canvas canvas, Paint paint) {
|
||||
if (decorView.getLeft() > 0) {
|
||||
renderLeft(canvas, paint);
|
||||
}
|
||||
else {
|
||||
renderRight(canvas, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,782 @@
|
||||
package com.r0adkll.slidr.widget;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.ViewGroupCompat;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.r0adkll.slidr.model.SlidrConfig;
|
||||
import com.r0adkll.slidr.util.ViewDragHelper;
|
||||
import com.r0adkll.slidr.model.SlidrInterface;
|
||||
|
||||
|
||||
public class SliderPanel extends FrameLayout {
|
||||
|
||||
private static final int MIN_FLING_VELOCITY = 400; // dips per second
|
||||
|
||||
private int screenWidth;
|
||||
private int screenHeight;
|
||||
|
||||
private View decorView;
|
||||
private ViewDragHelper dragHelper;
|
||||
private OnPanelSlideListener listener;
|
||||
private Paint scrimPaint;
|
||||
private ScrimRenderer scrimRenderer;
|
||||
|
||||
private boolean isLocked = false;
|
||||
private boolean isEdgeTouched = false;
|
||||
private int edgePosition;
|
||||
|
||||
private SlidrConfig config;
|
||||
|
||||
|
||||
public SliderPanel(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
|
||||
public SliderPanel(Context context, View decorView, SlidrConfig config){
|
||||
super(context);
|
||||
this.decorView = decorView;
|
||||
this.config = (config == null ? new SlidrConfig.Builder().build() : config);
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
boolean interceptForDrag;
|
||||
|
||||
if(isLocked){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(config.isEdgeOnly()) {
|
||||
isEdgeTouched = canDragFromEdge(ev);
|
||||
}
|
||||
|
||||
// Fix for pull request #13 and issue #12
|
||||
try {
|
||||
interceptForDrag = dragHelper.shouldInterceptTouchEvent(ev);
|
||||
} catch (Exception e) {
|
||||
interceptForDrag = false;
|
||||
}
|
||||
|
||||
return interceptForDrag && !isLocked;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(isLocked){
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
dragHelper.processTouchEvent(event);
|
||||
}catch (IllegalArgumentException e){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
super.computeScroll();
|
||||
if(dragHelper.continueSettling(true)){
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
scrimRenderer.render(canvas, config.getPosition(), scrimPaint);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the panel slide listener that gets called based on slider changes
|
||||
* @param listener callback implementation
|
||||
*/
|
||||
public void setOnPanelSlideListener(OnPanelSlideListener listener){
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the default {@link SlidrInterface} from which to control the panel with after attachment
|
||||
*/
|
||||
public SlidrInterface getDefaultInterface() {
|
||||
return defaultSlidrInterface;
|
||||
}
|
||||
|
||||
|
||||
private final SlidrInterface defaultSlidrInterface = new SlidrInterface() {
|
||||
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
SliderPanel.this.lock();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
SliderPanel.this.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The drag helper callback interface for the Left position
|
||||
*/
|
||||
private final ViewDragHelper.Callback leftCallback = new ViewDragHelper.Callback() {
|
||||
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
boolean edgeCase = !config.isEdgeOnly() || dragHelper.isEdgeTouched(edgePosition, pointerId);
|
||||
return child.getId() == decorView.getId() && edgeCase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionHorizontal(View child, int left, int dx) {
|
||||
return clamp(left, 0, screenWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewHorizontalDragRange(View child) {
|
||||
return screenWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
|
||||
int left = releasedChild.getLeft();
|
||||
int settleLeft = 0;
|
||||
int leftThreshold = (int) (getWidth() * config.getDistanceThreshold());
|
||||
boolean isVerticalSwiping = Math.abs(yvel) > config.getVelocityThreshold();
|
||||
|
||||
if(xvel > 0){
|
||||
|
||||
if(Math.abs(xvel) > config.getVelocityThreshold() && !isVerticalSwiping){
|
||||
settleLeft = screenWidth;
|
||||
}else if(left > leftThreshold){
|
||||
settleLeft = screenWidth;
|
||||
}
|
||||
|
||||
}else if(xvel == 0){
|
||||
if(left > leftThreshold){
|
||||
settleLeft = screenWidth;
|
||||
}
|
||||
}
|
||||
|
||||
dragHelper.settleCapturedViewAt(settleLeft, releasedChild.getTop());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
float percent = 1f - ((float)left / (float) screenWidth);
|
||||
|
||||
if(listener != null) listener.onSlideChange(percent);
|
||||
|
||||
// Update the dimmer alpha
|
||||
applyScrim(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if(listener != null) listener.onStateChanged(state);
|
||||
switch (state){
|
||||
case ViewDragHelper.STATE_IDLE:
|
||||
if(decorView.getLeft() == 0){
|
||||
// State Open
|
||||
if(listener != null) listener.onOpened();
|
||||
}else{
|
||||
// State Closed
|
||||
if(listener != null) listener.onClosed();
|
||||
}
|
||||
break;
|
||||
case ViewDragHelper.STATE_DRAGGING:
|
||||
|
||||
break;
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The drag helper callbacks for dragging the slidr attachment from the right of the screen
|
||||
*/
|
||||
private final ViewDragHelper.Callback rightCallback = new ViewDragHelper.Callback() {
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
boolean edgeCase = !config.isEdgeOnly() || dragHelper.isEdgeTouched(edgePosition, pointerId);
|
||||
return child.getId() == decorView.getId() && edgeCase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionHorizontal(View child, int left, int dx) {
|
||||
return clamp(left, -screenWidth, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewHorizontalDragRange(View child) {
|
||||
return screenWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
|
||||
int left = releasedChild.getLeft();
|
||||
int settleLeft = 0;
|
||||
int leftThreshold = (int) (getWidth() * config.getDistanceThreshold());
|
||||
boolean isVerticalSwiping = Math.abs(yvel) > config.getVelocityThreshold();
|
||||
|
||||
if(xvel < 0){
|
||||
|
||||
if(Math.abs(xvel) > config.getVelocityThreshold() && !isVerticalSwiping){
|
||||
settleLeft = -screenWidth;
|
||||
}else if(left < -leftThreshold){
|
||||
settleLeft = -screenWidth;
|
||||
}
|
||||
|
||||
}else if(xvel == 0){
|
||||
if(left < -leftThreshold){
|
||||
settleLeft = -screenWidth;
|
||||
}
|
||||
}
|
||||
|
||||
dragHelper.settleCapturedViewAt(settleLeft, releasedChild.getTop());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
float percent = 1f - ((float)Math.abs(left) / (float) screenWidth);
|
||||
|
||||
if(listener != null) listener.onSlideChange(percent);
|
||||
|
||||
// Update the dimmer alpha
|
||||
applyScrim(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if(listener != null) listener.onStateChanged(state);
|
||||
switch (state){
|
||||
case ViewDragHelper.STATE_IDLE:
|
||||
if(decorView.getLeft() == 0){
|
||||
// State Open
|
||||
if(listener != null) listener.onOpened();
|
||||
}else{
|
||||
// State Closed
|
||||
if(listener != null) listener.onClosed();
|
||||
}
|
||||
break;
|
||||
case ViewDragHelper.STATE_DRAGGING:
|
||||
|
||||
break;
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The drag helper callbacks for dragging the slidr attachment from the top of the screen
|
||||
*/
|
||||
private final ViewDragHelper.Callback topCallback = new ViewDragHelper.Callback() {
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
return child.getId() == decorView.getId() && (!config.isEdgeOnly() || isEdgeTouched);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||
return clamp(top, 0, screenHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return screenHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
|
||||
int top = releasedChild.getTop();
|
||||
int settleTop = 0;
|
||||
int topThreshold = (int) (getHeight() * config.getDistanceThreshold());
|
||||
boolean isSideSwiping = Math.abs(xvel) > config.getVelocityThreshold();
|
||||
|
||||
if(yvel > 0){
|
||||
if(Math.abs(yvel) > config.getVelocityThreshold() && !isSideSwiping){
|
||||
settleTop = screenHeight;
|
||||
}else if(top > topThreshold){
|
||||
settleTop = screenHeight;
|
||||
}
|
||||
}else if(yvel == 0){
|
||||
if(top > topThreshold){
|
||||
settleTop = screenHeight;
|
||||
}
|
||||
}
|
||||
|
||||
dragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleTop);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
float percent = 1f - ((float)Math.abs(top) / (float) screenHeight);
|
||||
|
||||
if(listener != null) listener.onSlideChange(percent);
|
||||
|
||||
// Update the dimmer alpha
|
||||
applyScrim(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if(listener != null) listener.onStateChanged(state);
|
||||
switch (state){
|
||||
case ViewDragHelper.STATE_IDLE:
|
||||
if(decorView.getTop() == 0){
|
||||
// State Open
|
||||
if(listener != null) listener.onOpened();
|
||||
}else{
|
||||
// State Closed
|
||||
if(listener != null) listener.onClosed();
|
||||
}
|
||||
break;
|
||||
case ViewDragHelper.STATE_DRAGGING:
|
||||
|
||||
break;
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The drag helper callbacks for dragging the slidr attachment from the bottom of hte screen
|
||||
*/
|
||||
private final ViewDragHelper.Callback bottomCallback = new ViewDragHelper.Callback() {
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
return child.getId() == decorView.getId() && (!config.isEdgeOnly() || isEdgeTouched);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||
return clamp(top, -screenHeight, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return screenHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
|
||||
int top = releasedChild.getTop();
|
||||
int settleTop = 0;
|
||||
int topThreshold = (int) (getHeight() * config.getDistanceThreshold());
|
||||
boolean isSideSwiping = Math.abs(xvel) > config.getVelocityThreshold();
|
||||
|
||||
if(yvel < 0){
|
||||
if(Math.abs(yvel) > config.getVelocityThreshold() && !isSideSwiping){
|
||||
settleTop = -screenHeight;
|
||||
}else if(top < -topThreshold){
|
||||
settleTop = -screenHeight;
|
||||
}
|
||||
}else if(yvel == 0){
|
||||
if(top < -topThreshold){
|
||||
settleTop = -screenHeight;
|
||||
}
|
||||
}
|
||||
|
||||
dragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleTop);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
float percent = 1f - ((float)Math.abs(top) / (float) screenHeight);
|
||||
|
||||
if(listener != null) listener.onSlideChange(percent);
|
||||
|
||||
// Update the dimmer alpha
|
||||
applyScrim(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if(listener != null) listener.onStateChanged(state);
|
||||
switch (state){
|
||||
case ViewDragHelper.STATE_IDLE:
|
||||
if(decorView.getTop() == 0){
|
||||
// State Open
|
||||
if(listener != null) listener.onOpened();
|
||||
}else{
|
||||
// State Closed
|
||||
if(listener != null) listener.onClosed();
|
||||
}
|
||||
break;
|
||||
case ViewDragHelper.STATE_DRAGGING:
|
||||
|
||||
break;
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The drag helper callbacks for dragging the slidr attachment in both vertical directions
|
||||
*/
|
||||
private final ViewDragHelper.Callback verticalCallback = new ViewDragHelper.Callback() {
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
return child.getId() == decorView.getId() && (!config.isEdgeOnly() || isEdgeTouched);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||
return clamp(top, -screenHeight, screenHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return screenHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
|
||||
int top = releasedChild.getTop();
|
||||
int settleTop = 0;
|
||||
int topThreshold = (int) (getHeight() * config.getDistanceThreshold());
|
||||
boolean isSideSwiping = Math.abs(xvel) > config.getVelocityThreshold();
|
||||
|
||||
if(yvel > 0){
|
||||
|
||||
// Being slinged down
|
||||
if(Math.abs(yvel) > config.getVelocityThreshold() && !isSideSwiping){
|
||||
settleTop = screenHeight;
|
||||
}else if(top > topThreshold){
|
||||
settleTop = screenHeight;
|
||||
}
|
||||
|
||||
}else if(yvel < 0){
|
||||
// Being slinged up
|
||||
if(Math.abs(yvel) > config.getVelocityThreshold() && !isSideSwiping){
|
||||
settleTop = -screenHeight;
|
||||
}else if(top < -topThreshold){
|
||||
settleTop = -screenHeight;
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
if(top > topThreshold){
|
||||
settleTop = screenHeight;
|
||||
}else if(top < -topThreshold){
|
||||
settleTop = -screenHeight;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleTop);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
float percent = 1f - ((float)Math.abs(top) / (float) screenHeight);
|
||||
|
||||
if(listener != null) listener.onSlideChange(percent);
|
||||
|
||||
// Update the dimmer alpha
|
||||
applyScrim(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if(listener != null) listener.onStateChanged(state);
|
||||
switch (state){
|
||||
case ViewDragHelper.STATE_IDLE:
|
||||
if(decorView.getTop() == 0){
|
||||
// State Open
|
||||
if(listener != null) listener.onOpened();
|
||||
}else{
|
||||
// State Closed
|
||||
if(listener != null) listener.onClosed();
|
||||
}
|
||||
break;
|
||||
case ViewDragHelper.STATE_DRAGGING:
|
||||
|
||||
break;
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The drag helper callbacks for dragging the slidr attachment in both horizontal directions
|
||||
*/
|
||||
private final ViewDragHelper.Callback horizontalCallback = new ViewDragHelper.Callback() {
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
boolean edgeCase = !config.isEdgeOnly() || dragHelper.isEdgeTouched(edgePosition, pointerId);
|
||||
return child.getId() == decorView.getId() && edgeCase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionHorizontal(View child, int left, int dx) {
|
||||
return clamp(left, -screenWidth, screenWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewHorizontalDragRange(View child) {
|
||||
return screenWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
|
||||
int left = releasedChild.getLeft();
|
||||
int settleLeft = 0;
|
||||
int leftThreshold = (int) (getWidth() * config.getDistanceThreshold());
|
||||
boolean isVerticalSwiping = Math.abs(yvel) > config.getVelocityThreshold();
|
||||
|
||||
if(xvel > 0){
|
||||
|
||||
if(Math.abs(xvel) > config.getVelocityThreshold() && !isVerticalSwiping){
|
||||
settleLeft = screenWidth;
|
||||
}else if(left > leftThreshold){
|
||||
settleLeft = screenWidth;
|
||||
}
|
||||
|
||||
}else if(xvel < 0){
|
||||
|
||||
if(Math.abs(xvel) > config.getVelocityThreshold() && !isVerticalSwiping){
|
||||
settleLeft = -screenWidth;
|
||||
}else if(left < -leftThreshold){
|
||||
settleLeft = -screenWidth;
|
||||
}
|
||||
|
||||
}else{
|
||||
if(left > leftThreshold){
|
||||
settleLeft = screenWidth;
|
||||
}else if(left < -leftThreshold){
|
||||
settleLeft = -screenWidth;
|
||||
}
|
||||
}
|
||||
|
||||
dragHelper.settleCapturedViewAt(settleLeft, releasedChild.getTop());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
float percent = 1f - ((float)Math.abs(left) / (float) screenWidth);
|
||||
|
||||
if(listener != null) listener.onSlideChange(percent);
|
||||
|
||||
// Update the dimmer alpha
|
||||
applyScrim(percent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if(listener != null) listener.onStateChanged(state);
|
||||
switch (state){
|
||||
case ViewDragHelper.STATE_IDLE:
|
||||
if(decorView.getLeft() == 0){
|
||||
// State Open
|
||||
if(listener != null) listener.onOpened();
|
||||
}else{
|
||||
// State Closed
|
||||
if(listener != null) listener.onClosed();
|
||||
}
|
||||
break;
|
||||
case ViewDragHelper.STATE_DRAGGING:
|
||||
|
||||
break;
|
||||
case ViewDragHelper.STATE_SETTLING:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private void init(){
|
||||
setWillNotDraw(false);
|
||||
screenWidth = getResources().getDisplayMetrics().widthPixels;
|
||||
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
final float minVel = MIN_FLING_VELOCITY * density;
|
||||
|
||||
ViewDragHelper.Callback callback;
|
||||
switch (config.getPosition()){
|
||||
case LEFT:
|
||||
callback = leftCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_LEFT;
|
||||
break;
|
||||
case RIGHT:
|
||||
callback = rightCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_RIGHT;
|
||||
break;
|
||||
case TOP:
|
||||
callback = topCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_TOP;
|
||||
break;
|
||||
case BOTTOM:
|
||||
callback = bottomCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_BOTTOM;
|
||||
break;
|
||||
case VERTICAL:
|
||||
callback = verticalCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_TOP | ViewDragHelper.EDGE_BOTTOM;
|
||||
break;
|
||||
case HORIZONTAL:
|
||||
callback = horizontalCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_RIGHT;
|
||||
break;
|
||||
default:
|
||||
callback = leftCallback;
|
||||
edgePosition = ViewDragHelper.EDGE_LEFT;
|
||||
}
|
||||
|
||||
dragHelper = ViewDragHelper.create(this, config.getSensitivity(), callback);
|
||||
dragHelper.setMinVelocity(minVel);
|
||||
dragHelper.setEdgeTrackingEnabled(edgePosition);
|
||||
|
||||
ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
|
||||
|
||||
// Setup the dimmer view
|
||||
scrimPaint = new Paint();
|
||||
scrimPaint.setColor(config.getScrimColor());
|
||||
scrimPaint.setAlpha(toAlpha(config.getScrimStartAlpha()));
|
||||
scrimRenderer = new ScrimRenderer(this, decorView);
|
||||
|
||||
/*
|
||||
* This is so we can get the height of the view and
|
||||
* ignore the system navigation that would be included if we
|
||||
* retrieved this value from the DisplayMetrics
|
||||
*/
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
screenHeight = getHeight();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void lock(){
|
||||
dragHelper.abort();
|
||||
isLocked = true;
|
||||
}
|
||||
|
||||
|
||||
private void unlock(){
|
||||
dragHelper.abort();
|
||||
isLocked = false;
|
||||
}
|
||||
|
||||
|
||||
private boolean canDragFromEdge(MotionEvent ev) {
|
||||
float x = ev.getX();
|
||||
float y = ev.getY();
|
||||
|
||||
switch (config.getPosition()) {
|
||||
case LEFT:
|
||||
return x < config.getEdgeSize(getWidth());
|
||||
case RIGHT:
|
||||
return x > getWidth() - config.getEdgeSize(getWidth());
|
||||
case BOTTOM:
|
||||
return y > getHeight() - config.getEdgeSize(getHeight());
|
||||
case TOP:
|
||||
return y < config.getEdgeSize(getHeight());
|
||||
case HORIZONTAL:
|
||||
return x < config.getEdgeSize(getWidth()) || x > getWidth() - config.getEdgeSize(getWidth());
|
||||
case VERTICAL:
|
||||
return y < config.getEdgeSize(getHeight()) || y > getHeight() - config.getEdgeSize(getHeight());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void applyScrim(float percent){
|
||||
float alpha = (percent * (config.getScrimStartAlpha() - config.getScrimEndAlpha())) + config.getScrimEndAlpha();
|
||||
scrimPaint.setAlpha(toAlpha(alpha));
|
||||
invalidate(scrimRenderer.getDirtyRect(config.getPosition()));
|
||||
}
|
||||
|
||||
|
||||
private static int clamp(int value, int min, int max){
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
|
||||
private static int toAlpha(float percentage) {
|
||||
return (int) (percentage * 255);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The panel sliding interface that gets called
|
||||
* whenever the panel is closed or opened
|
||||
*/
|
||||
public interface OnPanelSlideListener{
|
||||
void onStateChanged(int state);
|
||||
void onClosed();
|
||||
void onOpened();
|
||||
void onSlideChange(float percent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="slidable_panel" type="id" />
|
||||
<item name="slidable_content" type="id" />
|
||||
</resources>
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.r0adkll.slidr.widget;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.view.View;
|
||||
|
||||
import com.r0adkll.slidr.model.SlidrPosition;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ScrimRendererTest {
|
||||
|
||||
private static final int LEFT = 10;
|
||||
private static final int HORIZONTAL_LEFT = -10;
|
||||
private static final int RIGHT = 50;
|
||||
private static final int TOP = 20;
|
||||
private static final int VERTICAL_TOP = -20;
|
||||
private static final int BOTTOM = 100;
|
||||
private static final int WIDTH = 720;
|
||||
private static final int HEIGHT = 1280;
|
||||
|
||||
@Mock Paint paint;
|
||||
@Mock Canvas canvas;
|
||||
@Mock View decorView;
|
||||
@Mock View rootView;
|
||||
|
||||
private ScrimRenderer renderer;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(rootView.getMeasuredWidth()).thenReturn(WIDTH);
|
||||
when(rootView.getMeasuredHeight()).thenReturn(HEIGHT);
|
||||
when(decorView.getLeft()).thenReturn(LEFT);
|
||||
when(decorView.getRight()).thenReturn(RIGHT);
|
||||
when(decorView.getTop()).thenReturn(TOP);
|
||||
when(decorView.getBottom()).thenReturn(BOTTOM);
|
||||
renderer = new ScrimRenderer(rootView, decorView);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForLeftPosition() {
|
||||
renderer.render(canvas, SlidrPosition.LEFT, paint);
|
||||
verify(canvas).drawRect(0, 0, LEFT, HEIGHT, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForRightPosition() {
|
||||
renderer.render(canvas, SlidrPosition.RIGHT, paint);
|
||||
verify(canvas).drawRect(RIGHT, 0, WIDTH, HEIGHT, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForTopPosition() {
|
||||
renderer.render(canvas, SlidrPosition.TOP, paint);
|
||||
verify(canvas).drawRect(0, 0, WIDTH, TOP, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForBottomPosition() {
|
||||
renderer.render(canvas, SlidrPosition.BOTTOM, paint);
|
||||
verify(canvas).drawRect(0, BOTTOM, WIDTH, HEIGHT, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForPositiveVerticalPosition() {
|
||||
renderer.render(canvas, SlidrPosition.VERTICAL, paint);
|
||||
verify(canvas).drawRect(0, 0, WIDTH, TOP, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForNegativeVerticalPosition() {
|
||||
when(decorView.getTop()).thenReturn(VERTICAL_TOP);
|
||||
renderer.render(canvas, SlidrPosition.VERTICAL, paint);
|
||||
verify(canvas).drawRect(0, BOTTOM, WIDTH, HEIGHT, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForPositiveHorizontalPosition() {
|
||||
renderer.render(canvas, SlidrPosition.HORIZONTAL, paint);
|
||||
verify(canvas).drawRect(0, 0, LEFT, HEIGHT, paint);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldDrawRectForNegativeHorizontalPosition() {
|
||||
when(decorView.getLeft()).thenReturn(HORIZONTAL_LEFT);
|
||||
renderer.render(canvas, SlidrPosition.HORIZONTAL, paint);
|
||||
verify(canvas).drawRect(RIGHT, 0, WIDTH, HEIGHT, paint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.r0adkll.slidr.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.r0adkll.slidr.model.SlidrConfig;
|
||||
import com.r0adkll.slidr.model.SlidrPosition;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.internal.util.reflection.Whitebox.setInternalState;
|
||||
|
||||
/**
|
||||
* Created by farid on 18/01/16.
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({View.class})
|
||||
public class SliderPanelTest {
|
||||
|
||||
@Mock
|
||||
Context context;
|
||||
|
||||
@Mock
|
||||
MotionEvent motionEvent;
|
||||
|
||||
@Test
|
||||
public void testOnInterceptTouchEvent_whenLocked() throws Exception {
|
||||
//given
|
||||
SliderPanel sliderPanel = new SliderPanel(context);
|
||||
setInternalState(sliderPanel, "isLocked", true);
|
||||
|
||||
//when
|
||||
boolean result = sliderPanel.onInterceptTouchEvent(motionEvent);
|
||||
|
||||
//then
|
||||
assertFalse("Result must be false when isLocked is true", result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOnInterceptTouchEvent_whenNotLoacked_edgeOnly() throws Exception {
|
||||
//given
|
||||
SliderPanel sliderPanel = Mockito.spy(new SliderPanel(context));
|
||||
PowerMockito.when(sliderPanel, "getWidth").thenReturn(10);
|
||||
|
||||
SlidrConfig slidrConfig = Mockito.mock(SlidrConfig.class);
|
||||
when(slidrConfig.isEdgeOnly()).thenReturn(true);
|
||||
when(slidrConfig.getPosition()).thenReturn(SlidrPosition.LEFT);
|
||||
when(slidrConfig.getEdgeSize(Matchers.anyInt())).thenReturn(10.1f);
|
||||
|
||||
setInternalState(sliderPanel, "isLocked", false);
|
||||
setInternalState(sliderPanel, "config", slidrConfig);
|
||||
|
||||
//when
|
||||
boolean result = sliderPanel.onInterceptTouchEvent(motionEvent);
|
||||
|
||||
//then
|
||||
assertFalse("Result must be false", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTouchEvent_whenLocked() throws Exception {
|
||||
//given
|
||||
SliderPanel sliderPanel = Mockito.spy(new SliderPanel(context));
|
||||
setInternalState(sliderPanel, "isLocked", true);
|
||||
|
||||
//when
|
||||
boolean result = sliderPanel.onTouchEvent(motionEvent);
|
||||
|
||||
//then
|
||||
assertFalse("Result must be false when locked", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTouchEvent_whenNotLocked() throws Exception {
|
||||
//given
|
||||
SliderPanel sliderPanel = Mockito.spy(new SliderPanel(context));
|
||||
setInternalState(sliderPanel, "isLocked", false);
|
||||
|
||||
ViewDragHelper viewDragHelper = Mockito.mock(ViewDragHelper.class);
|
||||
setInternalState(sliderPanel, "dragHelper", viewDragHelper);
|
||||
|
||||
//when
|
||||
boolean result = sliderPanel.onTouchEvent(motionEvent);
|
||||
|
||||
//then
|
||||
assertTrue("Result must be true when not locked", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTouchEvent_whenNotLocked_butExceptionInProcessTouchEvent() throws Exception {
|
||||
//given
|
||||
SliderPanel sliderPanel = Mockito.spy(new SliderPanel(context));
|
||||
setInternalState(sliderPanel, "isLocked", false);
|
||||
|
||||
ViewDragHelper viewDragHelper = Mockito.mock(ViewDragHelper.class);
|
||||
PowerMockito.doThrow(new IllegalArgumentException()).when(viewDragHelper).processTouchEvent(motionEvent);
|
||||
setInternalState(sliderPanel, "dragHelper", viewDragHelper);
|
||||
|
||||
//when
|
||||
boolean result = sliderPanel.onTouchEvent(motionEvent);
|
||||
|
||||
//then
|
||||
assertFalse("Result must be false when not locked but exception occured during processTouchEvent()", result);
|
||||
}
|
||||
}
|
||||
+11
-10
@@ -176,12 +176,12 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
|
||||
implementation 'androidx.leanback:leanback:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.leanback:leanback-preference:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.recommendation:recommendation:' + leanbackXLibraryVersion
|
||||
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion // exoplayer 2.12.1 fix
|
||||
implementation 'androidx.media:media:' + mediaXLibraryVersion // exoplayer fix
|
||||
implementation 'androidx.leanback:leanback:' + leanbackXVersion
|
||||
implementation 'androidx.leanback:leanback-preference:' + leanbackXVersion
|
||||
implementation 'androidx.recommendation:recommendation:' + leanbackXVersion
|
||||
implementation 'androidx.annotation:annotation:' + annotationXVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXVersion // exoplayer 2.12.1 fix
|
||||
implementation 'androidx.media:media:' + mediaXVersion // exoplayer fix
|
||||
|
||||
implementation 'com.github.bumptech.glide:glide:' + glideVersion
|
||||
implementation 'com.github.bumptech.glide:annotations:' + glideVersion
|
||||
@@ -210,10 +210,10 @@ dependencies {
|
||||
|
||||
testImplementation 'junit:junit:' + junitVersion
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
androidTestImplementation 'androidx.test.ext:junit:' + testXSupportLibraryVersion
|
||||
androidTestImplementation 'androidx.test.ext:truth:' + testXSupportLibraryVersion
|
||||
androidTestImplementation 'androidx.test:runner:' + testXSupportLibraryVersion
|
||||
androidTestImplementation 'androidx.test:rules:' + testXSupportLibraryVersion
|
||||
androidTestImplementation 'androidx.test.ext:junit:' + testXSupportVersion
|
||||
androidTestImplementation 'androidx.test.ext:truth:' + testXSupportVersion
|
||||
androidTestImplementation 'androidx.test:runner:' + testXSupportVersion
|
||||
androidTestImplementation 'androidx.test:rules:' + testXSupportVersion
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:' + espressoVersion
|
||||
|
||||
implementation project(':fragment-1.1.0')
|
||||
@@ -224,6 +224,7 @@ dependencies {
|
||||
implementation project(':youtubeapi')
|
||||
implementation project(':chatkit')
|
||||
implementation project(':doubletapplayerview')
|
||||
implementation project(':slidableactivity') // https://github.com/r0adkll/Slidr
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:' + rxAndroidVersion
|
||||
implementation 'io.reactivex.rxjava2:rxjava:' + rxJavaVersion
|
||||
|
||||
+44
@@ -1,6 +1,8 @@
|
||||
package com.liskovsoft.smartyoutubetv2.tv.ui.common;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
@@ -16,6 +18,10 @@ import com.liskovsoft.smartyoutubetv2.common.utils.Utils;
|
||||
import com.liskovsoft.smartyoutubetv2.tv.ui.common.keyhandler.DoubleBackManager2;
|
||||
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.PlaybackActivity;
|
||||
import com.liskovsoft.smartyoutubetv2.tv.ui.search.tags.SearchTagsActivity;
|
||||
import com.r0adkll.slidr.Slidr;
|
||||
import com.r0adkll.slidr.model.SlidrConfig;
|
||||
import com.r0adkll.slidr.model.SlidrListener;
|
||||
import com.r0adkll.slidr.model.SlidrPosition;
|
||||
|
||||
/**
|
||||
* This parent class contains common methods that run in every activity such as search.
|
||||
@@ -38,6 +44,44 @@ public abstract class LeanbackActivity extends MotherActivity {
|
||||
new PlayerKeyTranslator(this) :
|
||||
new GlobalKeyTranslator(this);
|
||||
mGlobalKeyTranslator.apply();
|
||||
|
||||
initBackSlide();
|
||||
}
|
||||
|
||||
private void initBackSlide() {
|
||||
if (VERSION.SDK_INT < 21) {
|
||||
return;
|
||||
}
|
||||
|
||||
SlidrConfig config = new SlidrConfig.Builder()
|
||||
.position(SlidrPosition.LEFT) // Swipe from the left
|
||||
.edge(true) // Only trigger from the screen edge
|
||||
.edgeSize(0.18f) // Grab 18% of the screen (good for cars)
|
||||
.scrimColor(Color.BLACK) // Shadow color
|
||||
.scrimStartAlpha(0.8f) // Dim the background screen
|
||||
.scrimEndAlpha(0f) // Background clear when finished
|
||||
.distanceThreshold(0.1f)
|
||||
.listener(new SlidrListener() {
|
||||
@Override
|
||||
public void onSlideStateChanged(int state) {}
|
||||
|
||||
@Override
|
||||
public void onSlideChange(float percent) {}
|
||||
|
||||
@Override
|
||||
public void onSlideOpened() {}
|
||||
|
||||
@Override
|
||||
public boolean onSlideClosed() {
|
||||
// This replaces the default finish() with your back logic
|
||||
onBackPressed();
|
||||
return true; // Tells the library we handled the close
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
// Attach to this activity
|
||||
Slidr.attach(this, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user