├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── appeaser
│ │ └── deckviewlibrary
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ └── res
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── deckview
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── appeaser
│ │ └── deckview
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── appeaser
│ │ └── deckview
│ │ ├── helpers
│ │ ├── DeckChildViewTransform.java
│ │ ├── DeckViewConfig.java
│ │ ├── DeckViewSwipeHelper.java
│ │ └── FakeShadowDrawable.java
│ │ ├── utilities
│ │ ├── DVConstants.java
│ │ ├── DVUtils.java
│ │ ├── DozeTrigger.java
│ │ └── ReferenceCountedTrigger.java
│ │ └── views
│ │ ├── AnimateableDeckChildViewBounds.java
│ │ ├── DeckChildView.java
│ │ ├── DeckChildViewHeader.java
│ │ ├── DeckChildViewThumbnail.java
│ │ ├── DeckView.java
│ │ ├── DeckViewLayoutAlgorithm.java
│ │ ├── DeckViewScroller.java
│ │ ├── DeckViewTouchHandler.java
│ │ ├── FixedSizeImageView.java
│ │ ├── ViewAnimation.java
│ │ └── ViewPool.java
│ └── res
│ ├── drawable-v21
│ ├── deck_child_view_button_bg.xml
│ ├── deck_child_view_dismiss_dark.xml
│ ├── deck_child_view_dismiss_light.xml
│ ├── deck_child_view_header_bg.xml
│ └── deck_child_view_header_bg_color.xml
│ ├── interpolator-v21
│ ├── decelerate_quint.xml
│ ├── fast_out_linear_in.xml
│ ├── fast_out_slow_in.xml
│ └── linear_out_slow_in.xml
│ ├── layout
│ ├── deck_child_view.xml
│ └── deck_child_view_header.xml
│ ├── values-land
│ └── dimens.xml
│ ├── values-sw600dp-land
│ └── dimens.xml
│ ├── values-sw600dp
│ └── dimens.xml
│ ├── values-sw720dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── config.xml
│ ├── dimens.xml
│ └── strings.xml
├── deckviewsample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── appeaser
│ │ └── deckviewsample
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── appeaser
│ │ └── deckviewsample
│ │ ├── Datum.java
│ │ └── DeckViewSampleActivity.java
│ └── res
│ ├── drawable-nodpi
│ └── default_thumbnail.jpg
│ ├── drawable-v21
│ └── box.xml
│ ├── drawable-xxxhdpi
│ └── default_header_icon.png
│ ├── layout
│ └── activity_deck_view_sample.xml
│ ├── menu
│ └── menu_deck_view_sample.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-v21
│ └── styles.xml
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## AndroidStudio
3 | #################
4 |
5 | .gradle
6 | project.properties
7 | .idea
8 | gen
9 | *.class
10 | out
11 | *.iml
12 |
13 | #################
14 | ## Eclipse
15 | #################
16 |
17 | *.pydevproject
18 | .project
19 | .metadata
20 | bin/
21 | tmp/
22 | *.tmp
23 | *.bak
24 | *.swp
25 | *~.nib
26 | local.properties
27 | .classpath
28 | .settings/
29 | .loadpath
30 |
31 | # External tool builders
32 | .externalToolBuilders/
33 |
34 | # Locally stored "Eclipse launch configurations"
35 | *.launch
36 |
37 | # CDT-specific
38 | .cproject
39 |
40 | # PDT-specific
41 | .buildpath
42 |
43 |
44 | #################
45 | ## Visual Studio
46 | #################
47 |
48 | ## Ignore Visual Studio temporary files, build results, and
49 | ## files generated by popular Visual Studio add-ons.
50 |
51 | # User-specific files
52 | *.suo
53 | *.user
54 | *.sln.docstates
55 |
56 | # Build results
57 |
58 | [Dd]ebug/
59 | [Rr]elease/
60 | x64/
61 | build/
62 | [Bb]in/
63 | [Oo]bj/
64 |
65 | # MSTest test Results
66 | [Tt]est[Rr]esult*/
67 | [Bb]uild[Ll]og.*
68 |
69 | *_i.c
70 | *_p.c
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.pch
75 | *.pdb
76 | *.pgc
77 | *.pgd
78 | *.rsp
79 | *.sbr
80 | *.tlb
81 | *.tli
82 | *.tlh
83 | *.tmp
84 | *.tmp_proj
85 | *.log
86 | *.vspscc
87 | *.vssscc
88 | .builds
89 | *.pidb
90 | *.log
91 | *.scc
92 |
93 | # Visual C++ cache files
94 | ipch/
95 | *.aps
96 | *.ncb
97 | *.opensdf
98 | *.sdf
99 | *.cachefile
100 |
101 | # Visual Studio profiler
102 | *.psess
103 | *.vsp
104 | *.vspx
105 |
106 | # Guidance Automation Toolkit
107 | *.gpState
108 |
109 | # ReSharper is a .NET coding add-in
110 | _ReSharper*/
111 | *.[Rr]e[Ss]harper
112 |
113 | # TeamCity is a build add-in
114 | _TeamCity*
115 |
116 | # DotCover is a Code Coverage Tool
117 | *.dotCover
118 |
119 | # NCrunch
120 | *.ncrunch*
121 | .*crunch*.local.xml
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.Publish.xml
141 | *.pubxml
142 |
143 | # NuGet Packages Directory
144 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
145 | #packages/
146 |
147 | # Windows Azure Build Output
148 | csx
149 | *.build.csdef
150 |
151 | # Windows Store app package directory
152 | AppPackages/
153 |
154 | # Others
155 | sql/
156 | *.Cache
157 | ClientBin/
158 | [Ss]tyle[Cc]op.*
159 | ~$*
160 | *~
161 | *.dbmdl
162 | *.[Pp]ublish.xml
163 | *.pfx
164 | *.publishsettings
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file to a newer
170 | # Visual Studio version. Backup files are not needed, because we have git ;-)
171 | _UpgradeReport_Files/
172 | Backup*/
173 | UpgradeLog*.XML
174 | UpgradeLog*.htm
175 |
176 | # SQL Server files
177 | App_Data/*.mdf
178 | App_Data/*.ldf
179 |
180 | #############
181 | ## Windows detritus
182 | #############
183 |
184 | # Windows image file caches
185 | Thumbs.db
186 | ehthumbs.db
187 |
188 | # Folder config file
189 | Desktop.ini
190 |
191 | # Recycle Bin used on file shares
192 | $RECYCLE.BIN/
193 |
194 | # Mac crap
195 | .DS_Store
196 |
197 |
198 | #############
199 | ## Python
200 | #############
201 |
202 | *.py[co]
203 |
204 | # Packages
205 | *.egg
206 | *.egg-info
207 | dist/
208 | build/
209 | eggs/
210 | parts/
211 | var/
212 | sdist/
213 | develop-eggs/
214 | .installed.cfg
215 |
216 | # Installer logs
217 | pip-log.txt
218 |
219 | # Unit test / coverage reports
220 | .coverage
221 | .tox
222 |
223 | #Translations
224 | *.mo
225 |
226 | #Mr Developer
227 | .mr.developer.cfg
228 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DeckView
2 | A ViewGroup that mimics Android (Lollipop) Recent apps screen layout.
3 |
4 | ######Note:
5 | DeckView is **not** a true recycler. It *does* recycle views - but it also updates progress map for *all* of its children on each scroll step. This will result in lags with large datasets.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.appeaser.deckviewlibrary"
9 | minSdkVersion 21
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | }
25 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\Vikram\Documents\Android\adt-bundle-windows-x86-20130219\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/appeaser/deckviewlibrary/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckviewlibrary;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DeckViewLibrary
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/deckview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/deckview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 21
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | }
24 |
--------------------------------------------------------------------------------
/deckview/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:/Users/Vikram/Documents/Android/adt-bundle-windows-x86-20130219/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/deckview/src/androidTest/java/com/appeaser/deckview/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/deckview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/helpers/DeckChildViewTransform.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.helpers;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.graphics.Rect;
5 | import android.view.View;
6 | import android.view.ViewPropertyAnimator;
7 | import android.view.animation.Interpolator;
8 |
9 | /**
10 | * Created by Vikram on 02/04/2015.
11 | */
12 | /* The transform state for a task view */
13 | public class DeckChildViewTransform {
14 | public int startDelay = 0;
15 | public int translationY = 0;
16 | public float translationZ = 0;
17 | public float scale = 1f;
18 | public float alpha = 1f;
19 | public boolean visible = false;
20 | public Rect rect = new Rect();
21 | public float p = 0f;
22 |
23 | public DeckChildViewTransform() {
24 | // Do nothing
25 | }
26 |
27 | public DeckChildViewTransform(DeckChildViewTransform o) {
28 | startDelay = o.startDelay;
29 | translationY = o.translationY;
30 | translationZ = o.translationZ;
31 | scale = o.scale;
32 | alpha = o.alpha;
33 | visible = o.visible;
34 | rect.set(o.rect);
35 | p = o.p;
36 | }
37 |
38 | /**
39 | * Resets the current transform
40 | */
41 | public void reset() {
42 | startDelay = 0;
43 | translationY = 0;
44 | translationZ = 0;
45 | scale = 1f;
46 | alpha = 1f;
47 | visible = false;
48 | rect.setEmpty();
49 | p = 0f;
50 | }
51 |
52 | /**
53 | * Convenience functions to compare against current property values
54 | */
55 | public boolean hasAlphaChangedFrom(float v) {
56 | return (Float.compare(alpha, v) != 0);
57 | }
58 |
59 | public boolean hasScaleChangedFrom(float v) {
60 | return (Float.compare(scale, v) != 0);
61 | }
62 |
63 | public boolean hasTranslationYChangedFrom(float v) {
64 | return (Float.compare(translationY, v) != 0);
65 | }
66 |
67 | public boolean hasTranslationZChangedFrom(float v) {
68 | return (Float.compare(translationZ, v) != 0);
69 | }
70 |
71 | /**
72 | * Applies this transform to a view.
73 | */
74 | public void applyToTaskView(View v, int duration, Interpolator interp, boolean allowLayers,
75 | boolean allowShadows, ValueAnimator.AnimatorUpdateListener updateCallback) {
76 | // Check to see if any properties have changed, and update the task view
77 | if (duration > 0) {
78 | ViewPropertyAnimator anim = v.animate();
79 | boolean requiresLayers = false;
80 |
81 | // Animate to the final state
82 | if (hasTranslationYChangedFrom(v.getTranslationY())) {
83 | anim.translationY(translationY);
84 | }
85 | if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
86 | anim.translationZ(translationZ);
87 | }
88 | if (hasScaleChangedFrom(v.getScaleX())) {
89 | anim.scaleX(scale)
90 | .scaleY(scale);
91 | requiresLayers = true;
92 | }
93 | if (hasAlphaChangedFrom(v.getAlpha())) {
94 | // Use layers if we animate alpha
95 | anim.alpha(alpha);
96 | requiresLayers = true;
97 | }
98 | if (requiresLayers && allowLayers) {
99 | anim.withLayer();
100 | }
101 | if (updateCallback != null) {
102 | anim.setUpdateListener(updateCallback);
103 | } else {
104 | anim.setUpdateListener(null);
105 | }
106 | anim.setStartDelay(startDelay)
107 | .setDuration(duration)
108 | .setInterpolator(interp)
109 | .start();
110 | } else {
111 | // Set the changed properties
112 | if (hasTranslationYChangedFrom(v.getTranslationY())) {
113 | v.setTranslationY(translationY);
114 | }
115 | if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
116 | v.setTranslationZ(translationZ);
117 | }
118 | if (hasScaleChangedFrom(v.getScaleX())) {
119 | v.setScaleX(scale);
120 | v.setScaleY(scale);
121 | }
122 | if (hasAlphaChangedFrom(v.getAlpha())) {
123 | v.setAlpha(alpha);
124 | }
125 | }
126 | }
127 |
128 | /**
129 | * Reset the transform on a view.
130 | */
131 | public static void reset(View v) {
132 | v.setTranslationX(0f);
133 | v.setTranslationY(0f);
134 | v.setTranslationZ(0f);
135 | v.setScaleX(1f);
136 | v.setScaleY(1f);
137 | v.setAlpha(1f);
138 | }
139 |
140 | @Override
141 | public String toString() {
142 | return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +
143 | " scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
144 | " p: " + p;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/helpers/DeckViewConfig.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.helpers;
2 |
3 | /**
4 | * Created by Vikram on 02/04/2015.
5 | */
6 |
7 | import android.content.Context;
8 | import android.content.SharedPreferences;
9 | import android.content.res.Configuration;
10 | import android.content.res.Resources;
11 | import android.graphics.Rect;
12 | import android.util.DisplayMetrics;
13 | import android.util.TypedValue;
14 | import android.view.animation.AnimationUtils;
15 | import android.view.animation.Interpolator;
16 |
17 | import com.appeaser.deckview.R;
18 | import com.appeaser.deckview.utilities.DVConstants;
19 |
20 | /**
21 | * Configuration helper
22 | */
23 | public class DeckViewConfig {
24 | static DeckViewConfig sInstance;
25 | static int sPrevConfigurationHashCode;
26 |
27 | /**
28 | * Levels of svelte in increasing severity/austerity.
29 | */
30 | // No svelting.
31 | public static final int SVELTE_NONE = 0;
32 | // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
33 | // caching thumbnails as you scroll.
34 | public static final int SVELTE_LIMIT_CACHE = 1;
35 | // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
36 | // evict all thumbnails when hidden.
37 | public static final int SVELTE_DISABLE_CACHE = 2;
38 | // Disable all thumbnail loading.
39 | public static final int SVELTE_DISABLE_LOADING = 3;
40 |
41 | /**
42 | * Animations
43 | */
44 | public float animationPxMovementPerSecond;
45 |
46 | /**
47 | * Interpolators
48 | */
49 | public Interpolator fastOutSlowInInterpolator;
50 | public Interpolator fastOutLinearInInterpolator;
51 | public Interpolator linearOutSlowInInterpolator;
52 | public Interpolator quintOutInterpolator;
53 |
54 | /**
55 | * Filtering
56 | */
57 | public int filteringCurrentViewsAnimDuration;
58 | public int filteringNewViewsAnimDuration;
59 |
60 | /**
61 | * Insets
62 | */
63 | public Rect systemInsets = new Rect();
64 | public Rect displayRect = new Rect();
65 |
66 | /**
67 | * Layout
68 | */
69 | boolean isLandscape;
70 |
71 | /**
72 | * Task stack
73 | */
74 | public int taskStackScrollDuration;
75 | public int taskStackMaxDim;
76 | public int taskStackTopPaddingPx;
77 | public float taskStackWidthPaddingPct;
78 | public float taskStackOverscrollPct;
79 |
80 | /**
81 | * Transitions
82 | */
83 | public int transitionEnterFromAppDelay;
84 | public int transitionEnterFromHomeDelay;
85 |
86 | /**
87 | * Task view animation and styles
88 | */
89 | public int taskViewEnterFromAppDuration;
90 | public int taskViewEnterFromHomeDuration;
91 | public int taskViewEnterFromHomeStaggerDelay;
92 | public int taskViewExitToAppDuration;
93 | public int taskViewExitToHomeDuration;
94 | public int taskViewRemoveAnimDuration;
95 | public int taskViewRemoveAnimTranslationXPx;
96 | public int taskViewTranslationZMinPx;
97 | public int taskViewTranslationZMaxPx;
98 | public int taskViewRoundedCornerRadiusPx;
99 | public int taskViewHighlightPx;
100 | public int taskViewAffiliateGroupEnterOffsetPx;
101 | public float taskViewThumbnailAlpha;
102 |
103 | /**
104 | * Task bar colors
105 | */
106 | public int taskBarViewDefaultBackgroundColor;
107 | public int taskBarViewLightTextColor;
108 | public int taskBarViewDarkTextColor;
109 | public int taskBarViewHighlightColor;
110 | public float taskBarViewAffiliationColorMinAlpha;
111 |
112 | /**
113 | * Task bar size & animations
114 | */
115 | public int taskBarHeight;
116 | public int taskBarDismissDozeDelaySeconds;
117 |
118 | /**
119 | * Nav bar scrim
120 | */
121 | public int navBarScrimEnterDuration;
122 |
123 | /**
124 | * Launch states
125 | */
126 | public boolean launchedWithAltTab;
127 | public boolean launchedWithNoRecentTasks;
128 | public boolean launchedFromAppWithThumbnail;
129 | public boolean launchedFromHome;
130 | public boolean launchedFromSearchHome;
131 | public boolean launchedReuseTaskStackViews;
132 | public boolean launchedHasConfigurationChanged;
133 | public int launchedToTaskId;
134 | public int launchedNumVisibleTasks;
135 | public int launchedNumVisibleThumbnails;
136 |
137 | /**
138 | * Misc *
139 | */
140 | public boolean useHardwareLayers;
141 | public int altTabKeyDelay;
142 | public boolean fakeShadows;
143 |
144 | /**
145 | * Dev options and global settings
146 | */
147 | public boolean debugModeEnabled;
148 | public int svelteLevel;
149 |
150 | /**
151 | * Private constructor
152 | */
153 | private DeckViewConfig(Context context) {
154 | // Properties that don't have to be reloaded with each configuration change can be loaded
155 | // here.
156 |
157 | // Interpolators
158 | fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
159 | R.interpolator.fast_out_slow_in);
160 | fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
161 | R.interpolator.fast_out_linear_in);
162 | linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
163 | R.interpolator.linear_out_slow_in);
164 | quintOutInterpolator = AnimationUtils.loadInterpolator(context,
165 | R.interpolator.decelerate_quint);
166 | }
167 |
168 | /**
169 | * Updates the configuration to the current context
170 | */
171 | public static DeckViewConfig reinitialize(Context context) {
172 | if (sInstance == null) {
173 | sInstance = new DeckViewConfig(context);
174 | }
175 | int configHashCode = context.getResources().getConfiguration().hashCode();
176 | if (sPrevConfigurationHashCode != configHashCode) {
177 | sInstance.update(context);
178 | sPrevConfigurationHashCode = configHashCode;
179 | }
180 |
181 | sInstance.updateOnReinitialize(context);
182 | return sInstance;
183 | }
184 |
185 | /**
186 | * Returns the current recents configuration
187 | */
188 | public static DeckViewConfig getInstance() {
189 | return sInstance;
190 | }
191 |
192 | /**
193 | * Updates the state, given the specified context
194 | */
195 | void update(Context context) {
196 | SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
197 | Resources res = context.getResources();
198 | DisplayMetrics dm = res.getDisplayMetrics();
199 |
200 | // Debug mode
201 | debugModeEnabled = settings.getBoolean(DVConstants.Values.App.Key_DebugModeEnabled, false);
202 |
203 | // Layout
204 | isLandscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
205 |
206 | // Insets
207 | displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
208 |
209 | // Animations
210 | animationPxMovementPerSecond =
211 | res.getDimensionPixelSize(R.dimen.animation_movement_in_dps_per_second);
212 |
213 | // Filtering
214 | filteringCurrentViewsAnimDuration =
215 | res.getInteger(R.integer.filter_animate_current_views_duration);
216 | filteringNewViewsAnimDuration =
217 | res.getInteger(R.integer.filter_animate_new_views_duration);
218 |
219 | // Task stack
220 | taskStackScrollDuration =
221 | res.getInteger(R.integer.animate_deck_scroll_duration);
222 | TypedValue widthPaddingPctValue = new TypedValue();
223 | res.getValue(R.dimen.deck_width_padding_percentage, widthPaddingPctValue, true);
224 | taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
225 | TypedValue stackOverscrollPctValue = new TypedValue();
226 | res.getValue(R.dimen.deck_overscroll_percentage, stackOverscrollPctValue, true);
227 | taskStackOverscrollPct = stackOverscrollPctValue.getFloat();
228 | taskStackMaxDim = res.getInteger(R.integer.max_deck_view_dim);
229 | taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.deck_top_padding);
230 |
231 | // Transition
232 | transitionEnterFromAppDelay =
233 | res.getInteger(R.integer.enter_from_app_transition_duration);
234 | transitionEnterFromHomeDelay =
235 | res.getInteger(R.integer.enter_from_home_transition_duration);
236 |
237 | // Task view animation and styles
238 | taskViewEnterFromAppDuration =
239 | res.getInteger(R.integer.task_enter_from_app_duration);
240 | taskViewEnterFromHomeDuration =
241 | res.getInteger(R.integer.task_enter_from_home_duration);
242 | taskViewEnterFromHomeStaggerDelay =
243 | res.getInteger(R.integer.task_enter_from_home_stagger_delay);
244 | taskViewExitToAppDuration =
245 | res.getInteger(R.integer.task_exit_to_app_duration);
246 | taskViewExitToHomeDuration =
247 | res.getInteger(R.integer.task_exit_to_home_duration);
248 | taskViewRemoveAnimDuration =
249 | res.getInteger(R.integer.animate_task_view_remove_duration);
250 | taskViewRemoveAnimTranslationXPx =
251 | res.getDimensionPixelSize(R.dimen.task_view_remove_anim_translation_x);
252 | taskViewRoundedCornerRadiusPx =
253 | res.getDimensionPixelSize(R.dimen.task_view_rounded_corners_radius);
254 | taskViewHighlightPx = res.getDimensionPixelSize(R.dimen.task_view_highlight);
255 | taskViewTranslationZMinPx = res.getDimensionPixelSize(R.dimen.task_view_z_min);
256 | taskViewTranslationZMaxPx = res.getDimensionPixelSize(R.dimen.task_view_z_max);
257 | taskViewAffiliateGroupEnterOffsetPx =
258 | res.getDimensionPixelSize(R.dimen.task_view_affiliate_group_enter_offset);
259 | TypedValue thumbnailAlphaValue = new TypedValue();
260 | res.getValue(R.dimen.task_view_thumbnail_alpha, thumbnailAlphaValue, true);
261 | taskViewThumbnailAlpha = thumbnailAlphaValue.getFloat();
262 |
263 | // Task bar colors
264 | taskBarViewDefaultBackgroundColor =
265 | res.getColor(R.color.task_bar_default_background_color);
266 | taskBarViewLightTextColor =
267 | res.getColor(R.color.task_bar_light_text_color);
268 | taskBarViewDarkTextColor =
269 | res.getColor(R.color.task_bar_dark_text_color);
270 | taskBarViewHighlightColor =
271 | res.getColor(R.color.task_bar_highlight_color);
272 | TypedValue affMinAlphaPctValue = new TypedValue();
273 | res.getValue(R.dimen.task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);
274 | taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();
275 |
276 | // Task bar size & animations
277 | taskBarHeight = res.getDimensionPixelSize(R.dimen.deck_child_header_bar_height);
278 | taskBarDismissDozeDelaySeconds =
279 | res.getInteger(R.integer.task_bar_dismiss_delay_seconds);
280 |
281 | // Nav bar scrim
282 | navBarScrimEnterDuration =
283 | res.getInteger(R.integer.nav_bar_scrim_enter_duration);
284 |
285 | // Misc
286 | useHardwareLayers = res.getBoolean(R.bool.config_use_hardware_layers);
287 | altTabKeyDelay = res.getInteger(R.integer.deck_alt_tab_key_delay);
288 | fakeShadows = res.getBoolean(R.bool.config_fake_shadows);
289 | svelteLevel = res.getInteger(R.integer.deck_svelte_level);
290 | }
291 |
292 | /**
293 | * Updates the system insets
294 | */
295 | public void updateSystemInsets(Rect insets) {
296 | systemInsets.set(insets);
297 | }
298 |
299 | /**
300 | * Updates the search bar app widget
301 | */
302 | public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
303 |
304 | }
305 |
306 | /**
307 | * Updates the states that need to be re-read whenever we re-initialize.
308 | */
309 | void updateOnReinitialize(Context context/*, SystemServicesProxy ssp*/) {
310 |
311 | }
312 |
313 | /**
314 | * Called when the configuration has changed, and we want to reset any configuration specific
315 | * members.
316 | */
317 | public void updateOnConfigurationChange() {
318 | // Reset this flag on configuration change to ensure that we recreate new task views
319 | launchedReuseTaskStackViews = false;
320 | // Set this flag to indicate that the configuration has changed since Recents last launched
321 | launchedHasConfigurationChanged = true;
322 | }
323 |
324 | /**
325 | * Returns the task stack bounds in the current orientation. These bounds do not account for
326 | * the system insets.
327 | */
328 | public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,
329 | Rect taskStackBounds) {
330 | taskStackBounds.set(0, 0, windowWidth, windowHeight);
331 | }
332 | }
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/helpers/DeckViewSwipeHelper.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.helpers;
2 |
3 | /**
4 | * Created by Vikram on 02/04/2015.
5 | */
6 |
7 | import android.animation.Animator;
8 | import android.animation.AnimatorListenerAdapter;
9 | import android.animation.ObjectAnimator;
10 | import android.animation.ValueAnimator;
11 | import android.annotation.TargetApi;
12 | import android.os.Build;
13 | import android.util.DisplayMetrics;
14 | import android.view.MotionEvent;
15 | import android.view.VelocityTracker;
16 | import android.view.View;
17 | import android.view.animation.LinearInterpolator;
18 |
19 | /**
20 | * This class facilitates swipe to dismiss. It defines an interface to be implemented by the
21 | * by the class hosting the views that need to swiped, and, using this interface, handles touch
22 | * events and translates / fades / animates the view as it is dismissed.
23 | */
24 | public class DeckViewSwipeHelper {
25 | static final String TAG = "DeckViewSwipeHelper";
26 | private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
27 | private static final boolean CONSTRAIN_SWIPE = true;
28 | private static final boolean FADE_OUT_DURING_SWIPE = true;
29 | private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
30 |
31 | public static final int X = 0;
32 | public static final int Y = 1;
33 |
34 | private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
35 |
36 | private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
37 | private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
38 | private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
39 | private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
40 |
41 | public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
42 | // where fade starts
43 | static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width
44 | // beyond which alpha->0
45 | private float mMinAlpha = 0f;
46 |
47 | private float mPagingTouchSlop;
48 | Callback mCallback;
49 | private int mSwipeDirection;
50 | private VelocityTracker mVelocityTracker;
51 |
52 | private float mInitialTouchPos;
53 | private boolean mDragging;
54 |
55 | private View mCurrView;
56 | private boolean mCanCurrViewBeDimissed;
57 | private float mDensityScale;
58 |
59 | public boolean mAllowSwipeTowardsStart = true;
60 | public boolean mAllowSwipeTowardsEnd = true;
61 | private boolean mRtl;
62 |
63 | public DeckViewSwipeHelper(int swipeDirection, Callback callback, float densityScale,
64 | float pagingTouchSlop) {
65 | mCallback = callback;
66 | mSwipeDirection = swipeDirection;
67 | mVelocityTracker = VelocityTracker.obtain();
68 | mDensityScale = densityScale;
69 | mPagingTouchSlop = pagingTouchSlop;
70 | }
71 |
72 | public void setDensityScale(float densityScale) {
73 | mDensityScale = densityScale;
74 | }
75 |
76 | public void setPagingTouchSlop(float pagingTouchSlop) {
77 | mPagingTouchSlop = pagingTouchSlop;
78 | }
79 |
80 | public void cancelOngoingDrag() {
81 | if (mDragging) {
82 | if (mCurrView != null) {
83 | mCallback.onDragCancelled(mCurrView);
84 | setTranslation(mCurrView, 0);
85 | mCallback.onSnapBackCompleted(mCurrView);
86 | mCurrView = null;
87 | }
88 | mDragging = false;
89 | }
90 | }
91 |
92 | public void resetTranslation(View v) {
93 | setTranslation(v, 0);
94 | }
95 |
96 | private float getPos(MotionEvent ev) {
97 | return mSwipeDirection == X ? ev.getX() : ev.getY();
98 | }
99 |
100 | private float getTranslation(View v) {
101 | return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
102 | }
103 |
104 | private float getVelocity(VelocityTracker vt) {
105 | return mSwipeDirection == X ? vt.getXVelocity() :
106 | vt.getYVelocity();
107 | }
108 |
109 | private ObjectAnimator createTranslationAnimation(View v, float newPos) {
110 | ObjectAnimator anim = ObjectAnimator.ofFloat(v,
111 | mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
112 | return anim;
113 | }
114 |
115 | private float getPerpendicularVelocity(VelocityTracker vt) {
116 | return mSwipeDirection == X ? vt.getYVelocity() :
117 | vt.getXVelocity();
118 | }
119 |
120 | private void setTranslation(View v, float translate) {
121 | if (mSwipeDirection == X) {
122 | v.setTranslationX(translate);
123 | } else {
124 | v.setTranslationY(translate);
125 | }
126 | }
127 |
128 | private float getSize(View v) {
129 | final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();
130 | return mSwipeDirection == X ? dm.widthPixels : dm.heightPixels;
131 | }
132 |
133 | public void setMinAlpha(float minAlpha) {
134 | mMinAlpha = minAlpha;
135 | }
136 |
137 | float getAlphaForOffset(View view) {
138 | float viewSize = getSize(view);
139 | final float fadeSize = ALPHA_FADE_END * viewSize;
140 | float result = 1.0f;
141 | float pos = getTranslation(view);
142 | if (pos >= viewSize * ALPHA_FADE_START) {
143 | result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
144 | } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
145 | result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
146 | }
147 | result = Math.min(result, 1.0f);
148 | result = Math.max(result, 0f);
149 | return Math.max(mMinAlpha, result);
150 | }
151 |
152 | /**
153 | * Determines whether the given view has RTL layout.
154 | */
155 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
156 | public static boolean isLayoutRtl(View view) {
157 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
158 | return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
159 | } else {
160 | return false;
161 | }
162 | }
163 |
164 | public boolean onInterceptTouchEvent(MotionEvent ev) {
165 | final int action = ev.getAction();
166 |
167 | switch (action) {
168 | case MotionEvent.ACTION_DOWN:
169 | mDragging = false;
170 | mCurrView = mCallback.getChildAtPosition(ev);
171 | mVelocityTracker.clear();
172 | if (mCurrView != null) {
173 | mRtl = isLayoutRtl(mCurrView);
174 | mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
175 | mVelocityTracker.addMovement(ev);
176 | mInitialTouchPos = getPos(ev);
177 | } else {
178 | mCanCurrViewBeDimissed = false;
179 | }
180 | break;
181 | case MotionEvent.ACTION_MOVE:
182 | if (mCurrView != null) {
183 | mVelocityTracker.addMovement(ev);
184 | float pos = getPos(ev);
185 | float delta = pos - mInitialTouchPos;
186 | if (Math.abs(delta) > mPagingTouchSlop) {
187 | mCallback.onBeginDrag(mCurrView);
188 | mDragging = true;
189 | mInitialTouchPos = pos - getTranslation(mCurrView);
190 | }
191 | }
192 | break;
193 | case MotionEvent.ACTION_UP:
194 | case MotionEvent.ACTION_CANCEL:
195 | mDragging = false;
196 | mCurrView = null;
197 | break;
198 | }
199 | return mDragging;
200 | }
201 |
202 | /**
203 | * @param view The view to be dismissed
204 | * @param velocity The desired pixels/second speed at which the view should move
205 | */
206 | private void dismissChild(final View view, float velocity) {
207 | final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
208 | float newPos;
209 | if (velocity < 0
210 | || (velocity == 0 && getTranslation(view) < 0)
211 | // if we use the Menu to dismiss an item in landscape, animate up
212 | || (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {
213 | newPos = -getSize(view);
214 | } else {
215 | newPos = getSize(view);
216 | }
217 | int duration = MAX_ESCAPE_ANIMATION_DURATION;
218 | if (velocity != 0) {
219 | duration = Math.min(duration,
220 | (int) (Math.abs(newPos - getTranslation(view)) *
221 | 1000f / Math.abs(velocity)));
222 | } else {
223 | duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
224 | }
225 |
226 | ValueAnimator anim = createTranslationAnimation(view, newPos);
227 | anim.setInterpolator(sLinearInterpolator);
228 | anim.setDuration(duration);
229 | anim.addListener(new AnimatorListenerAdapter() {
230 | @Override
231 | public void onAnimationEnd(Animator animation) {
232 | mCallback.onChildDismissed(view);
233 | if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
234 | view.setAlpha(1.f);
235 | }
236 | }
237 | });
238 | anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
239 | @Override
240 | public void onAnimationUpdate(ValueAnimator animation) {
241 | if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
242 | view.setAlpha(getAlphaForOffset(view));
243 | }
244 | }
245 | });
246 | anim.start();
247 | }
248 |
249 | private void snapChild(final View view, float velocity) {
250 | final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
251 | ValueAnimator anim = createTranslationAnimation(view, 0);
252 | int duration = SNAP_ANIM_LEN;
253 | anim.setDuration(duration);
254 | anim.setInterpolator(DeckViewConfig.getInstance().linearOutSlowInInterpolator);
255 | anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
256 | @Override
257 | public void onAnimationUpdate(ValueAnimator animation) {
258 | if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
259 | view.setAlpha(getAlphaForOffset(view));
260 | }
261 | mCallback.onSwipeChanged(mCurrView, view.getTranslationX());
262 | }
263 | });
264 | anim.addListener(new AnimatorListenerAdapter() {
265 | @Override
266 | public void onAnimationEnd(Animator animation) {
267 | if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
268 | view.setAlpha(1.0f);
269 | }
270 | mCallback.onSnapBackCompleted(view);
271 | }
272 | });
273 | anim.start();
274 | }
275 |
276 | public boolean onTouchEvent(MotionEvent ev) {
277 | if (!mDragging) {
278 | if (!onInterceptTouchEvent(ev)) {
279 | return mCanCurrViewBeDimissed;
280 | }
281 | }
282 |
283 | mVelocityTracker.addMovement(ev);
284 | final int action = ev.getAction();
285 | switch (action) {
286 | case MotionEvent.ACTION_OUTSIDE:
287 | case MotionEvent.ACTION_MOVE:
288 | if (mCurrView != null) {
289 | float delta = getPos(ev) - mInitialTouchPos;
290 | setSwipeAmount(delta);
291 | mCallback.onSwipeChanged(mCurrView, delta);
292 | }
293 | break;
294 | case MotionEvent.ACTION_UP:
295 | case MotionEvent.ACTION_CANCEL:
296 | if (mCurrView != null) {
297 | endSwipe(mVelocityTracker);
298 | }
299 | break;
300 | }
301 | return true;
302 | }
303 |
304 | private void setSwipeAmount(float amount) {
305 | // don't let items that can't be dismissed be dragged more than
306 | // maxScrollDistance
307 | if (CONSTRAIN_SWIPE
308 | && (!isValidSwipeDirection(amount) || !mCallback.canChildBeDismissed(mCurrView))) {
309 | float size = getSize(mCurrView);
310 | float maxScrollDistance = 0.15f * size;
311 | if (Math.abs(amount) >= size) {
312 | amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;
313 | } else {
314 | amount = maxScrollDistance * (float) Math.sin((amount / size) * (Math.PI / 2));
315 | }
316 | }
317 | setTranslation(mCurrView, amount);
318 | if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
319 | float alpha = getAlphaForOffset(mCurrView);
320 | mCurrView.setAlpha(alpha);
321 | }
322 | }
323 |
324 | private boolean isValidSwipeDirection(float amount) {
325 | if (mSwipeDirection == X) {
326 | if (mRtl) {
327 | return (amount <= 0) ? mAllowSwipeTowardsEnd : mAllowSwipeTowardsStart;
328 | } else {
329 | return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;
330 | }
331 | }
332 |
333 | // Vertical swipes are always valid.
334 | return true;
335 | }
336 |
337 | private void endSwipe(VelocityTracker velocityTracker) {
338 | velocityTracker.computeCurrentVelocity(1000 /* px/sec */);
339 | float velocity = getVelocity(velocityTracker);
340 | float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
341 | float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
342 | float translation = getTranslation(mCurrView);
343 | // Decide whether to dismiss the current view
344 | boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
345 | Math.abs(translation) > 0.6 * getSize(mCurrView);
346 | boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
347 | (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
348 | (velocity > 0) == (translation > 0);
349 |
350 | boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
351 | && isValidSwipeDirection(translation)
352 | && (childSwipedFastEnough || childSwipedFarEnough);
353 |
354 | if (dismissChild) {
355 | // flingadingy
356 | dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
357 | } else {
358 | // snappity
359 | mCallback.onDragCancelled(mCurrView);
360 | snapChild(mCurrView, velocity);
361 | }
362 | }
363 |
364 | public interface Callback {
365 | View getChildAtPosition(MotionEvent ev);
366 |
367 | boolean canChildBeDismissed(View v);
368 |
369 | void onBeginDrag(View v);
370 |
371 | void onSwipeChanged(View v, float delta);
372 |
373 | void onChildDismissed(View v);
374 |
375 | void onSnapBackCompleted(View v);
376 |
377 | void onDragCancelled(View v);
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/helpers/FakeShadowDrawable.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.helpers;
2 |
3 | /**
4 | * Created by Vikram on 01/04/2015.
5 | */
6 |
7 | import android.content.res.Resources;
8 | import android.graphics.Canvas;
9 | import android.graphics.ColorFilter;
10 | import android.graphics.LinearGradient;
11 | import android.graphics.Paint;
12 | import android.graphics.Path;
13 | import android.graphics.PixelFormat;
14 | import android.graphics.RadialGradient;
15 | import android.graphics.Rect;
16 | import android.graphics.RectF;
17 | import android.graphics.Shader;
18 | import android.graphics.drawable.Drawable;
19 | import android.util.Log;
20 |
21 | import com.appeaser.deckview.R;
22 |
23 | /**
24 | * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from
25 | * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/
26 | * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few
27 | * modifications to suit our needs in SystemUI.
28 | */
29 | public class FakeShadowDrawable extends Drawable {
30 | // used to calculate content padding
31 | final static double COS_45 = Math.cos(Math.toRadians(45));
32 |
33 | final static float SHADOW_MULTIPLIER = 1.5f;
34 |
35 | final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
36 |
37 | Paint mCornerShadowPaint;
38 |
39 | Paint mEdgeShadowPaint;
40 |
41 | final RectF mCardBounds;
42 |
43 | float mCornerRadius;
44 |
45 | Path mCornerShadowPath;
46 |
47 | // updated value with inset
48 | float mMaxShadowSize;
49 |
50 | // actual value set by developer
51 | float mRawMaxShadowSize;
52 |
53 | // multiplied value to account for shadow offset
54 | float mShadowSize;
55 |
56 | // actual value set by developer
57 | float mRawShadowSize;
58 |
59 | private boolean mDirty = true;
60 |
61 | private final int mShadowStartColor;
62 |
63 | private final int mShadowEndColor;
64 |
65 | private boolean mAddPaddingForCorners = true;
66 |
67 | /**
68 | * If shadow size is set to a value above max shadow, we print a warning
69 | */
70 | private boolean mPrintedShadowClipWarning = false;
71 |
72 | public FakeShadowDrawable(Resources resources, DeckViewConfig config) {
73 | mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color);
74 | mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color);
75 | mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset);
76 | setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size),
77 | resources.getDimensionPixelSize(R.dimen.fake_shadow_size));
78 | mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
79 | mCornerShadowPaint.setStyle(Paint.Style.FILL);
80 | mCornerShadowPaint.setDither(true);
81 | mCornerRadius = config.taskViewRoundedCornerRadiusPx;
82 | mCardBounds = new RectF();
83 | mEdgeShadowPaint = new Paint(mCornerShadowPaint);
84 | }
85 |
86 | @Override
87 | public void setAlpha(int alpha) {
88 | mCornerShadowPaint.setAlpha(alpha);
89 | mEdgeShadowPaint.setAlpha(alpha);
90 | }
91 |
92 | @Override
93 | protected void onBoundsChange(Rect bounds) {
94 | super.onBoundsChange(bounds);
95 | mDirty = true;
96 | }
97 |
98 | void setShadowSize(float shadowSize, float maxShadowSize) {
99 | if (shadowSize < 0 || maxShadowSize < 0) {
100 | throw new IllegalArgumentException("invalid shadow size");
101 | }
102 | if (shadowSize > maxShadowSize) {
103 | shadowSize = maxShadowSize;
104 | if (!mPrintedShadowClipWarning) {
105 | Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
106 | + "{CardView#setMaxCardElevation}.");
107 | mPrintedShadowClipWarning = true;
108 | }
109 | }
110 | if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
111 | return;
112 | }
113 | mRawShadowSize = shadowSize;
114 | mRawMaxShadowSize = maxShadowSize;
115 | mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
116 | mMaxShadowSize = maxShadowSize + mInsetShadow;
117 | mDirty = true;
118 | invalidateSelf();
119 | }
120 |
121 | @Override
122 | public boolean getPadding(Rect padding) {
123 | int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
124 | mAddPaddingForCorners));
125 | int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
126 | mAddPaddingForCorners));
127 | padding.set(hOffset, vOffset, hOffset, vOffset);
128 | return true;
129 | }
130 |
131 | static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
132 | boolean addPaddingForCorners) {
133 | if (addPaddingForCorners) {
134 | return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
135 | } else {
136 | return maxShadowSize * SHADOW_MULTIPLIER;
137 | }
138 | }
139 |
140 | static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
141 | boolean addPaddingForCorners) {
142 | if (addPaddingForCorners) {
143 | return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
144 | } else {
145 | return maxShadowSize;
146 | }
147 | }
148 |
149 | @Override
150 | public void setColorFilter(ColorFilter cf) {
151 | mCornerShadowPaint.setColorFilter(cf);
152 | mEdgeShadowPaint.setColorFilter(cf);
153 | }
154 |
155 | @Override
156 | public int getOpacity() {
157 | return PixelFormat.OPAQUE;
158 | }
159 |
160 | @Override
161 | public void draw(Canvas canvas) {
162 | if (mDirty) {
163 | buildComponents(getBounds());
164 | mDirty = false;
165 | }
166 | canvas.translate(0, mRawShadowSize / 4);
167 | drawShadow(canvas);
168 | canvas.translate(0, -mRawShadowSize / 4);
169 | }
170 |
171 | private void drawShadow(Canvas canvas) {
172 | final float edgeShadowTop = -mCornerRadius - mShadowSize;
173 | final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
174 | final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
175 | final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
176 | // LT
177 | int saved = canvas.save();
178 | canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
179 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
180 | if (drawHorizontalEdges) {
181 | canvas.drawRect(0, edgeShadowTop,
182 | mCardBounds.width() - 2 * inset, -mCornerRadius,
183 | mEdgeShadowPaint);
184 | }
185 | canvas.restoreToCount(saved);
186 | // RB
187 | saved = canvas.save();
188 | canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
189 | canvas.rotate(180f);
190 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
191 | if (drawHorizontalEdges) {
192 | canvas.drawRect(0, edgeShadowTop,
193 | mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
194 | mEdgeShadowPaint);
195 | }
196 | canvas.restoreToCount(saved);
197 | // LB
198 | saved = canvas.save();
199 | canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
200 | canvas.rotate(270f);
201 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
202 | if (drawVerticalEdges) {
203 | canvas.drawRect(0, edgeShadowTop,
204 | mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
205 | }
206 | canvas.restoreToCount(saved);
207 | // RT
208 | saved = canvas.save();
209 | canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
210 | canvas.rotate(90f);
211 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
212 | if (drawVerticalEdges) {
213 | canvas.drawRect(0, edgeShadowTop,
214 | mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
215 | }
216 | canvas.restoreToCount(saved);
217 | }
218 |
219 | private void buildShadowCorners() {
220 | RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
221 | RectF outerBounds = new RectF(innerBounds);
222 | outerBounds.inset(-mShadowSize, -mShadowSize);
223 |
224 | if (mCornerShadowPath == null) {
225 | mCornerShadowPath = new Path();
226 | } else {
227 | mCornerShadowPath.reset();
228 | }
229 | mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
230 | mCornerShadowPath.moveTo(-mCornerRadius, 0);
231 | mCornerShadowPath.rLineTo(-mShadowSize, 0);
232 | // outer arc
233 | mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
234 | // inner arc
235 | mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
236 | mCornerShadowPath.close();
237 |
238 | float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
239 | mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
240 | new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
241 | new float[]{0f, startRatio, 1f}
242 | , Shader.TileMode.CLAMP));
243 |
244 | // we offset the content shadowSize/2 pixels up to make it more realistic.
245 | // this is why edge shadow shader has some extra space
246 | // When drawing bottom edge shadow, we use that extra space.
247 | mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
248 | -mCornerRadius - mShadowSize,
249 | new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
250 | new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
251 | }
252 |
253 | private void buildComponents(Rect bounds) {
254 | // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
255 | // We could have different top-bottom offsets to avoid extra gap above but in that case
256 | // center aligning Views inside the CardView would be problematic.
257 | final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
258 | mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
259 | bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
260 | buildShadowCorners();
261 | }
262 |
263 | float getMinWidth() {
264 | final float content = 2 *
265 | Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
266 | return content + (mRawMaxShadowSize + mInsetShadow) * 2;
267 | }
268 |
269 | float getMinHeight() {
270 | final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
271 | + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
272 | return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/utilities/DVConstants.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.utilities;
2 |
3 | /**
4 | * Created by Vikram on 02/04/2015.
5 | */
6 | public class DVConstants {
7 | public static class DebugFlags {
8 | // Enable this with any other debug flag to see more info
9 | public static final boolean Verbose = false;
10 |
11 | public static class App {
12 | // Enables debug drawing for the transition thumbnail
13 | public static final boolean EnableTransitionThumbnailDebugMode = false;
14 | // Enables the filtering of tasks according to their grouping
15 | public static final boolean EnableTaskFiltering = false;
16 | // Enables clipping of tasks against each other
17 | public static final boolean EnableTaskStackClipping = true;
18 | // Enables tapping on the TaskBar to launch the task
19 | public static final boolean EnableTaskBarTouchEvents = true;
20 | // Enables app-info pane on long-pressing the icon
21 | public static final boolean EnableDevAppInfoOnLongPress = true;
22 | // Enables debug mode
23 | public static final boolean EnableDebugMode = false;
24 | // Enables the search bar layout
25 | public static final boolean EnableSearchLayout = true;
26 | // Enables the thumbnail alpha on the front-most task
27 | public static final boolean EnableThumbnailAlphaOnFrontmost = false;
28 | // This disables the bitmap and icon caches
29 | public static final boolean DisableBackgroundCache = false;
30 | // Enables the simulated task affiliations
31 | public static final boolean EnableSimulatedTaskGroups = false;
32 | // Defines the number of mock task affiliations per group
33 | public static final int TaskAffiliationsGroupCount = 12;
34 | // Enables us to create mock recents tasks
35 | public static final boolean EnableSystemServicesProxy = false;
36 | // Defines the number of mock recents packages to create
37 | public static final int SystemServicesProxyMockPackageCount = 3;
38 | // Defines the number of mock recents tasks to create
39 | public static final int SystemServicesProxyMockTaskCount = 100;
40 | }
41 | }
42 |
43 | public static class Values {
44 | public static class App {
45 | public static int AppWidgetHostId = 1024;
46 | public static String Key_SearchAppWidgetId = "searchAppWidgetId";
47 | public static String Key_DebugModeEnabled = "debugModeEnabled";
48 | public static String DebugModeVersion = "A";
49 | }
50 |
51 | public static class DView {
52 | public static final int TaskStackMinOverscrollRange = 32;
53 | public static final int TaskStackMaxOverscrollRange = 128;
54 | public static final int FilterStartDelay = 25;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/utilities/DVUtils.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.utilities;
2 |
3 | import android.animation.Animator;
4 | import android.graphics.Color;
5 | import android.graphics.Matrix;
6 | import android.graphics.Rect;
7 | import android.graphics.RectF;
8 | import android.view.View;
9 |
10 | import com.appeaser.deckview.helpers.DeckViewConfig;
11 |
12 | import java.lang.reflect.InvocationTargetException;
13 | import java.lang.reflect.Method;
14 | import java.util.ArrayList;
15 |
16 | /**
17 | * Created by Vikram on 02/04/2015.
18 | */
19 | public class DVUtils {
20 | // Reflection methods for altering shadows
21 | private static Method sPropertyMethod;
22 |
23 | static {
24 | try {
25 | Class> c = Class.forName("android.view.GLES20Canvas");
26 | sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
27 | if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
28 | } catch (ClassNotFoundException e) {
29 | e.printStackTrace();
30 | } catch (NoSuchMethodException e) {
31 | e.printStackTrace();
32 | }
33 | }
34 |
35 | /**
36 | * Calculates a consistent animation duration (ms) for all animations depending on the movement
37 | * of the object being animated.
38 | */
39 | public static int calculateTranslationAnimationDuration(int distancePx) {
40 | return calculateTranslationAnimationDuration(distancePx, 100);
41 | }
42 |
43 | public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {
44 | DeckViewConfig config = DeckViewConfig.getInstance();
45 | return Math.max(minDuration, (int) (1000f /* ms/s */ *
46 | (Math.abs(distancePx) / config.animationPxMovementPerSecond)));
47 | }
48 |
49 | /**
50 | * Scales a rect about its centroid
51 | */
52 | public static void scaleRectAboutCenter(Rect r, float scale) {
53 | if (scale != 1.0f) {
54 | int cx = r.centerX();
55 | int cy = r.centerY();
56 | r.offset(-cx, -cy);
57 | r.left = (int) (r.left * scale + 0.5f);
58 | r.top = (int) (r.top * scale + 0.5f);
59 | r.right = (int) (r.right * scale + 0.5f);
60 | r.bottom = (int) (r.bottom * scale + 0.5f);
61 | r.offset(cx, cy);
62 | }
63 | }
64 |
65 | /**
66 | * Maps a coorindate in a descendant view into the parent.
67 | */
68 | public static float mapCoordInDescendentToSelf(View descendant, View root,
69 | float[] coord, boolean includeRootScroll) {
70 | ArrayList ancestorChain = new ArrayList();
71 |
72 | float[] pt = {coord[0], coord[1]};
73 |
74 | View v = descendant;
75 | while (v != root && v != null) {
76 | ancestorChain.add(v);
77 | v = (View) v.getParent();
78 | }
79 | ancestorChain.add(root);
80 |
81 | float scale = 1.0f;
82 | int count = ancestorChain.size();
83 | for (int i = 0; i < count; i++) {
84 | View v0 = ancestorChain.get(i);
85 | // For TextViews, scroll has a meaning which relates to the text position
86 | // which is very strange... ignore the scroll.
87 | if (v0 != descendant || includeRootScroll) {
88 | pt[0] -= v0.getScrollX();
89 | pt[1] -= v0.getScrollY();
90 | }
91 |
92 | v0.getMatrix().mapPoints(pt);
93 | pt[0] += v0.getLeft();
94 | pt[1] += v0.getTop();
95 | scale *= v0.getScaleX();
96 | }
97 |
98 | coord[0] = pt[0];
99 | coord[1] = pt[1];
100 | return scale;
101 | }
102 |
103 | /**
104 | * Maps a coordinate in the root to a descendent.
105 | */
106 | public static float mapCoordInSelfToDescendent(View descendant, View root,
107 | float[] coord, Matrix tmpInverseMatrix) {
108 | ArrayList ancestorChain = new ArrayList();
109 |
110 | float[] pt = {coord[0], coord[1]};
111 |
112 | View v = descendant;
113 | while (v != root) {
114 | ancestorChain.add(v);
115 | v = (View) v.getParent();
116 | }
117 | ancestorChain.add(root);
118 |
119 | float scale = 1.0f;
120 | int count = ancestorChain.size();
121 | tmpInverseMatrix.set(IDENTITY_MATRIX);
122 | for (int i = count - 1; i >= 0; i--) {
123 | View ancestor = ancestorChain.get(i);
124 | View next = i > 0 ? ancestorChain.get(i - 1) : null;
125 |
126 | pt[0] += ancestor.getScrollX();
127 | pt[1] += ancestor.getScrollY();
128 |
129 | if (next != null) {
130 | pt[0] -= next.getLeft();
131 | pt[1] -= next.getTop();
132 | next.getMatrix().invert(tmpInverseMatrix);
133 | tmpInverseMatrix.mapPoints(pt);
134 | scale *= next.getScaleX();
135 | }
136 | }
137 |
138 | coord[0] = pt[0];
139 | coord[1] = pt[1];
140 | return scale;
141 | }
142 |
143 | /**
144 | * Calculates the constrast between two colors, using the algorithm provided by the WCAG v2.
145 | */
146 | public static float computeContrastBetweenColors(int bg, int fg) {
147 | float bgR = Color.red(bg) / 255f;
148 | float bgG = Color.green(bg) / 255f;
149 | float bgB = Color.blue(bg) / 255f;
150 | bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
151 | bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
152 | bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
153 | float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
154 |
155 | float fgR = Color.red(fg) / 255f;
156 | float fgG = Color.green(fg) / 255f;
157 | float fgB = Color.blue(fg) / 255f;
158 | fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
159 | fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
160 | fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
161 | float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
162 |
163 | return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
164 | }
165 |
166 | /**
167 | * Returns the base color overlaid with another overlay color with a specified alpha.
168 | */
169 | public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
170 | return Color.rgb(
171 | (int) (overlayAlpha * Color.red(baseColor) +
172 | (1f - overlayAlpha) * Color.red(overlayColor)),
173 | (int) (overlayAlpha * Color.green(baseColor) +
174 | (1f - overlayAlpha) * Color.green(overlayColor)),
175 | (int) (overlayAlpha * Color.blue(baseColor) +
176 | (1f - overlayAlpha) * Color.blue(overlayColor)));
177 | }
178 |
179 | /**
180 | * Sets some private shadow properties.
181 | */
182 | public static void setShadowProperty(String property, String value)
183 | throws IllegalAccessException, InvocationTargetException {
184 | sPropertyMethod.invoke(null, property, value);
185 | }
186 |
187 | /**
188 | * Cancels an animation ensuring that if it has listeners, onCancel and onEnd
189 | * are not called.
190 | */
191 | public static void cancelAnimationWithoutCallbacks(Animator animator) {
192 | if (animator != null) {
193 | animator.removeAllListeners();
194 | animator.cancel();
195 | }
196 | }
197 |
198 | public static Matrix IDENTITY_MATRIX = new Matrix() {
199 | void oops() {
200 | throw new IllegalStateException("Matrix can not be modified");
201 | }
202 |
203 | @Override
204 | public void set(Matrix src) {
205 | oops();
206 | }
207 |
208 | @Override
209 | public void reset() {
210 | oops();
211 | }
212 |
213 | @Override
214 | public void setTranslate(float dx, float dy) {
215 | oops();
216 | }
217 |
218 | @Override
219 | public void setScale(float sx, float sy, float px, float py) {
220 | oops();
221 | }
222 |
223 | @Override
224 | public void setScale(float sx, float sy) {
225 | oops();
226 | }
227 |
228 | @Override
229 | public void setRotate(float degrees, float px, float py) {
230 | oops();
231 | }
232 |
233 | @Override
234 | public void setRotate(float degrees) {
235 | oops();
236 | }
237 |
238 | @Override
239 | public void setSinCos(float sinValue, float cosValue, float px, float py) {
240 | oops();
241 | }
242 |
243 | @Override
244 | public void setSinCos(float sinValue, float cosValue) {
245 | oops();
246 | }
247 |
248 | @Override
249 | public void setSkew(float kx, float ky, float px, float py) {
250 | oops();
251 | }
252 |
253 | @Override
254 | public void setSkew(float kx, float ky) {
255 | oops();
256 | }
257 |
258 | @Override
259 | public boolean setConcat(Matrix a, Matrix b) {
260 | oops();
261 | return false;
262 | }
263 |
264 | @Override
265 | public boolean preTranslate(float dx, float dy) {
266 | oops();
267 | return false;
268 | }
269 |
270 | @Override
271 | public boolean preScale(float sx, float sy, float px, float py) {
272 | oops();
273 | return false;
274 | }
275 |
276 | @Override
277 | public boolean preScale(float sx, float sy) {
278 | oops();
279 | return false;
280 | }
281 |
282 | @Override
283 | public boolean preRotate(float degrees, float px, float py) {
284 | oops();
285 | return false;
286 | }
287 |
288 | @Override
289 | public boolean preRotate(float degrees) {
290 | oops();
291 | return false;
292 | }
293 |
294 | @Override
295 | public boolean preSkew(float kx, float ky, float px, float py) {
296 | oops();
297 | return false;
298 | }
299 |
300 | @Override
301 | public boolean preSkew(float kx, float ky) {
302 | oops();
303 | return false;
304 | }
305 |
306 | @Override
307 | public boolean preConcat(Matrix other) {
308 | oops();
309 | return false;
310 | }
311 |
312 | @Override
313 | public boolean postTranslate(float dx, float dy) {
314 | oops();
315 | return false;
316 | }
317 |
318 | @Override
319 | public boolean postScale(float sx, float sy, float px, float py) {
320 | oops();
321 | return false;
322 | }
323 |
324 | @Override
325 | public boolean postScale(float sx, float sy) {
326 | oops();
327 | return false;
328 | }
329 |
330 | @Override
331 | public boolean postRotate(float degrees, float px, float py) {
332 | oops();
333 | return false;
334 | }
335 |
336 | @Override
337 | public boolean postRotate(float degrees) {
338 | oops();
339 | return false;
340 | }
341 |
342 | @Override
343 | public boolean postSkew(float kx, float ky, float px, float py) {
344 | oops();
345 | return false;
346 | }
347 |
348 | @Override
349 | public boolean postSkew(float kx, float ky) {
350 | oops();
351 | return false;
352 | }
353 |
354 | @Override
355 | public boolean postConcat(Matrix other) {
356 | oops();
357 | return false;
358 | }
359 |
360 | @Override
361 | public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
362 | oops();
363 | return false;
364 | }
365 |
366 | @Override
367 | public boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex,
368 | int pointCount) {
369 | oops();
370 | return false;
371 | }
372 |
373 | @Override
374 | public void setValues(float[] values) {
375 | oops();
376 | }
377 | };
378 | }
379 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/utilities/DozeTrigger.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.utilities;
2 |
3 | /**
4 | * Created by Vikram on 01/04/2015.
5 | */
6 |
7 | import android.os.Handler;
8 |
9 | /**
10 | * A dozer is a class that fires a trigger after it falls asleep. You can occasionally poke it to
11 | * wake it up, but it will fall asleep if left untouched.
12 | */
13 | public class DozeTrigger {
14 |
15 | Handler mHandler;
16 |
17 | boolean mIsDozing;
18 | boolean mHasTriggered;
19 | int mDozeDurationSeconds;
20 | Runnable mSleepRunnable;
21 |
22 | // Sleep-runnable
23 | Runnable mDozeRunnable = new Runnable() {
24 | @Override
25 | public void run() {
26 | mSleepRunnable.run();
27 | mIsDozing = false;
28 | mHasTriggered = true;
29 | }
30 | };
31 |
32 | public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
33 | mHandler = new Handler();
34 | mDozeDurationSeconds = dozeDurationSeconds;
35 | mSleepRunnable = sleepRunnable;
36 | }
37 |
38 | /**
39 | * Starts dozing. This also resets the trigger flag.
40 | */
41 | public void startDozing() {
42 | forcePoke();
43 | mHasTriggered = false;
44 | }
45 |
46 | /**
47 | * Stops dozing.
48 | */
49 | public void stopDozing() {
50 | mHandler.removeCallbacks(mDozeRunnable);
51 | mIsDozing = false;
52 | }
53 |
54 | /**
55 | * Poke this dozer to wake it up for a little bit, if it is dozing.
56 | */
57 | public void poke() {
58 | if (mIsDozing) {
59 | forcePoke();
60 | }
61 | }
62 |
63 | /**
64 | * Poke this dozer to wake it up for a little bit.
65 | */
66 | void forcePoke() {
67 | mHandler.removeCallbacks(mDozeRunnable);
68 | mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
69 | mIsDozing = true;
70 | }
71 |
72 | /**
73 | * Returns whether we are dozing or not.
74 | */
75 | public boolean isDozing() {
76 | return mIsDozing;
77 | }
78 |
79 | /**
80 | * Returns whether the trigger has fired at least once.
81 | */
82 | public boolean hasTriggered() {
83 | return mHasTriggered;
84 | }
85 |
86 | /**
87 | * Resets the doze trigger state.
88 | */
89 | public void resetTrigger() {
90 | mHasTriggered = false;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/utilities/ReferenceCountedTrigger.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.utilities;
2 |
3 | /**
4 | * Created by Vikram on 01/04/2015.
5 | */
6 |
7 | import android.animation.Animator;
8 | import android.animation.AnimatorListenerAdapter;
9 | import android.content.Context;
10 |
11 | import java.util.ArrayList;
12 |
13 | /**
14 | * A ref counted trigger that does some logic when the count is first incremented, or last
15 | * decremented. Not thread safe as it's not currently needed.
16 | */
17 | public class ReferenceCountedTrigger {
18 |
19 | Context mContext;
20 | int mCount;
21 | ArrayList mFirstIncRunnables = new ArrayList();
22 | ArrayList mLastDecRunnables = new ArrayList();
23 | Runnable mErrorRunnable;
24 |
25 | // Convenience runnables
26 | Runnable mIncrementRunnable = new Runnable() {
27 | @Override
28 | public void run() {
29 | increment();
30 | }
31 | };
32 | Runnable mDecrementRunnable = new Runnable() {
33 | @Override
34 | public void run() {
35 | decrement();
36 | }
37 | };
38 |
39 | public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable,
40 | Runnable lastDecRunnable, Runnable errorRunanable) {
41 | mContext = context;
42 | if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable);
43 | if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable);
44 | mErrorRunnable = errorRunanable;
45 | }
46 |
47 | /**
48 | * Increments the ref count
49 | */
50 | public void increment() {
51 | if (mCount == 0 && !mFirstIncRunnables.isEmpty()) {
52 | int numRunnables = mFirstIncRunnables.size();
53 | for (int i = 0; i < numRunnables; i++) {
54 | mFirstIncRunnables.get(i).run();
55 | }
56 | }
57 | mCount++;
58 | }
59 |
60 | /**
61 | * Convenience method to increment this trigger as a runnable
62 | */
63 | public Runnable incrementAsRunnable() {
64 | return mIncrementRunnable;
65 | }
66 |
67 | /**
68 | * Adds a runnable to the last-decrement runnables list.
69 | */
70 | public void addLastDecrementRunnable(Runnable r) {
71 | // To ensure that the last decrement always calls, we increment and decrement after setting
72 | // the last decrement runnable
73 | boolean ensureLastDecrement = (mCount == 0);
74 | if (ensureLastDecrement) increment();
75 | mLastDecRunnables.add(r);
76 | if (ensureLastDecrement) decrement();
77 | }
78 |
79 | /**
80 | * Decrements the ref count
81 | */
82 | public void decrement() {
83 | mCount--;
84 | if (mCount == 0 && !mLastDecRunnables.isEmpty()) {
85 | int numRunnables = mLastDecRunnables.size();
86 | for (int i = 0; i < numRunnables; i++) {
87 | mLastDecRunnables.get(i).run();
88 | }
89 | } else if (mCount < 0) {
90 | if (mErrorRunnable != null) {
91 | mErrorRunnable.run();
92 | } else {
93 | new Throwable("Invalid ref count").printStackTrace();
94 | //Console.logError(mContext, "Invalid ref count");
95 | }
96 | }
97 | }
98 |
99 | /**
100 | * Convenience method to decrement this trigger as a runnable.
101 | */
102 | public Runnable decrementAsRunnable() {
103 | return mDecrementRunnable;
104 | }
105 |
106 | /**
107 | * Convenience method to decrement this trigger as a animator listener.
108 | */
109 | public Animator.AnimatorListener decrementOnAnimationEnd() {
110 | return new AnimatorListenerAdapter() {
111 | @Override
112 | public void onAnimationEnd(Animator animation) {
113 | decrement();
114 | }
115 | };
116 | }
117 |
118 | /**
119 | * Returns the current ref count
120 | */
121 | public int getCount() {
122 | return mCount;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/AnimateableDeckChildViewBounds.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.graphics.Outline;
4 | import android.graphics.Rect;
5 | import android.view.View;
6 | import android.view.ViewOutlineProvider;
7 |
8 | import com.appeaser.deckview.helpers.DeckViewConfig;
9 |
10 | /**
11 | * Created by Vikram on 02/04/2015.
12 | */
13 | /* An outline provider that has a clip and outline that can be animated. */
14 | public class AnimateableDeckChildViewBounds extends ViewOutlineProvider {
15 |
16 | DeckViewConfig mConfig;
17 |
18 | DeckChildView mSourceView;
19 | Rect mClipRect = new Rect();
20 | Rect mClipBounds = new Rect();
21 | int mCornerRadius;
22 | float mAlpha = 1f;
23 | final float mMinAlpha = 0.25f;
24 |
25 | public AnimateableDeckChildViewBounds(DeckChildView source, int cornerRadius) {
26 | mConfig = DeckViewConfig.getInstance();
27 | mSourceView = source;
28 | mCornerRadius = cornerRadius;
29 | setClipBottom(getClipBottom());
30 | }
31 |
32 | @Override
33 | public void getOutline(View view, Outline outline) {
34 | outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
35 | outline.setRoundRect(mClipRect.left, mClipRect.top,
36 | mSourceView.getWidth() - mClipRect.right,
37 | mSourceView.getHeight() - mClipRect.bottom,
38 | mCornerRadius);
39 | }
40 |
41 | /**
42 | * Sets the view outline alpha.
43 | */
44 | void setAlpha(float alpha) {
45 | if (Float.compare(alpha, mAlpha) != 0) {
46 | mAlpha = alpha;
47 | mSourceView.invalidateOutline();
48 | }
49 | }
50 |
51 | /**
52 | * Sets the bottom clip.
53 | */
54 | public void setClipBottom(int bottom) {
55 | if (bottom != mClipRect.bottom) {
56 | mClipRect.bottom = bottom;
57 | mSourceView.invalidateOutline();
58 | updateClipBounds();
59 | if (!mConfig.useHardwareLayers) {
60 | mSourceView.mThumbnailView.updateThumbnailVisibility(
61 | bottom - mSourceView.getPaddingBottom());
62 | }
63 | }
64 | }
65 |
66 | /**
67 | * Returns the bottom clip.
68 | */
69 | public int getClipBottom() {
70 | return mClipRect.bottom;
71 | }
72 |
73 | private void updateClipBounds() {
74 | mClipBounds.set(mClipRect.left, mClipRect.top,
75 | mSourceView.getWidth() - mClipRect.right,
76 | mSourceView.getHeight() - mClipRect.bottom);
77 | mSourceView.setClipBounds(mClipBounds);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/DeckChildViewHeader.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ArgbEvaluator;
7 | import android.animation.ObjectAnimator;
8 | import android.animation.ValueAnimator;
9 | import android.content.Context;
10 | import android.content.res.ColorStateList;
11 | import android.content.res.Resources;
12 | import android.graphics.Canvas;
13 | import android.graphics.Color;
14 | import android.graphics.Outline;
15 | import android.graphics.Paint;
16 | import android.graphics.PorterDuff;
17 | import android.graphics.PorterDuffColorFilter;
18 | import android.graphics.PorterDuffXfermode;
19 | import android.graphics.drawable.ColorDrawable;
20 | import android.graphics.drawable.Drawable;
21 | import android.graphics.drawable.GradientDrawable;
22 | import android.graphics.drawable.RippleDrawable;
23 | import android.util.AttributeSet;
24 | import android.view.MotionEvent;
25 | import android.view.View;
26 | import android.view.ViewOutlineProvider;
27 | import android.widget.FrameLayout;
28 | import android.widget.ImageView;
29 | import android.widget.TextView;
30 |
31 | import com.appeaser.deckview.R;
32 | import com.appeaser.deckview.helpers.DeckViewConfig;
33 | import com.appeaser.deckview.utilities.DVConstants;
34 | import com.appeaser.deckview.utilities.DVUtils;
35 |
36 | /**
37 | * Created by Vikram on 02/04/2015.
38 | */
39 | /* The task bar view */
40 | public class DeckChildViewHeader extends FrameLayout {
41 |
42 | DeckViewConfig mConfig;
43 |
44 | // Header views
45 | ImageView mDismissButton;
46 | ImageView mApplicationIcon;
47 | TextView mActivityDescription;
48 |
49 | // Header drawables
50 | boolean mCurrentPrimaryColorIsDark;
51 | int mCurrentPrimaryColor;
52 | int mBackgroundColor;
53 | Drawable mLightDismissDrawable;
54 | Drawable mDarkDismissDrawable;
55 | RippleDrawable mBackground;
56 | GradientDrawable mBackgroundColorDrawable;
57 | AnimatorSet mFocusAnimator;
58 | String mDismissContentDescription;
59 |
60 | // Static highlight that we draw at the top of each view
61 | static Paint sHighlightPaint;
62 |
63 | // Header dim, which is only used when task view hardware layers are not used
64 | Paint mDimLayerPaint = new Paint();
65 | PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
66 |
67 | public DeckChildViewHeader(Context context) {
68 | this(context, null);
69 | }
70 |
71 | public DeckChildViewHeader(Context context, AttributeSet attrs) {
72 | this(context, attrs, 0);
73 | }
74 |
75 | public DeckChildViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
76 | this(context, attrs, defStyleAttr, 0);
77 | }
78 |
79 | public DeckChildViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
80 | super(context, attrs, defStyleAttr, defStyleRes);
81 | mConfig = DeckViewConfig.getInstance();
82 | setWillNotDraw(false);
83 | setClipToOutline(true);
84 | setOutlineProvider(new ViewOutlineProvider() {
85 | @Override
86 | public void getOutline(View view, Outline outline) {
87 | outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
88 | }
89 | });
90 |
91 | // Load the dismiss resources
92 | Resources res = context.getResources();
93 | mLightDismissDrawable = res.getDrawable(R.drawable.deck_child_view_dismiss_light);
94 | mDarkDismissDrawable = res.getDrawable(R.drawable.deck_child_view_dismiss_dark);
95 | mDismissContentDescription =
96 | res.getString(R.string.accessibility_item_will_be_dismissed);
97 |
98 | // Configure the highlight paint
99 | if (sHighlightPaint == null) {
100 | sHighlightPaint = new Paint();
101 | sHighlightPaint.setStyle(Paint.Style.STROKE);
102 | sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
103 | sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
104 | sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
105 | sHighlightPaint.setAntiAlias(true);
106 | }
107 | }
108 |
109 | @Override
110 | public boolean onTouchEvent(MotionEvent event) {
111 | // We ignore taps on the task bar except on the filter and dismiss buttons
112 | if (!DVConstants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
113 |
114 | return super.onTouchEvent(event);
115 | }
116 |
117 | @Override
118 | protected void onFinishInflate() {
119 | // Initialize the icon and description views
120 | mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
121 | mActivityDescription = (TextView) findViewById(R.id.activity_description);
122 | mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
123 |
124 | // Hide the backgrounds if they are ripple drawables
125 | if (!DVConstants.DebugFlags.App.EnableTaskFiltering) {
126 | if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
127 | mApplicationIcon.setBackground(null);
128 | }
129 | }
130 |
131 | mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
132 | .deck_child_view_header_bg_color);
133 | // Copy the ripple drawable since we are going to be manipulating it
134 | mBackground = (RippleDrawable)
135 | getContext().getDrawable(R.drawable.deck_child_view_header_bg);
136 | mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
137 | mBackground.setColor(ColorStateList.valueOf(0));
138 | mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
139 | setBackground(mBackground);
140 | }
141 |
142 | @Override
143 | protected void onDraw(Canvas canvas) {
144 | // Draw the highlight at the top edge (but put the bottom edge just out of view)
145 | float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
146 | float radius = mConfig.taskViewRoundedCornerRadiusPx;
147 | int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
148 | canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
149 | canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
150 | getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
151 | canvas.restoreToCount(count);
152 | }
153 |
154 | @Override
155 | public boolean hasOverlappingRendering() {
156 | return false;
157 | }
158 |
159 | /**
160 | * Sets the dim alpha, only used when we are not using hardware layers.
161 | * (see RecentsConfiguration.useHardwareLayers)
162 | */
163 | void setDimAlpha(int alpha) {
164 | mDimColorFilter = new PorterDuffColorFilter(Color.argb(alpha, 0, 0, 0),
165 | PorterDuff.Mode.SRC_ATOP);
166 | mDimLayerPaint.setColorFilter(mDimColorFilter);
167 | setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
168 | }
169 |
170 | /**
171 | * Returns the secondary color for a primary color.
172 | */
173 | int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
174 | int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
175 | return DVUtils.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
176 | }
177 |
178 | /**
179 | * Binds the bar view to the task
180 | */
181 | //public void rebindToTask(Task t) {
182 | public void rebindToTask(Drawable headerIcon, String headerTitle, int headerBgColor) {
183 | // If an activity icon is defined, then we use that as the primary icon to show in the bar,
184 | // otherwise, we fall back to the application icon
185 | mApplicationIcon.setImageDrawable(headerIcon);
186 | mApplicationIcon.setContentDescription(headerTitle);
187 |
188 | mActivityDescription.setText(headerTitle);
189 |
190 | // Try and apply the system ui tint
191 | int existingBgColor = (getBackground() instanceof ColorDrawable) ?
192 | ((ColorDrawable) getBackground()).getColor() : 0;
193 | if (existingBgColor != headerBgColor) {
194 | mBackgroundColorDrawable.setColor(headerBgColor);
195 | mBackgroundColor = headerBgColor;
196 | }
197 | mCurrentPrimaryColor = headerBgColor;
198 | //mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
199 | mActivityDescription.setTextColor(mConfig.taskBarViewLightTextColor);
200 | mDismissButton.setImageDrawable(mLightDismissDrawable);
201 | mDismissButton.setContentDescription(String.format(mDismissContentDescription,
202 | headerTitle));
203 | }
204 |
205 | /**
206 | * Unbinds the bar view from the task
207 | */
208 | void unbindFromTask() {
209 | mApplicationIcon.setImageDrawable(null);
210 | }
211 |
212 | /**
213 | * Animates this task bar dismiss button when launching a task.
214 | */
215 | void startLaunchTaskDismissAnimation() {
216 | if (mDismissButton.getVisibility() == View.VISIBLE) {
217 | mDismissButton.animate().cancel();
218 | mDismissButton.animate()
219 | .alpha(0f)
220 | .setStartDelay(0)
221 | .setInterpolator(mConfig.fastOutSlowInInterpolator)
222 | .setDuration(mConfig.taskViewExitToAppDuration)
223 | .withLayer()
224 | .start();
225 | }
226 | }
227 |
228 | /**
229 | * Animates this task bar if the user does not interact with the stack after a certain time.
230 | */
231 | void startNoUserInteractionAnimation() {
232 | if (mDismissButton.getVisibility() != View.VISIBLE) {
233 | mDismissButton.setVisibility(View.VISIBLE);
234 | mDismissButton.setAlpha(0f);
235 | mDismissButton.animate()
236 | .alpha(1f)
237 | .setStartDelay(0)
238 | .setInterpolator(mConfig.fastOutLinearInInterpolator)
239 | .setDuration(mConfig.taskViewEnterFromAppDuration)
240 | .withLayer()
241 | .start();
242 | }
243 | }
244 |
245 | /**
246 | * Mark this task view that the user does has not interacted with the stack after a certain time.
247 | */
248 | void setNoUserInteractionState() {
249 | if (mDismissButton.getVisibility() != View.VISIBLE) {
250 | mDismissButton.animate().cancel();
251 | mDismissButton.setVisibility(View.VISIBLE);
252 | mDismissButton.setAlpha(1f);
253 | }
254 | }
255 |
256 | /**
257 | * Resets the state tracking that the user has not interacted with the stack after a certain time.
258 | */
259 | void resetNoUserInteractionState() {
260 | mDismissButton.setVisibility(View.INVISIBLE);
261 | }
262 |
263 | @Override
264 | protected int[] onCreateDrawableState(int extraSpace) {
265 |
266 | // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
267 | // This is to prevent layer trashing when the view is pressed.
268 | return new int[]{};
269 | }
270 |
271 | /**
272 | * Notifies the associated TaskView has been focused.
273 | */
274 | void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
275 | // If we are not animating the visible state, just return
276 | if (!animateFocusedState) return;
277 |
278 | boolean isRunning = false;
279 | if (mFocusAnimator != null) {
280 | isRunning = mFocusAnimator.isRunning();
281 | DVUtils.cancelAnimationWithoutCallbacks(mFocusAnimator);
282 | }
283 |
284 | if (focused) {
285 | int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
286 | int[][] states = new int[][]{
287 | new int[]{android.R.attr.state_enabled},
288 | new int[]{android.R.attr.state_pressed}
289 | };
290 | int[] newStates = new int[]{
291 | android.R.attr.state_enabled,
292 | android.R.attr.state_pressed
293 | };
294 | int[] colors = new int[]{
295 | secondaryColor,
296 | secondaryColor
297 | };
298 | mBackground.setColor(new ColorStateList(states, colors));
299 | mBackground.setState(newStates);
300 | // Pulse the background color
301 | int currentColor = mBackgroundColor;
302 | int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
303 | ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
304 | currentColor, lightPrimaryColor);
305 | backgroundColor.addListener(new AnimatorListenerAdapter() {
306 | @Override
307 | public void onAnimationStart(Animator animation) {
308 | mBackground.setState(new int[]{});
309 | }
310 | });
311 | backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
312 | @Override
313 | public void onAnimationUpdate(ValueAnimator animation) {
314 | int color = (int) animation.getAnimatedValue();
315 | mBackgroundColorDrawable.setColor(color);
316 | mBackgroundColor = color;
317 | }
318 | });
319 | backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
320 | backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
321 | // Pulse the translation
322 | ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
323 | translation.setRepeatCount(ValueAnimator.INFINITE);
324 | translation.setRepeatMode(ValueAnimator.REVERSE);
325 |
326 | mFocusAnimator = new AnimatorSet();
327 | mFocusAnimator.playTogether(backgroundColor, translation);
328 | mFocusAnimator.setStartDelay(750);
329 | mFocusAnimator.setDuration(750);
330 | mFocusAnimator.start();
331 | } else {
332 | if (isRunning) {
333 | // Restore the background color
334 | int currentColor = mBackgroundColor;
335 | ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
336 | currentColor, mCurrentPrimaryColor);
337 | backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
338 | @Override
339 | public void onAnimationUpdate(ValueAnimator animation) {
340 | int color = (int) animation.getAnimatedValue();
341 | mBackgroundColorDrawable.setColor(color);
342 | mBackgroundColor = color;
343 | }
344 | });
345 | // Restore the translation
346 | ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
347 |
348 | mFocusAnimator = new AnimatorSet();
349 | mFocusAnimator.playTogether(backgroundColor, translation);
350 | mFocusAnimator.setDuration(150);
351 | mFocusAnimator.start();
352 | } else {
353 | mBackground.setState(new int[]{});
354 | setTranslationZ(0f);
355 | }
356 | }
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/DeckChildViewThumbnail.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | /**
4 | * Created by Vikram on 02/04/2015.
5 | */
6 |
7 | import android.animation.Animator;
8 | import android.animation.AnimatorListenerAdapter;
9 | import android.animation.ValueAnimator;
10 | import android.content.Context;
11 | import android.graphics.Bitmap;
12 | import android.graphics.BitmapShader;
13 | import android.graphics.Canvas;
14 | import android.graphics.Color;
15 | import android.graphics.LightingColorFilter;
16 | import android.graphics.Matrix;
17 | import android.graphics.Paint;
18 | import android.graphics.Rect;
19 | import android.graphics.RectF;
20 | import android.graphics.Shader;
21 | import android.util.AttributeSet;
22 | import android.view.View;
23 |
24 | import com.appeaser.deckview.helpers.DeckViewConfig;
25 | import com.appeaser.deckview.utilities.DVUtils;
26 |
27 | /**
28 | * The task thumbnail view. It implements an image view that allows for animating the dim and
29 | * alpha of the thumbnail image.
30 | */
31 | public class DeckChildViewThumbnail extends View {
32 |
33 | DeckViewConfig mConfig;
34 |
35 | // Drawing
36 | float mDimAlpha;
37 | Matrix mScaleMatrix = new Matrix();
38 | Paint mDrawPaint = new Paint();
39 | RectF mBitmapRect = new RectF();
40 | RectF mLayoutRect = new RectF();
41 | BitmapShader mBitmapShader;
42 | LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
43 |
44 | // Thumbnail alpha
45 | float mThumbnailAlpha;
46 | ValueAnimator mThumbnailAlphaAnimator;
47 | ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
48 | = new ValueAnimator.AnimatorUpdateListener() {
49 | @Override
50 | public void onAnimationUpdate(ValueAnimator animation) {
51 | mThumbnailAlpha = (float) animation.getAnimatedValue();
52 | updateThumbnailPaintFilter();
53 | }
54 | };
55 |
56 | // Task bar clipping, the top of this thumbnail can be clipped against the opaque header
57 | // bar that overlaps this thumbnail
58 | View mTaskBar;
59 | Rect mClipRect = new Rect();
60 |
61 | // Visibility optimization, if the thumbnail height is less than the height of the header
62 | // bar for the task view, then just mark this thumbnail view as invisible
63 | boolean mInvisible;
64 |
65 | public DeckChildViewThumbnail(Context context) {
66 | this(context, null);
67 | }
68 |
69 | public DeckChildViewThumbnail(Context context, AttributeSet attrs) {
70 | this(context, attrs, 0);
71 | }
72 |
73 | public DeckChildViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
74 | this(context, attrs, defStyleAttr, 0);
75 | }
76 |
77 | public DeckChildViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
78 | super(context, attrs, defStyleAttr, defStyleRes);
79 | mConfig = DeckViewConfig.getInstance();
80 | mDrawPaint.setColorFilter(mLightingColorFilter);
81 | mDrawPaint.setFilterBitmap(true);
82 | mDrawPaint.setAntiAlias(true);
83 | }
84 |
85 | @Override
86 | protected void onFinishInflate() {
87 | mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
88 | updateThumbnailPaintFilter();
89 | }
90 |
91 | @Override
92 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
93 | super.onLayout(changed, left, top, right, bottom);
94 | if (changed) {
95 | mLayoutRect.set(0, 0, getWidth(), getHeight());
96 | updateThumbnailScale();
97 | }
98 | }
99 |
100 | @Override
101 | protected void onDraw(Canvas canvas) {
102 | if (mInvisible) {
103 | return;
104 | }
105 | // Draw the thumbnail with the rounded corners
106 | canvas.drawRoundRect(0, 0, getWidth(), getHeight(),
107 | mConfig.taskViewRoundedCornerRadiusPx,
108 | mConfig.taskViewRoundedCornerRadiusPx, mDrawPaint);
109 | }
110 |
111 | /**
112 | * Sets the thumbnail to a given bitmap.
113 | */
114 | void setThumbnail(Bitmap bm) {
115 | mThumbnail = bm;
116 |
117 | if (bm != null) {
118 | mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
119 | Shader.TileMode.CLAMP);
120 | mDrawPaint.setShader(mBitmapShader);
121 | mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
122 | updateThumbnailScale();
123 | } else {
124 | mBitmapShader = null;
125 | mDrawPaint.setShader(null);
126 | }
127 | updateThumbnailPaintFilter();
128 | }
129 |
130 | /**
131 | * Updates the paint to draw the thumbnail.
132 | */
133 | void updateThumbnailPaintFilter() {
134 | if (mInvisible) {
135 | return;
136 | }
137 | int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
138 | int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
139 | if (mBitmapShader != null) {
140 | mLightingColorFilter =
141 | new LightingColorFilter(Color.argb(255, mul, mul, mul),
142 | Color.argb(0, add, add, add));
143 | mDrawPaint.setColorFilter(mLightingColorFilter);
144 | mDrawPaint.setColor(0xffffffff);
145 | } else {
146 | int grey = mul + add;
147 | mDrawPaint.setColorFilter(null);
148 | mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
149 | }
150 | invalidate();
151 | }
152 |
153 | /**
154 | * Updates the thumbnail shader's scale transform.
155 | */
156 | void updateThumbnailScale() {
157 | if (mBitmapShader != null) {
158 | mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);
159 | mBitmapShader.setLocalMatrix(mScaleMatrix);
160 | }
161 | }
162 |
163 | /**
164 | * Updates the clip rect based on the given task bar.
165 | */
166 | void updateClipToTaskBar(View taskBar) {
167 | mTaskBar = taskBar;
168 | int top = (int) Math.max(0, taskBar.getTranslationY() +
169 | taskBar.getMeasuredHeight() - 1);
170 | mClipRect.set(0, top, getMeasuredWidth(), getMeasuredHeight());
171 | setClipBounds(mClipRect);
172 | }
173 |
174 | /**
175 | * Updates the visibility of the the thumbnail.
176 | */
177 | void updateThumbnailVisibility(int clipBottom) {
178 | boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
179 | if (invisible != mInvisible) {
180 | mInvisible = invisible;
181 | if (!mInvisible) {
182 | updateThumbnailPaintFilter();
183 | }
184 | invalidate();
185 | }
186 | }
187 |
188 | /**
189 | * Sets the dim alpha, only used when we are not using hardware layers.
190 | * (see RecentsConfiguration.useHardwareLayers)
191 | */
192 | public void setDimAlpha(float dimAlpha) {
193 | mDimAlpha = dimAlpha;
194 | updateThumbnailPaintFilter();
195 | }
196 |
197 | /**
198 | * Binds the thumbnail view to the task
199 | */
200 | //void rebindToTask(Task t) {
201 | void rebindToTask(Bitmap thumbnail) {
202 | if (thumbnail != null) {
203 | setThumbnail(thumbnail);
204 | } else {
205 | setThumbnail(null);
206 | }
207 | }
208 |
209 | /**
210 | * Unbinds the thumbnail view from the task
211 | */
212 | void unbindFromTask() {
213 | setThumbnail(null);
214 | }
215 |
216 | Bitmap mThumbnail;
217 |
218 | public Bitmap getThumbnail() {
219 | return mThumbnail;
220 | }
221 |
222 | /**
223 | * Handles focus changes.
224 | */
225 | void onFocusChanged(boolean focused) {
226 | if (focused) {
227 | if (Float.compare(getAlpha(), 1f) != 0) {
228 | startFadeAnimation(1f, 0, 150, null);
229 | }
230 | } else {
231 | if (Float.compare(getAlpha(), mConfig.taskViewThumbnailAlpha) != 0) {
232 | startFadeAnimation(mConfig.taskViewThumbnailAlpha, 0, 150, null);
233 | }
234 | }
235 | }
236 |
237 | /**
238 | * Prepares for the enter recents animation, this gets called before the the view
239 | * is first visible and will be followed by a startEnterRecentsAnimation() call.
240 | */
241 | void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
242 | if (isTaskViewLaunchTargetTask) {
243 | mThumbnailAlpha = 1f;
244 | } else {
245 | mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
246 | }
247 | updateThumbnailPaintFilter();
248 | }
249 |
250 | /**
251 | * Animates this task thumbnail as it enters Recents.
252 | */
253 | void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
254 | startFadeAnimation(mConfig.taskViewThumbnailAlpha, delay,
255 | mConfig.taskViewEnterFromAppDuration, postAnimRunnable);
256 | }
257 |
258 | /**
259 | * Animates this task thumbnail as it exits Recents.
260 | */
261 | void startLaunchTaskAnimation(Runnable postAnimRunnable) {
262 | startFadeAnimation(1f, 0, mConfig.taskViewExitToAppDuration, postAnimRunnable);
263 | }
264 |
265 | /**
266 | * Starts a new thumbnail alpha animation.
267 | */
268 | void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
269 | DVUtils.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
270 | mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
271 | mThumbnailAlphaAnimator.setStartDelay(delay);
272 | mThumbnailAlphaAnimator.setDuration(duration);
273 | mThumbnailAlphaAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
274 | mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
275 | if (postAnimRunnable != null) {
276 | mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
277 | @Override
278 | public void onAnimationEnd(Animator animation) {
279 | postAnimRunnable.run();
280 | }
281 | });
282 | }
283 | mThumbnailAlphaAnimator.start();
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/DeckViewLayoutAlgorithm.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.graphics.Rect;
4 |
5 | import com.appeaser.deckview.helpers.DeckChildViewTransform;
6 | import com.appeaser.deckview.helpers.DeckViewConfig;
7 | import com.appeaser.deckview.utilities.DVUtils;
8 |
9 | import java.util.ArrayList;
10 | import java.util.HashMap;
11 |
12 | /**
13 | * Created by Vikram on 02/04/2015.
14 | */
15 | /* The layout logic for a TaskStackView.
16 | *
17 | * We are using a curve that defines the curve of the tasks as that go back in the recents list.
18 | * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the
19 | * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.
20 | */
21 | public class DeckViewLayoutAlgorithm {
22 |
23 | // These are all going to change
24 | static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
25 |
26 | // A report of the visibility state of the stack
27 | public class VisibilityReport {
28 | public int numVisibleTasks;
29 | public int numVisibleThumbnails;
30 |
31 | /**
32 | * Package level ctor
33 | */
34 | VisibilityReport(int tasks, int thumbnails) {
35 | numVisibleTasks = tasks;
36 | numVisibleThumbnails = thumbnails;
37 | }
38 | }
39 |
40 | DeckViewConfig mConfig;
41 |
42 | // The various rects that define the stack view
43 | public Rect mViewRect = new Rect();
44 | Rect mStackVisibleRect = new Rect();
45 | Rect mStackRect = new Rect();
46 | Rect mTaskRect = new Rect();
47 |
48 | // The min/max scroll progress
49 | float mMinScrollP;
50 | float mMaxScrollP;
51 | float mInitialScrollP;
52 | int mWithinAffiliationOffset;
53 | int mBetweenAffiliationOffset;
54 | HashMap mTaskProgressMap = new HashMap();
55 |
56 | // Log function
57 | static final float XScale = 1.75f; // The large the XScale, the longer the flat area of the curve
58 | static final float LogBase = 3000;
59 | static final int PrecisionSteps = 250;
60 | static float[] xp;
61 | static float[] px;
62 |
63 | public DeckViewLayoutAlgorithm(DeckViewConfig config) {
64 | mConfig = config;
65 |
66 | // Precompute the path
67 | initializeCurve();
68 | }
69 |
70 | /**
71 | * Computes the stack and task rects
72 | */
73 | public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
74 | // Compute the stack rects
75 | mViewRect.set(0, 0, windowWidth, windowHeight);
76 | mStackRect.set(taskStackBounds);
77 | mStackVisibleRect.set(taskStackBounds);
78 | mStackVisibleRect.bottom = mViewRect.bottom;
79 |
80 | int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
81 | int heightPadding = mConfig.taskStackTopPaddingPx;
82 | mStackRect.inset(widthPadding, heightPadding);
83 |
84 | // Compute the task rect
85 | int size = mStackRect.width();
86 | int left = mStackRect.left + (mStackRect.width() - size) / 2;
87 | mTaskRect.set(left, mStackRect.top,
88 | left + size, mStackRect.top + size);
89 |
90 | // Update the affiliation offsets
91 | float visibleTaskPct = 0.5f;
92 | mWithinAffiliationOffset = mConfig.taskBarHeight;
93 | mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());
94 | }
95 |
96 | /**
97 | * Computes the minimum and maximum scroll progress values. This method may be called before
98 | * the RecentsConfiguration is set, so we need to pass in the alt-tab state.
99 | */
100 | void computeMinMaxScroll(ArrayList data, boolean launchedWithAltTab,
101 | boolean launchedFromHome) {
102 | // Clear the progress map
103 | mTaskProgressMap.clear();
104 |
105 | // Return early if we have no tasks
106 | if (data.isEmpty()) {
107 | mMinScrollP = mMaxScrollP = 0;
108 | return;
109 | }
110 |
111 | // Note that we should account for the scale difference of the offsets at the screen bottom
112 | int taskHeight = mTaskRect.height();
113 | float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
114 | float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
115 | mWithinAffiliationOffset);
116 | float scale = curveProgressToScale(pWithinAffiliateTop);
117 | int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
118 | pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
119 | mWithinAffiliationOffset + scaleYOffset);
120 | float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
121 | float pBetweenAffiliateOffset = pAtBottomOfStackRect -
122 | screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
123 | float pTaskHeightOffset = pAtBottomOfStackRect -
124 | screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
125 | float pNavBarOffset = pAtBottomOfStackRect -
126 | screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
127 | mStackRect.bottom));
128 |
129 | // Update the task offsets
130 | float pAtBackMostCardTop = 0.5f;
131 | float pAtFrontMostCardTop = pAtBackMostCardTop;
132 | int taskCount = data.size();
133 | for (int i = 0; i < taskCount; i++) {
134 | //Task task = tasks.get(i);
135 | //mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
136 | mTaskProgressMap.put(data.get(i), pAtFrontMostCardTop);
137 |
138 | if (i < (taskCount - 1)) {
139 | // Increment the peek height
140 | // TODO: Might need adjustments
141 | //float pPeek = task.group.isFrontMostTask(task) ?
142 | //pBetweenAffiliateOffset : pWithinAffiliateOffset;
143 | float pPeek = pBetweenAffiliateOffset;
144 | pAtFrontMostCardTop += pPeek;
145 | }
146 | }
147 |
148 | mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
149 | mMinScrollP = data.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
150 | if (launchedWithAltTab && launchedFromHome) {
151 | // Center the top most task, since that will be focused first
152 | mInitialScrollP = mMaxScrollP;
153 | } else {
154 | mInitialScrollP = pAtFrontMostCardTop - 0.825f;
155 | }
156 | mInitialScrollP = Math.min(mMaxScrollP, Math.max(0, mInitialScrollP));
157 | }
158 |
159 | /**
160 | * Computes the maximum number of visible tasks and thumbnails. Requires that
161 | * computeMinMaxScroll() is called first.
162 | */
163 | public VisibilityReport computeStackVisibilityReport(ArrayList data) {
164 | if (data.size() <= 1) {
165 | return new VisibilityReport(1, 1);
166 | }
167 |
168 | // Walk backwards in the task stack and count the number of tasks and visible thumbnails
169 | int taskHeight = mTaskRect.height();
170 | int numVisibleTasks = 1;
171 | int numVisibleThumbnails = 1;
172 | //float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
173 |
174 | float progress = mTaskProgressMap.get(data.get(data.size() - 1)) - mInitialScrollP;
175 | int prevScreenY = curveProgressToScreenY(progress);
176 | for (int i = data.size() - 2; i >= 0; i--) {
177 | //Task task = tasks.get(i);
178 | //progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
179 | progress = mTaskProgressMap.get(data.get(i)) - mInitialScrollP;
180 | if (progress < 0) {
181 | break;
182 | }
183 |
184 | // TODO: Might need adjustments
185 | //boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
186 | boolean isFrontMostTaskInGroup = true;
187 | if (isFrontMostTaskInGroup) {
188 | float scaleAtP = curveProgressToScale(progress);
189 | int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
190 | int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;
191 | boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight;
192 | if (hasVisibleThumbnail) {
193 | numVisibleThumbnails++;
194 | numVisibleTasks++;
195 | prevScreenY = screenY;
196 | } else {
197 | // Once we hit the next front most task that does not have a visible thumbnail,
198 | // walk through remaining visible set
199 | for (int j = i; j >= 0; j--) {
200 | numVisibleTasks++;
201 | progress = mTaskProgressMap.get(data.get(i)) - mInitialScrollP;
202 | if (progress < 0) {
203 | break;
204 | }
205 | }
206 | break;
207 | }
208 | } else if (!isFrontMostTaskInGroup) {
209 | // Affiliated task, no thumbnail
210 | numVisibleTasks++;
211 | }
212 | }
213 | return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
214 | }
215 |
216 | /**
217 | * Update/get the transform
218 | */
219 | public DeckChildViewTransform getStackTransform(T key, float stackScroll,
220 | DeckChildViewTransform transformOut,
221 | DeckChildViewTransform prevTransform) {
222 | // Return early if we have an invalid index
223 | if (!mTaskProgressMap.containsKey(key)) {
224 | transformOut.reset();
225 | return transformOut;
226 | }
227 | return getStackTransform(mTaskProgressMap.get(key), stackScroll, transformOut,
228 | prevTransform);
229 | }
230 |
231 | /**
232 | * Update/get the transform
233 | */
234 | public DeckChildViewTransform getStackTransform(float taskProgress, float stackScroll,
235 | DeckChildViewTransform transformOut,
236 | DeckChildViewTransform prevTransform) {
237 | float pTaskRelative = taskProgress - stackScroll;
238 | float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
239 | // If the task top is outside of the bounds below the screen, then immediately reset it
240 | if (pTaskRelative > 1f) {
241 | transformOut.reset();
242 | transformOut.rect.set(mTaskRect);
243 | return transformOut;
244 | }
245 | // The check for the top is trickier, since we want to show the next task if it is at all
246 | // visible, even if p < 0.
247 | if (pTaskRelative < 0f) {
248 | if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
249 | transformOut.reset();
250 | transformOut.rect.set(mTaskRect);
251 | return transformOut;
252 | }
253 | }
254 | float scale = curveProgressToScale(pBounded);
255 | int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
256 | int minZ = mConfig.taskViewTranslationZMinPx;
257 | int maxZ = mConfig.taskViewTranslationZMaxPx;
258 | transformOut.scale = scale;
259 | transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -
260 | scaleYOffset;
261 | transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));
262 | transformOut.rect.set(mTaskRect);
263 | transformOut.rect.offset(0, transformOut.translationY);
264 | DVUtils.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
265 | transformOut.visible = true;
266 | transformOut.p = pTaskRelative;
267 | return transformOut;
268 | }
269 |
270 | /**
271 | * Returns the untransformed task view size.
272 | */
273 | public Rect getUntransformedTaskViewSize() {
274 | Rect tvSize = new Rect(mTaskRect);
275 | tvSize.offsetTo(0, 0);
276 | return tvSize;
277 | }
278 |
279 | /**
280 | * Returns the scroll to such task top = 1f;
281 | */
282 | public float getStackScrollForTask(T key) {
283 | if (!mTaskProgressMap.containsKey(key)) return 0f;
284 | return mTaskProgressMap.get(key);
285 | }
286 |
287 | /**
288 | * Initializes the curve.
289 | */
290 | public static void initializeCurve() {
291 | if (xp != null && px != null) return;
292 | xp = new float[PrecisionSteps + 1];
293 | px = new float[PrecisionSteps + 1];
294 |
295 | // Approximate f(x)
296 | float[] fx = new float[PrecisionSteps + 1];
297 | float step = 1f / PrecisionSteps;
298 | float x = 0;
299 | for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
300 | fx[xStep] = logFunc(x);
301 | x += step;
302 | }
303 | // Calculate the arc length for x:1->0
304 | float pLength = 0;
305 | float[] dx = new float[PrecisionSteps + 1];
306 | dx[0] = 0;
307 | for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
308 | dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
309 | pLength += dx[xStep];
310 | }
311 | // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
312 | float p = 0;
313 | px[0] = 0f;
314 | px[PrecisionSteps] = 1f;
315 | for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
316 | p += Math.abs(dx[xStep] / pLength);
317 | px[xStep] = p;
318 | }
319 | // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
320 | // function.
321 | int xStep = 0;
322 | p = 0;
323 | xp[0] = 0f;
324 | xp[PrecisionSteps] = 1f;
325 | for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
326 | // Walk forward in px and find the x where px <= p && p < px+1
327 | while (xStep < PrecisionSteps) {
328 | if (px[xStep] > p) break;
329 | xStep++;
330 | }
331 | // Now, px[xStep-1] <= p < px[xStep]
332 | if (xStep == 0) {
333 | xp[pStep] = 0;
334 | } else {
335 | // Find x such that proportionally, x is correct
336 | float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
337 | x = (xStep - 1 + fraction) * step;
338 | xp[pStep] = x;
339 | }
340 | p += step;
341 | }
342 | }
343 |
344 | /**
345 | * Reverses and scales out x.
346 | */
347 | static float reverse(float x) {
348 | return (-x * XScale) + 1;
349 | }
350 |
351 | /**
352 | * The log function describing the curve.
353 | */
354 | static float logFunc(float x) {
355 | return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
356 | }
357 |
358 | /**
359 | * The inverse of the log function describing the curve.
360 | */
361 | float invLogFunc(float y) {
362 | return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
363 | }
364 |
365 | /**
366 | * Converts from the progress along the curve to a screen coordinate.
367 | */
368 | int curveProgressToScreenY(float p) {
369 | if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
370 | float pIndex = p * PrecisionSteps;
371 | int pFloorIndex = (int) Math.floor(pIndex);
372 | int pCeilIndex = (int) Math.ceil(pIndex);
373 | float xFraction = 0;
374 | if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
375 | float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
376 | xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
377 | }
378 | float x = xp[pFloorIndex] + xFraction;
379 | return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
380 | }
381 |
382 | /**
383 | * Converts from the progress along the curve to a scale.
384 | */
385 | float curveProgressToScale(float p) {
386 | if (p < 0) return StackPeekMinScale;
387 | if (p > 1) return 1f;
388 | float scaleRange = (1f - StackPeekMinScale);
389 | float scale = StackPeekMinScale + (p * scaleRange);
390 | return scale;
391 | }
392 |
393 | /**
394 | * Converts from a screen coordinate to the progress along the curve.
395 | */
396 | float screenYToCurveProgress(int screenY) {
397 | float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
398 | if (x < 0 || x > 1) return x;
399 | float xIndex = x * PrecisionSteps;
400 | int xFloorIndex = (int) Math.floor(xIndex);
401 | int xCeilIndex = (int) Math.ceil(xIndex);
402 | float pFraction = 0;
403 | if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
404 | float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
405 | pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
406 | }
407 | return px[xFloorIndex] + pFraction;
408 | }
409 | }
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/DeckViewScroller.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ObjectAnimator;
6 | import android.animation.ValueAnimator;
7 | import android.content.Context;
8 | import android.widget.OverScroller;
9 |
10 | import com.appeaser.deckview.helpers.DeckViewConfig;
11 | import com.appeaser.deckview.utilities.DVUtils;
12 |
13 | /**
14 | * Created by Vikram on 02/04/2015.
15 | */
16 | /* The scrolling logic for a TaskStackView */
17 | public class DeckViewScroller {
18 | public interface DeckViewScrollerCallbacks {
19 | public void onScrollChanged(float p);
20 | }
21 |
22 | DeckViewConfig mConfig;
23 | DeckViewLayoutAlgorithm mLayoutAlgorithm;
24 | DeckViewScrollerCallbacks mCb;
25 |
26 | float mStackScrollP;
27 |
28 | OverScroller mScroller;
29 | ObjectAnimator mScrollAnimator;
30 | float mFinalAnimatedScroll;
31 |
32 | public DeckViewScroller(Context context, DeckViewConfig config,
33 | DeckViewLayoutAlgorithm layoutAlgorithm) {
34 | mConfig = config;
35 | mScroller = new OverScroller(context);
36 | mLayoutAlgorithm = layoutAlgorithm;
37 | setStackScroll(getStackScroll());
38 | }
39 |
40 | /**
41 | * Resets the task scroller.
42 | */
43 | public void reset() {
44 | mStackScrollP = 0f;
45 | }
46 |
47 | /**
48 | * Sets the callbacks
49 | */
50 | public void setCallbacks(DeckViewScrollerCallbacks cb) {
51 | mCb = cb;
52 | }
53 |
54 | /**
55 | * Gets the current stack scroll
56 | */
57 | public float getStackScroll() {
58 | return mStackScrollP;
59 | }
60 |
61 | /**
62 | * Sets the current stack scroll
63 | */
64 | public void setStackScroll(float s) {
65 | mStackScrollP = s;
66 | if (mCb != null) {
67 | mCb.onScrollChanged(mStackScrollP);
68 | }
69 | }
70 |
71 | /**
72 | * Sets the current stack scroll without calling the callback.
73 | */
74 | void setStackScrollRaw(float s) {
75 | mStackScrollP = s;
76 | }
77 |
78 | /**
79 | * Sets the current stack scroll to the initial state when you first enter recents.
80 | *
81 | * @return whether the stack progress changed.
82 | */
83 | public boolean setStackScrollToInitialState() {
84 | float prevStackScrollP = mStackScrollP;
85 | setStackScroll(getBoundedStackScroll(mLayoutAlgorithm.mInitialScrollP));
86 | return Float.compare(prevStackScrollP, mStackScrollP) != 0;
87 | }
88 |
89 | /**
90 | * Bounds the current scroll if necessary
91 | */
92 | public boolean boundScroll() {
93 | float curScroll = getStackScroll();
94 | float newScroll = getBoundedStackScroll(curScroll);
95 | if (Float.compare(newScroll, curScroll) != 0) {
96 | setStackScroll(newScroll);
97 | return true;
98 | }
99 | return false;
100 | }
101 |
102 | /**
103 | * Bounds the current scroll if necessary, but does not synchronize the stack view with the model.
104 | */
105 | public boolean boundScrollRaw() {
106 | float curScroll = getStackScroll();
107 | float newScroll = getBoundedStackScroll(curScroll);
108 | if (Float.compare(newScroll, curScroll) != 0) {
109 | setStackScrollRaw(newScroll);
110 | return true;
111 | }
112 | return false;
113 | }
114 |
115 | /**
116 | * Returns the bounded stack scroll
117 | */
118 | float getBoundedStackScroll(float scroll) {
119 | return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
120 | }
121 |
122 | /**
123 | * Returns the amount that the aboslute value of how much the scroll is out of bounds.
124 | */
125 | float getScrollAmountOutOfBounds(float scroll) {
126 | if (scroll < mLayoutAlgorithm.mMinScrollP) {
127 | return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
128 | } else if (scroll > mLayoutAlgorithm.mMaxScrollP) {
129 | return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP);
130 | }
131 | return 0f;
132 | }
133 |
134 | /**
135 | * Returns whether the specified scroll is out of bounds
136 | */
137 | boolean isScrollOutOfBounds() {
138 | return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;
139 | }
140 |
141 | /**
142 | * Animates the stack scroll into bounds
143 | */
144 | ObjectAnimator animateBoundScroll() {
145 | float curScroll = getStackScroll();
146 | float newScroll = getBoundedStackScroll(curScroll);
147 | if (Float.compare(newScroll, curScroll) != 0) {
148 | // Start a new scroll animation
149 | animateScroll(curScroll, newScroll, null);
150 | }
151 | return mScrollAnimator;
152 | }
153 |
154 | /**
155 | * Animates the stack scroll
156 | */
157 | void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) {
158 | // Finish any current scrolling animations
159 | if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
160 | setStackScroll(mFinalAnimatedScroll);
161 | mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0);
162 | }
163 | stopScroller();
164 | stopBoundScrollAnimation();
165 |
166 | mFinalAnimatedScroll = newScroll;
167 | mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
168 | mScrollAnimator.setDuration(mConfig.taskStackScrollDuration);
169 | mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator);
170 | mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
171 | @Override
172 | public void onAnimationUpdate(ValueAnimator animation) {
173 | setStackScroll((Float) animation.getAnimatedValue());
174 | }
175 | });
176 | mScrollAnimator.addListener(new AnimatorListenerAdapter() {
177 | @Override
178 | public void onAnimationEnd(Animator animation) {
179 | if (postRunnable != null) {
180 | postRunnable.run();
181 | }
182 | mScrollAnimator.removeAllListeners();
183 | }
184 | });
185 | mScrollAnimator.start();
186 | }
187 |
188 | /**
189 | * Aborts any current stack scrolls
190 | */
191 | void stopBoundScrollAnimation() {
192 | DVUtils.cancelAnimationWithoutCallbacks(mScrollAnimator);
193 | }
194 |
195 | /**
196 | * * OverScroller ***
197 | */
198 |
199 | int progressToScrollRange(float p) {
200 | return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height());
201 | }
202 |
203 | float scrollRangeToProgress(int s) {
204 | return (float) s / mLayoutAlgorithm.mStackVisibleRect.height();
205 | }
206 |
207 | /**
208 | * Called from the view draw, computes the next scroll.
209 | */
210 | boolean computeScroll() {
211 | if (mScroller.computeScrollOffset()) {
212 | float scroll = scrollRangeToProgress(mScroller.getCurrY());
213 | setStackScrollRaw(scroll);
214 | if (mCb != null) {
215 | mCb.onScrollChanged(scroll);
216 | }
217 | return true;
218 | }
219 | return false;
220 | }
221 |
222 | /**
223 | * Returns whether the overscroller is scrolling.
224 | */
225 | boolean isScrolling() {
226 | return !mScroller.isFinished();
227 | }
228 |
229 | /**
230 | * Stops the scroller and any current fling.
231 | */
232 | void stopScroller() {
233 | if (!mScroller.isFinished()) {
234 | mScroller.abortAnimation();
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/DeckViewTouchHandler.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.content.Context;
4 | import android.view.InputDevice;
5 | import android.view.MotionEvent;
6 | import android.view.VelocityTracker;
7 | import android.view.View;
8 | import android.view.ViewConfiguration;
9 | import android.view.ViewParent;
10 |
11 | import com.appeaser.deckview.helpers.DeckViewConfig;
12 | import com.appeaser.deckview.helpers.DeckViewSwipeHelper;
13 | import com.appeaser.deckview.utilities.DVConstants;
14 |
15 | /**
16 | * Created by Vikram on 02/04/2015.
17 | */
18 | /* Handles touch events for a TaskStackView. */
19 | public class DeckViewTouchHandler implements DeckViewSwipeHelper.Callback {
20 | static int INACTIVE_POINTER_ID = -1;
21 |
22 | DeckViewConfig mConfig;
23 | DeckView mDeckView;
24 | DeckViewScroller mScroller;
25 | VelocityTracker mVelocityTracker;
26 |
27 | boolean mIsScrolling;
28 |
29 | float mInitialP;
30 | float mLastP;
31 | float mTotalPMotion;
32 | int mInitialMotionX, mInitialMotionY;
33 | int mLastMotionX, mLastMotionY;
34 | int mActivePointerId = INACTIVE_POINTER_ID;
35 | DeckChildView mActiveDeckChildView = null;
36 |
37 | int mMinimumVelocity;
38 | int mMaximumVelocity;
39 | // The scroll touch slop is used to calculate when we start scrolling
40 | int mScrollTouchSlop;
41 | // The page touch slop is used to calculate when we start swiping
42 | float mPagingTouchSlop;
43 |
44 | DeckViewSwipeHelper mSwipeHelper;
45 | boolean mInterceptedBySwipeHelper;
46 |
47 | public DeckViewTouchHandler(Context context, DeckView dv,
48 | DeckViewConfig config, DeckViewScroller scroller) {
49 | ViewConfiguration configuration = ViewConfiguration.get(context);
50 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
51 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
52 | mScrollTouchSlop = configuration.getScaledTouchSlop();
53 | mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
54 | mDeckView = dv;
55 | mScroller = scroller;
56 | mConfig = config;
57 |
58 | float densityScale = context.getResources().getDisplayMetrics().density;
59 | mSwipeHelper = new DeckViewSwipeHelper(DeckViewSwipeHelper.X, this,
60 | densityScale, mPagingTouchSlop);
61 | mSwipeHelper.setMinAlpha(1f);
62 | }
63 |
64 | /**
65 | * Velocity tracker helpers
66 | */
67 | void initOrResetVelocityTracker() {
68 | if (mVelocityTracker == null) {
69 | mVelocityTracker = VelocityTracker.obtain();
70 | } else {
71 | mVelocityTracker.clear();
72 | }
73 | }
74 |
75 | void initVelocityTrackerIfNotExists() {
76 | if (mVelocityTracker == null) {
77 | mVelocityTracker = VelocityTracker.obtain();
78 | }
79 | }
80 |
81 | void recycleVelocityTracker() {
82 | if (mVelocityTracker != null) {
83 | mVelocityTracker.recycle();
84 | mVelocityTracker = null;
85 | }
86 | }
87 |
88 | /**
89 | * Returns the view at the specified coordinates
90 | */
91 | DeckChildView findViewAtPoint(int x, int y) {
92 | int childCount = mDeckView.getChildCount();
93 | for (int i = childCount - 1; i >= 0; i--) {
94 | DeckChildView tv = (DeckChildView) mDeckView.getChildAt(i);
95 | if (tv.getVisibility() == View.VISIBLE) {
96 | if (mDeckView.isTransformedTouchPointInView(x, y, tv)) {
97 | return tv;
98 | }
99 | }
100 | }
101 | return null;
102 | }
103 |
104 | /**
105 | * Constructs a simulated motion event for the current stack scroll.
106 | */
107 | MotionEvent createMotionEventForStackScroll(MotionEvent ev) {
108 | MotionEvent pev = MotionEvent.obtainNoHistory(ev);
109 | pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
110 | return pev;
111 | }
112 |
113 | /**
114 | * Touch preprocessing for handling below
115 | */
116 | public boolean onInterceptTouchEvent(MotionEvent ev) {
117 | // Return early if we have no children
118 | boolean hasChildren = (mDeckView.getChildCount() > 0);
119 | if (!hasChildren) {
120 | return false;
121 | }
122 |
123 | // Pass through to swipe helper if we are swiping
124 | mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
125 | if (mInterceptedBySwipeHelper) {
126 | return true;
127 | }
128 |
129 | boolean wasScrolling = mScroller.isScrolling() ||
130 | (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
131 | int action = ev.getAction();
132 | switch (action & MotionEvent.ACTION_MASK) {
133 | case MotionEvent.ACTION_DOWN: {
134 | // Save the touch down info
135 | mInitialMotionX = mLastMotionX = (int) ev.getX();
136 | mInitialMotionY = mLastMotionY = (int) ev.getY();
137 | mInitialP = mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
138 | mActivePointerId = ev.getPointerId(0);
139 | mActiveDeckChildView = findViewAtPoint(mLastMotionX, mLastMotionY);
140 | // Stop the current scroll if it is still flinging
141 | mScroller.stopScroller();
142 | mScroller.stopBoundScrollAnimation();
143 | // Initialize the velocity tracker
144 | initOrResetVelocityTracker();
145 | mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
146 | break;
147 | }
148 | case MotionEvent.ACTION_MOVE: {
149 | if (mActivePointerId == INACTIVE_POINTER_ID) break;
150 |
151 | // Initialize the velocity tracker if necessary
152 | initVelocityTrackerIfNotExists();
153 | mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
154 |
155 | int activePointerIndex = ev.findPointerIndex(mActivePointerId);
156 | int y = (int) ev.getY(activePointerIndex);
157 | int x = (int) ev.getX(activePointerIndex);
158 | if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
159 | // Save the touch move info
160 | mIsScrolling = true;
161 | // Disallow parents from intercepting touch events
162 | final ViewParent parent = mDeckView.getParent();
163 | if (parent != null) {
164 | parent.requestDisallowInterceptTouchEvent(true);
165 | }
166 | }
167 |
168 | mLastMotionX = x;
169 | mLastMotionY = y;
170 | mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
171 | break;
172 | }
173 | case MotionEvent.ACTION_CANCEL:
174 | case MotionEvent.ACTION_UP: {
175 | // Animate the scroll back if we've cancelled
176 | mScroller.animateBoundScroll();
177 | // Reset the drag state and the velocity tracker
178 | mIsScrolling = false;
179 | mActivePointerId = INACTIVE_POINTER_ID;
180 | mActiveDeckChildView = null;
181 | mTotalPMotion = 0;
182 | recycleVelocityTracker();
183 | break;
184 | }
185 | }
186 |
187 | return wasScrolling || mIsScrolling;
188 | }
189 |
190 | /**
191 | * Handles touch events once we have intercepted them
192 | */
193 | public boolean onTouchEvent(MotionEvent ev) {
194 | // Short circuit if we have no children
195 | boolean hasChildren = (mDeckView.getChildCount() > 0);
196 | if (!hasChildren) {
197 | return false;
198 | }
199 |
200 | // Pass through to swipe helper if we are swiping
201 | if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
202 | return true;
203 | }
204 |
205 | // Update the velocity tracker
206 | initVelocityTrackerIfNotExists();
207 |
208 | int action = ev.getAction();
209 | switch (action & MotionEvent.ACTION_MASK) {
210 | case MotionEvent.ACTION_DOWN: {
211 | // Save the touch down info
212 | mInitialMotionX = mLastMotionX = (int) ev.getX();
213 | mInitialMotionY = mLastMotionY = (int) ev.getY();
214 | mInitialP = mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
215 | mActivePointerId = ev.getPointerId(0);
216 | mActiveDeckChildView = findViewAtPoint(mLastMotionX, mLastMotionY);
217 | // Stop the current scroll if it is still flinging
218 | mScroller.stopScroller();
219 | mScroller.stopBoundScrollAnimation();
220 | // Initialize the velocity tracker
221 | initOrResetVelocityTracker();
222 | mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
223 | // Disallow parents from intercepting touch events
224 | final ViewParent parent = mDeckView.getParent();
225 | if (parent != null) {
226 | parent.requestDisallowInterceptTouchEvent(true);
227 | }
228 | break;
229 | }
230 | case MotionEvent.ACTION_POINTER_DOWN: {
231 | final int index = ev.getActionIndex();
232 | mActivePointerId = ev.getPointerId(index);
233 | mLastMotionX = (int) ev.getX(index);
234 | mLastMotionY = (int) ev.getY(index);
235 | mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
236 | break;
237 | }
238 | case MotionEvent.ACTION_MOVE: {
239 | if (mActivePointerId == INACTIVE_POINTER_ID) break;
240 |
241 | mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
242 |
243 | int activePointerIndex = ev.findPointerIndex(mActivePointerId);
244 | int x = (int) ev.getX(activePointerIndex);
245 | int y = (int) ev.getY(activePointerIndex);
246 | int yTotal = Math.abs(y - mInitialMotionY);
247 | float curP = mDeckView.getStackAlgorithm().screenYToCurveProgress(y);
248 | float deltaP = mLastP - curP;
249 | if (!mIsScrolling) {
250 | if (yTotal > mScrollTouchSlop) {
251 | mIsScrolling = true;
252 | // Disallow parents from intercepting touch events
253 | final ViewParent parent = mDeckView.getParent();
254 | if (parent != null) {
255 | parent.requestDisallowInterceptTouchEvent(true);
256 | }
257 | }
258 | }
259 | if (mIsScrolling) {
260 | float curStackScroll = mScroller.getStackScroll();
261 | float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
262 | if (Float.compare(overScrollAmount, 0f) != 0) {
263 | // Bound the overscroll to a fixed amount, and inversely scale the y-movement
264 | // relative to how close we are to the max overscroll
265 | float maxOverScroll = mConfig.taskStackOverscrollPct;
266 | deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
267 | / maxOverScroll));
268 | }
269 | mScroller.setStackScroll(curStackScroll + deltaP);
270 | }
271 | mLastMotionX = x;
272 | mLastMotionY = y;
273 | mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
274 | mTotalPMotion += Math.abs(deltaP);
275 | break;
276 | }
277 | case MotionEvent.ACTION_UP: {
278 | mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
279 | int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
280 | if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
281 | float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
282 | int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
283 | (DVConstants.Values.DView.TaskStackMaxOverscrollRange -
284 | DVConstants.Values.DView.TaskStackMinOverscrollRange));
285 | mScroller.mScroller.fling(0,
286 | mScroller.progressToScrollRange(mScroller.getStackScroll()),
287 | 0, velocity,
288 | 0, 0,
289 | mScroller.progressToScrollRange(mDeckView.getStackAlgorithm().mMinScrollP),
290 | mScroller.progressToScrollRange(mDeckView.getStackAlgorithm().mMaxScrollP),
291 | 0, DVConstants.Values.DView.TaskStackMinOverscrollRange +
292 | overscrollRange);
293 | // Invalidate to kick off computeScroll
294 | mDeckView.invalidate();
295 | } else if (mScroller.isScrollOutOfBounds()) {
296 | // Animate the scroll back into bounds
297 | mScroller.animateBoundScroll();
298 | }
299 |
300 | mActivePointerId = INACTIVE_POINTER_ID;
301 | mIsScrolling = false;
302 | mTotalPMotion = 0;
303 | recycleVelocityTracker();
304 | break;
305 | }
306 | case MotionEvent.ACTION_POINTER_UP: {
307 | int pointerIndex = ev.getActionIndex();
308 | int pointerId = ev.getPointerId(pointerIndex);
309 | if (pointerId == mActivePointerId) {
310 | // Select a new active pointer id and reset the motion state
311 | final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
312 | mActivePointerId = ev.getPointerId(newPointerIndex);
313 | mLastMotionX = (int) ev.getX(newPointerIndex);
314 | mLastMotionY = (int) ev.getY(newPointerIndex);
315 | mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
316 | mVelocityTracker.clear();
317 | }
318 | break;
319 | }
320 | case MotionEvent.ACTION_CANCEL: {
321 | if (mScroller.isScrollOutOfBounds()) {
322 | // Animate the scroll back into bounds
323 | mScroller.animateBoundScroll();
324 | }
325 | mActivePointerId = INACTIVE_POINTER_ID;
326 | mIsScrolling = false;
327 | mTotalPMotion = 0;
328 | recycleVelocityTracker();
329 | break;
330 | }
331 | }
332 | return true;
333 | }
334 |
335 | /**
336 | * Handles generic motion events
337 | */
338 | public boolean onGenericMotionEvent(MotionEvent ev) {
339 | if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
340 | InputDevice.SOURCE_CLASS_POINTER) {
341 | int action = ev.getAction();
342 | switch (action & MotionEvent.ACTION_MASK) {
343 | case MotionEvent.ACTION_SCROLL:
344 | // Find the front most task and scroll the next task to the front
345 | float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
346 | if (vScroll > 0) {
347 | if (mDeckView.ensureFocusedTask()) {
348 | mDeckView.focusNextTask(true, false);
349 | }
350 | } else {
351 | if (mDeckView.ensureFocusedTask()) {
352 | mDeckView.focusNextTask(false, false);
353 | }
354 | }
355 | return true;
356 | }
357 | }
358 | return false;
359 | }
360 |
361 | /**
362 | * * SwipeHelper Implementation ***
363 | */
364 |
365 | @Override
366 | public View getChildAtPosition(MotionEvent ev) {
367 | return findViewAtPoint((int) ev.getX(), (int) ev.getY());
368 | }
369 |
370 | @Override
371 | public boolean canChildBeDismissed(View v) {
372 | return true;
373 | }
374 |
375 | @Override
376 | public void onBeginDrag(View v) {
377 | DeckChildView tv = (DeckChildView) v;
378 | // Disable clipping with the stack while we are swiping
379 | tv.setClipViewInStack(false);
380 | // Disallow touch events from this task view
381 | tv.setTouchEnabled(false);
382 | // Disallow parents from intercepting touch events
383 | final ViewParent parent = mDeckView.getParent();
384 | if (parent != null) {
385 | parent.requestDisallowInterceptTouchEvent(true);
386 | }
387 | }
388 |
389 | @Override
390 | public void onSwipeChanged(View v, float delta) {
391 | // Do nothing
392 | }
393 |
394 | @Override
395 | public void onChildDismissed(View v) {
396 | DeckChildView tv = (DeckChildView) v;
397 | // Re-enable clipping with the stack (we will reuse this view)
398 | tv.setClipViewInStack(true);
399 | // Re-enable touch events from this task view
400 | tv.setTouchEnabled(true);
401 | // Remove the task view from the stack
402 | mDeckView.onDeckChildViewDismissed(tv);
403 | }
404 |
405 | @Override
406 | public void onSnapBackCompleted(View v) {
407 | DeckChildView tv = (DeckChildView) v;
408 | // Re-enable clipping with the stack
409 | tv.setClipViewInStack(true);
410 | // Re-enable touch events from this task view
411 | tv.setTouchEnabled(true);
412 | }
413 |
414 | @Override
415 | public void onDragCancelled(View v) {
416 | // Do nothing
417 | }
418 | }
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/FixedSizeImageView.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | /**
4 | * Created by Vikram on 01/04/2015.
5 | */
6 |
7 | import android.content.Context;
8 | import android.graphics.drawable.BitmapDrawable;
9 | import android.graphics.drawable.Drawable;
10 | import android.util.AttributeSet;
11 | import android.widget.ImageView;
12 |
13 | /**
14 | * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when
15 | * setting the image to Null.
16 | */
17 | public class FixedSizeImageView extends ImageView {
18 |
19 | boolean mAllowRelayout = true;
20 | boolean mAllowInvalidate = true;
21 |
22 | public FixedSizeImageView(Context context) {
23 | this(context, null);
24 | }
25 |
26 | public FixedSizeImageView(Context context, AttributeSet attrs) {
27 | this(context, attrs, 0);
28 | }
29 |
30 | public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
31 | this(context, attrs, defStyleAttr, 0);
32 | }
33 |
34 | public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
35 | super(context, attrs, defStyleAttr, defStyleRes);
36 | }
37 |
38 | @Override
39 | public void requestLayout() {
40 | if (mAllowRelayout) {
41 | super.requestLayout();
42 | }
43 | }
44 |
45 | @Override
46 | public void invalidate() {
47 | if (mAllowInvalidate) {
48 | super.invalidate();
49 | }
50 | }
51 |
52 | @Override
53 | public void setImageDrawable(Drawable drawable) {
54 | boolean isNullBitmapDrawable = (drawable instanceof BitmapDrawable) &&
55 | (((BitmapDrawable) drawable).getBitmap() == null);
56 | if (drawable == null || isNullBitmapDrawable) {
57 | mAllowRelayout = false;
58 | mAllowInvalidate = false;
59 | }
60 | super.setImageDrawable(drawable);
61 | mAllowRelayout = true;
62 | mAllowInvalidate = true;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/ViewAnimation.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.graphics.Rect;
5 |
6 | import com.appeaser.deckview.helpers.DeckChildViewTransform;
7 | import com.appeaser.deckview.utilities.ReferenceCountedTrigger;
8 |
9 | /**
10 | * Created by Vikram on 01/04/2015.
11 | */
12 | /* Common code related to view animations */
13 | public class ViewAnimation {
14 |
15 | /* The animation context for a task view animation into Recents */
16 | public static class TaskViewEnterContext {
17 | // A trigger to run some logic when all the animations complete. This works around the fact
18 | // that it is difficult to coordinate ViewPropertyAnimators
19 | public ReferenceCountedTrigger postAnimationTrigger;
20 | // An update listener to notify as the enter animation progresses (used for the home transition)
21 | public ValueAnimator.AnimatorUpdateListener updateListener;
22 |
23 | // These following properties are updated for each task view we start the enter animation on
24 |
25 | // Whether or not the current task occludes the launch target
26 | boolean currentTaskOccludesLaunchTarget;
27 | // The task rect for the current stack
28 | Rect currentTaskRect;
29 | // The transform of the current task view
30 | public DeckChildViewTransform currentTaskTransform;
31 | // The view index of the current task view
32 | public int currentStackViewIndex;
33 | // The total number of task views
34 | public int currentStackViewCount;
35 |
36 | public TaskViewEnterContext(ReferenceCountedTrigger t) {
37 | postAnimationTrigger = t;
38 | }
39 | }
40 |
41 | /* The animation context for a task view animation out of Recents */
42 | public static class TaskViewExitContext {
43 | // A trigger to run some logic when all the animations complete. This works around the fact
44 | // that it is difficult to coordinate ViewPropertyAnimators
45 | public ReferenceCountedTrigger postAnimationTrigger;
46 |
47 | // The translationY to apply to a TaskView to move it off the bottom of the task stack
48 | public int offscreenTranslationY;
49 |
50 | public TaskViewExitContext(ReferenceCountedTrigger t) {
51 | postAnimationTrigger = t;
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/deckview/src/main/java/com/appeaser/deckview/views/ViewPool.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckview.views;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.Iterator;
6 | import java.util.LinkedList;
7 |
8 | /**
9 | * Created by Vikram on 01/04/2015.
10 | */
11 | /* A view pool to manage more views than we can visibly handle */
12 | public class ViewPool {
13 |
14 | /* An interface to the consumer of a view pool */
15 | public interface ViewPoolConsumer {
16 | public V createView(Context context);
17 |
18 | public void prepareViewToEnterPool(V v);
19 |
20 | public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
21 |
22 | public boolean hasPreferredData(V v, T preferredData);
23 | }
24 |
25 | Context mContext;
26 | ViewPoolConsumer mViewCreator;
27 | LinkedList mPool = new LinkedList();
28 |
29 | /**
30 | * Initializes the pool with a fixed predetermined pool size
31 | */
32 | public ViewPool(Context context, ViewPoolConsumer viewCreator) {
33 | mContext = context;
34 | mViewCreator = viewCreator;
35 | }
36 |
37 | /**
38 | * Returns a view into the pool
39 | */
40 | void returnViewToPool(V v) {
41 | mViewCreator.prepareViewToEnterPool(v);
42 | mPool.push(v);
43 | }
44 |
45 | /**
46 | * Gets a view from the pool and prepares it
47 | */
48 | V pickUpViewFromPool(T preferredData, T prepareData) {
49 | V v = null;
50 | boolean isNewView = false;
51 | if (mPool.isEmpty()) {
52 | v = mViewCreator.createView(mContext);
53 | isNewView = true;
54 | } else {
55 | // Try and find a preferred view
56 | Iterator iter = mPool.iterator();
57 | while (iter.hasNext()) {
58 | V vpv = iter.next();
59 | if (mViewCreator.hasPreferredData(vpv, preferredData)) {
60 | v = vpv;
61 | iter.remove();
62 | break;
63 | }
64 | }
65 | // Otherwise, just grab the first view
66 | if (v == null) {
67 | v = mPool.pop();
68 | }
69 | }
70 | mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
71 | return v;
72 | }
73 |
74 | /**
75 | * Returns an iterator to the list of the views in the pool.
76 | */
77 | Iterator poolViewIterator() {
78 | if (mPool != null) {
79 | return mPool.iterator();
80 | }
81 | return null;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/deckview/src/main/res/drawable-v21/deck_child_view_button_bg.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
--------------------------------------------------------------------------------
/deckview/src/main/res/drawable-v21/deck_child_view_dismiss_dark.xml:
--------------------------------------------------------------------------------
1 |
16 |
21 |
24 |
--------------------------------------------------------------------------------
/deckview/src/main/res/drawable-v21/deck_child_view_dismiss_light.xml:
--------------------------------------------------------------------------------
1 |
16 |
21 |
24 |
--------------------------------------------------------------------------------
/deckview/src/main/res/drawable-v21/deck_child_view_header_bg.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
18 |
19 |
--------------------------------------------------------------------------------
/deckview/src/main/res/drawable-v21/deck_child_view_header_bg_color.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/deckview/src/main/res/interpolator-v21/decelerate_quint.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
--------------------------------------------------------------------------------
/deckview/src/main/res/interpolator-v21/fast_out_linear_in.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/deckview/src/main/res/interpolator-v21/fast_out_slow_in.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/deckview/src/main/res/interpolator-v21/linear_out_slow_in.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/deckview/src/main/res/layout/deck_child_view.xml:
--------------------------------------------------------------------------------
1 |
15 |
19 |
20 |
24 |
25 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/deckview/src/main/res/layout/deck_child_view_header.xml:
--------------------------------------------------------------------------------
1 |
15 |
20 |
21 |
29 |
30 |
45 |
46 |
56 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 0.26
6 |
7 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values-sw600dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 0.25
6 |
7 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 0.075
6 |
7 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values-sw720dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 3dp
6 |
7 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #ffe6e6e6
6 |
7 |
8 | #ffeeeeee
9 |
10 |
11 | #cc000000
12 |
13 |
14 | #28ffffff
15 |
16 |
17 | #44000000
18 |
19 |
20 | #03000000
21 |
22 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 250
6 |
7 |
8 | 250
9 |
10 |
11 | 225
12 |
13 |
14 | 96
15 |
16 |
20 | 325
21 |
22 |
26 | 100
27 |
28 |
29 | 200
30 |
31 |
33 | 225
34 |
35 |
37 | 12
38 |
39 |
40 | 125
41 |
42 |
44 | 225
45 |
46 |
47 | 250
48 |
49 |
50 | 1
51 |
52 |
53 | 400
54 |
55 |
57 | false
58 |
59 |
60 | 200
61 |
62 |
63 | false
64 |
65 |
66 | 212
67 | 0
68 |
69 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 | 800dp
7 |
8 |
9 | - 0.03333
10 |
11 |
12 | - 0.0875
13 |
14 |
15 | 16dp
16 |
17 |
18 | 100dp
19 |
20 |
21 | 2dp
22 |
23 |
24 | 1.5dp
25 |
26 |
27 | 20dp
28 |
29 |
30 | 80dp
31 |
32 |
33 | 64dp
34 |
35 |
36 | - 0.6
37 |
38 |
39 | 56dp
40 |
41 |
42 | - 0.9
43 |
44 |
46 | 1dp
47 |
48 | 8dp
49 |
50 |
51 | 48dp
52 |
53 |
--------------------------------------------------------------------------------
/deckview/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DeckViewLibrary
3 |
4 | Nothing here
5 |
6 |
7 | Dismiss %s.
8 |
9 |
--------------------------------------------------------------------------------
/deckviewsample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/deckviewsample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.appeaser.deckviewsample"
9 | minSdkVersion 21
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.squareup.picasso:picasso:2.5.2'
25 | compile project(':deckview')
26 | }
27 |
--------------------------------------------------------------------------------
/deckviewsample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:/Users/Vikram/Documents/Android/adt-bundle-windows-x86-20130219/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/deckviewsample/src/androidTest/java/com/appeaser/deckviewsample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckviewsample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/deckviewsample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/java/com/appeaser/deckviewsample/Datum.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckviewsample;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import com.squareup.picasso.Target;
7 |
8 | /**
9 | * Simple model class
10 | * One important requirement for DeckView to function
11 | * is that all items in the dataset *must be* uniquely
12 | * identifiable. No two items can be such
13 | * that `item1.equals(item2)` returns `true`.
14 | * See equals() implementation below.
15 | * `id` is generated using `DeckViewSampleActivity#generateuniqueKey()`
16 | * Implementing `Parcelable` serves only one purpose - to persist data
17 | * on configuration change.
18 | */
19 | public class Datum implements Parcelable {
20 |
21 | public int id;
22 | public String headerTitle, link;
23 | public Target target;
24 |
25 | public Datum() {
26 | // Nothing
27 | }
28 |
29 | @Override
30 | public int describeContents() {
31 | return 0;
32 | }
33 |
34 | public Datum(Parcel in) {
35 | readFromParcel(in);
36 | }
37 |
38 | public void readFromParcel(Parcel in) {
39 | id = in.readInt();
40 | headerTitle = in.readString();
41 | link = in.readString();
42 | }
43 |
44 | @Override
45 | public void writeToParcel(Parcel dest, int flags) {
46 | dest.writeInt(id);
47 | dest.writeString(headerTitle);
48 | dest.writeString(link);
49 | }
50 |
51 | public static final Creator CREATOR = new Creator() {
52 | public Datum createFromParcel(Parcel in) {
53 | return new Datum(in);
54 | }
55 |
56 | public Datum[] newArray(int size) {
57 | return new Datum[size];
58 | }
59 | };
60 |
61 | @Override
62 | public boolean equals(Object o) {
63 | return ((Datum) o).id == this.id;
64 | }
65 | }
--------------------------------------------------------------------------------
/deckviewsample/src/main/java/com/appeaser/deckviewsample/DeckViewSampleActivity.java:
--------------------------------------------------------------------------------
1 | package com.appeaser.deckviewsample;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.Drawable;
8 | import android.os.Bundle;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.widget.Toast;
12 |
13 | import com.appeaser.deckview.views.DeckChildView;
14 | import com.appeaser.deckview.views.DeckView;
15 | import com.squareup.picasso.Picasso;
16 | import com.squareup.picasso.Target;
17 |
18 | import java.lang.ref.WeakReference;
19 | import java.util.ArrayList;
20 | import java.util.Random;
21 |
22 | /**
23 | * Basic sample for DeckView.
24 | * Images are downloaded and cached using
25 | * Picasso "http://square.github.io/picasso/".
26 | * DeckView is *very* young & can only
27 | * afford basic functionality.
28 | */
29 | public class DeckViewSampleActivity extends Activity {
30 |
31 | // View that stacks its children like a deck of cards
32 | DeckView mDeckView;
33 |
34 | Drawable mDefaultHeaderIcon;
35 | ArrayList mEntries;
36 |
37 | // Placeholder for when the image is being downloaded
38 | Bitmap mDefaultThumbnail;
39 |
40 | // Retain position on configuration change
41 | // imageSize to pass to http://lorempixel.com
42 | int scrollToChildIndex = -1, imageSize = 500;
43 |
44 | // SavedInstance bundle keys
45 | final String CURRENT_SCROLL = "current.scroll", CURRENT_LIST = "current.list";
46 |
47 | @Override
48 | protected void onCreate(Bundle savedInstanceState) {
49 | super.onCreate(savedInstanceState);
50 | setContentView(R.layout.activity_deck_view_sample);
51 |
52 | mDeckView = (DeckView) findViewById(R.id.deckview);
53 | mDefaultThumbnail = BitmapFactory.decodeResource(getResources(),
54 | R.drawable.default_thumbnail);
55 | mDefaultHeaderIcon = getResources().getDrawable(R.drawable.default_header_icon);
56 |
57 | if (savedInstanceState != null) {
58 | if (savedInstanceState.containsKey(CURRENT_LIST)) {
59 | mEntries = savedInstanceState.getParcelableArrayList(CURRENT_LIST);
60 | }
61 |
62 | if (savedInstanceState.containsKey(CURRENT_SCROLL)) {
63 | scrollToChildIndex = savedInstanceState.getInt(CURRENT_SCROLL);
64 | }
65 | }
66 |
67 | if (mEntries == null) {
68 | mEntries = new ArrayList<>();
69 |
70 | for (int i = 1; i < 100; i++) {
71 | Datum datum = new Datum();
72 | datum.id = generateUniqueKey();
73 | datum.link = "http://lorempixel.com/" + imageSize + "/" + imageSize
74 | + "/sports/" + "ID " + datum.id + "/";
75 | datum.headerTitle = "Image ID " + datum.id;
76 | mEntries.add(datum);
77 | }
78 | }
79 |
80 | // Callback implementation
81 | DeckView.Callback deckViewCallback = new DeckView.Callback() {
82 | @Override
83 | public ArrayList getData() {
84 | return mEntries;
85 | }
86 |
87 | @Override
88 | public void loadViewData(WeakReference> dcv, Datum item) {
89 | loadViewDataInternal(item, dcv);
90 | }
91 |
92 | @Override
93 | public void unloadViewData(Datum item) {
94 | Picasso.with(DeckViewSampleActivity.this).cancelRequest(item.target);
95 | }
96 |
97 | @Override
98 | public void onViewDismissed(Datum item) {
99 | mEntries.remove(item);
100 | mDeckView.notifyDataSetChanged();
101 | }
102 |
103 | @Override
104 | public void onItemClick(Datum item) {
105 | Toast.makeText(DeckViewSampleActivity.this,
106 | "Item with title: '" + item.headerTitle + "' clicked",
107 | Toast.LENGTH_SHORT).show();
108 | }
109 |
110 | @Override
111 | public void onNoViewsToDeck() {
112 | Toast.makeText(DeckViewSampleActivity.this,
113 | "No views to show",
114 | Toast.LENGTH_SHORT).show();
115 | }
116 | };
117 |
118 | mDeckView.initialize(deckViewCallback);
119 |
120 | if (scrollToChildIndex != -1) {
121 | mDeckView.post(new Runnable() {
122 | @Override
123 | public void run() {
124 | // Restore scroll position
125 | mDeckView.scrollToChild(scrollToChildIndex);
126 | }
127 | });
128 | }
129 | }
130 |
131 | void loadViewDataInternal(final Datum datum,
132 | final WeakReference> weakView) {
133 | // datum.target can be null
134 | Picasso.with(DeckViewSampleActivity.this).cancelRequest(datum.target);
135 |
136 | datum.target = new Target() {
137 | @Override
138 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
139 | // Pass loaded Bitmap to view
140 | if (weakView.get() != null) {
141 | weakView.get().onDataLoaded(datum, bitmap,
142 | mDefaultHeaderIcon, datum.headerTitle, Color.DKGRAY);
143 | }
144 | }
145 |
146 | @Override
147 | public void onBitmapFailed(Drawable errorDrawable) {
148 | // Loading failed. Pass default thumbnail instead
149 | if (weakView.get() != null) {
150 | weakView.get().onDataLoaded(datum, mDefaultThumbnail,
151 | mDefaultHeaderIcon, datum.headerTitle + " Failed", Color.DKGRAY);
152 | }
153 | }
154 |
155 | @Override
156 | public void onPrepareLoad(Drawable placeHolderDrawable) {
157 | // Pass the default thumbnail for now. It will
158 | // be replaced once the target Bitmap has been loaded
159 | if (weakView.get() != null) {
160 | weakView.get().onDataLoaded(datum, mDefaultThumbnail,
161 | mDefaultHeaderIcon, "Loading...", Color.DKGRAY);
162 | }
163 | }
164 | };
165 |
166 | // Begin loading
167 | Picasso.with(DeckViewSampleActivity.this).load(datum.link).into(datum.target);
168 | }
169 |
170 |
171 | @Override
172 | public boolean onCreateOptionsMenu(Menu menu) {
173 | // Inflate the menu; this adds items to the action bar if it is present.
174 | getMenuInflater().inflate(R.menu.menu_deck_view_sample, menu);
175 | return true;
176 | }
177 |
178 | @Override
179 | public boolean onOptionsItemSelected(MenuItem item) {
180 | // Handle action bar item clicks here. The action bar will
181 | // automatically handle clicks on the Home/Up button, so long
182 | // as you specify a parent activity in AndroidManifest.xml.
183 | int id = item.getItemId();
184 |
185 | // Add a new item to the end of the list
186 | if (id == R.id.action_add) {
187 | Datum datum = new Datum();
188 | datum.id = generateUniqueKey();
189 | datum.headerTitle = "(New) Image ID " + datum.id;
190 | datum.link = "http://lorempixel.com/" + imageSize + "/" + imageSize
191 | + "/sports/" + "ID " + datum.id + "/";
192 | mEntries.add(datum);
193 | mDeckView.notifyDataSetChanged();
194 | return true;
195 | } else if (id == R.id.action_add_multiple) {
196 | // Add multiple items (between 5 & 10 items)
197 | // at random indices
198 | Random rand = new Random();
199 |
200 | // adding between 5 and 10 items
201 | int numberOfItemsToAdd = rand.nextInt(6) + 5;
202 |
203 | for (int i = 0; i < numberOfItemsToAdd; i++) {
204 | int atIndex = mEntries.size() > 0 ?
205 | rand.nextInt(mEntries.size()) : 0;
206 |
207 | Datum datum = new Datum();
208 | datum.id = generateUniqueKey();
209 | datum.link = "http://lorempixel.com/" + imageSize + "/" + imageSize
210 | + "/sports/" + "ID " + datum.id + "/";
211 | datum.headerTitle = "(New) Image ID " + datum.id;
212 | mEntries.add(atIndex, datum);
213 | }
214 |
215 | mDeckView.notifyDataSetChanged();
216 | return true;
217 | }
218 |
219 | return super.onOptionsItemSelected(item);
220 | }
221 |
222 | @Override
223 | protected void onSaveInstanceState(Bundle outState) {
224 | // Save current scroll and the list
225 | int currentChildIndex = mDeckView.getCurrentChildIndex();
226 | outState.putInt(CURRENT_SCROLL, currentChildIndex);
227 | outState.putParcelableArrayList(CURRENT_LIST, mEntries);
228 |
229 | super.onSaveInstanceState(outState);
230 | }
231 |
232 | // Generates a key that will remain unique
233 | // during the application's lifecycle
234 | private static int generateUniqueKey() {
235 | return ++KEY;
236 | }
237 |
238 | private static int KEY = 0;
239 | }
240 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/drawable-nodpi/default_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/deckviewsample/src/main/res/drawable-nodpi/default_thumbnail.jpg
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/drawable-v21/box.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
25 |
30 |
31 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/drawable-xxxhdpi/default_header_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/deckviewsample/src/main/res/drawable-xxxhdpi/default_header_icon.png
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/layout/activity_deck_view_sample.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/menu/menu_deck_view_sample.xml:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/deckviewsample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/deckviewsample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/deckviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/deckviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DeckViewSample
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/deckviewsample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vikramkakkar/DeckView/0487563749a8b3c0df1a685a79229bca70599e1a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':deckview', ':deckviewsample'
2 |
--------------------------------------------------------------------------------