├── .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 | 4 | 8 | 9 | 13 | 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 | --------------------------------------------------------------------------------