Repository: vikramkakkar/DeckView
Branch: master
Commit: 0487563749a8
Files: 74
Total size: 247.1 KB
Directory structure:
gitextract_kqwh3f90/
├── .gitignore
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── appeaser/
│ │ └── deckviewlibrary/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ └── res/
│ └── 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/
│ │ ├── colors.xml
│ │ ├── config.xml
│ │ ├── dimens.xml
│ │ └── strings.xml
│ ├── values-land/
│ │ └── dimens.xml
│ ├── values-sw600dp/
│ │ └── dimens.xml
│ ├── values-sw600dp-land/
│ │ └── dimens.xml
│ └── values-sw720dp/
│ └── dimens.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-v21/
│ │ └── box.xml
│ ├── layout/
│ │ └── activity_deck_view_sample.xml
│ ├── menu/
│ │ └── menu_deck_view_sample.xml
│ ├── values/
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-v21/
│ │ └── styles.xml
│ └── values-w820dp/
│ └── dimens.xml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
#################
## AndroidStudio
#################
.gradle
project.properties
.idea
gen
*.class
out
*.iml
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
*.pubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.pfx
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
#############
## Windows detritus
#############
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac crap
.DS_Store
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist/
build/
eggs/
parts/
var/
sdist/
develop-eggs/
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
================================================
FILE: README.md
================================================
# DeckView
A ViewGroup that mimics Android (Lollipop) Recent apps screen layout.
######Note:
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.
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.appeaser.deckviewlibrary"
minSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\Vikram\Documents\Android\adt-bundle-windows-x86-20130219\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/androidTest/java/com/appeaser/deckviewlibrary/ApplicationTest.java
================================================
package com.appeaser.deckviewlibrary;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/res/values/strings.xml
================================================
DeckViewLibrary
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
================================================
FILE: deckview/.gitignore
================================================
/build
================================================
FILE: deckview/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
================================================
FILE: deckview/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:/Users/Vikram/Documents/Android/adt-bundle-windows-x86-20130219/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: deckview/src/androidTest/java/com/appeaser/deckview/ApplicationTest.java
================================================
package com.appeaser.deckview;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: deckview/src/main/AndroidManifest.xml
================================================
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/helpers/DeckChildViewTransform.java
================================================
package com.appeaser.deckview.helpers;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
/**
* Created by Vikram on 02/04/2015.
*/
/* The transform state for a task view */
public class DeckChildViewTransform {
public int startDelay = 0;
public int translationY = 0;
public float translationZ = 0;
public float scale = 1f;
public float alpha = 1f;
public boolean visible = false;
public Rect rect = new Rect();
public float p = 0f;
public DeckChildViewTransform() {
// Do nothing
}
public DeckChildViewTransform(DeckChildViewTransform o) {
startDelay = o.startDelay;
translationY = o.translationY;
translationZ = o.translationZ;
scale = o.scale;
alpha = o.alpha;
visible = o.visible;
rect.set(o.rect);
p = o.p;
}
/**
* Resets the current transform
*/
public void reset() {
startDelay = 0;
translationY = 0;
translationZ = 0;
scale = 1f;
alpha = 1f;
visible = false;
rect.setEmpty();
p = 0f;
}
/**
* Convenience functions to compare against current property values
*/
public boolean hasAlphaChangedFrom(float v) {
return (Float.compare(alpha, v) != 0);
}
public boolean hasScaleChangedFrom(float v) {
return (Float.compare(scale, v) != 0);
}
public boolean hasTranslationYChangedFrom(float v) {
return (Float.compare(translationY, v) != 0);
}
public boolean hasTranslationZChangedFrom(float v) {
return (Float.compare(translationZ, v) != 0);
}
/**
* Applies this transform to a view.
*/
public void applyToTaskView(View v, int duration, Interpolator interp, boolean allowLayers,
boolean allowShadows, ValueAnimator.AnimatorUpdateListener updateCallback) {
// Check to see if any properties have changed, and update the task view
if (duration > 0) {
ViewPropertyAnimator anim = v.animate();
boolean requiresLayers = false;
// Animate to the final state
if (hasTranslationYChangedFrom(v.getTranslationY())) {
anim.translationY(translationY);
}
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
anim.translationZ(translationZ);
}
if (hasScaleChangedFrom(v.getScaleX())) {
anim.scaleX(scale)
.scaleY(scale);
requiresLayers = true;
}
if (hasAlphaChangedFrom(v.getAlpha())) {
// Use layers if we animate alpha
anim.alpha(alpha);
requiresLayers = true;
}
if (requiresLayers && allowLayers) {
anim.withLayer();
}
if (updateCallback != null) {
anim.setUpdateListener(updateCallback);
} else {
anim.setUpdateListener(null);
}
anim.setStartDelay(startDelay)
.setDuration(duration)
.setInterpolator(interp)
.start();
} else {
// Set the changed properties
if (hasTranslationYChangedFrom(v.getTranslationY())) {
v.setTranslationY(translationY);
}
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
v.setTranslationZ(translationZ);
}
if (hasScaleChangedFrom(v.getScaleX())) {
v.setScaleX(scale);
v.setScaleY(scale);
}
if (hasAlphaChangedFrom(v.getAlpha())) {
v.setAlpha(alpha);
}
}
}
/**
* Reset the transform on a view.
*/
public static void reset(View v) {
v.setTranslationX(0f);
v.setTranslationY(0f);
v.setTranslationZ(0f);
v.setScaleX(1f);
v.setScaleY(1f);
v.setAlpha(1f);
}
@Override
public String toString() {
return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +
" scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
" p: " + p;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/helpers/DeckViewConfig.java
================================================
package com.appeaser.deckview.helpers;
/**
* Created by Vikram on 02/04/2015.
*/
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.appeaser.deckview.R;
import com.appeaser.deckview.utilities.DVConstants;
/**
* Configuration helper
*/
public class DeckViewConfig {
static DeckViewConfig sInstance;
static int sPrevConfigurationHashCode;
/**
* Levels of svelte in increasing severity/austerity.
*/
// No svelting.
public static final int SVELTE_NONE = 0;
// Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
// caching thumbnails as you scroll.
public static final int SVELTE_LIMIT_CACHE = 1;
// Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
// evict all thumbnails when hidden.
public static final int SVELTE_DISABLE_CACHE = 2;
// Disable all thumbnail loading.
public static final int SVELTE_DISABLE_LOADING = 3;
/**
* Animations
*/
public float animationPxMovementPerSecond;
/**
* Interpolators
*/
public Interpolator fastOutSlowInInterpolator;
public Interpolator fastOutLinearInInterpolator;
public Interpolator linearOutSlowInInterpolator;
public Interpolator quintOutInterpolator;
/**
* Filtering
*/
public int filteringCurrentViewsAnimDuration;
public int filteringNewViewsAnimDuration;
/**
* Insets
*/
public Rect systemInsets = new Rect();
public Rect displayRect = new Rect();
/**
* Layout
*/
boolean isLandscape;
/**
* Task stack
*/
public int taskStackScrollDuration;
public int taskStackMaxDim;
public int taskStackTopPaddingPx;
public float taskStackWidthPaddingPct;
public float taskStackOverscrollPct;
/**
* Transitions
*/
public int transitionEnterFromAppDelay;
public int transitionEnterFromHomeDelay;
/**
* Task view animation and styles
*/
public int taskViewEnterFromAppDuration;
public int taskViewEnterFromHomeDuration;
public int taskViewEnterFromHomeStaggerDelay;
public int taskViewExitToAppDuration;
public int taskViewExitToHomeDuration;
public int taskViewRemoveAnimDuration;
public int taskViewRemoveAnimTranslationXPx;
public int taskViewTranslationZMinPx;
public int taskViewTranslationZMaxPx;
public int taskViewRoundedCornerRadiusPx;
public int taskViewHighlightPx;
public int taskViewAffiliateGroupEnterOffsetPx;
public float taskViewThumbnailAlpha;
/**
* Task bar colors
*/
public int taskBarViewDefaultBackgroundColor;
public int taskBarViewLightTextColor;
public int taskBarViewDarkTextColor;
public int taskBarViewHighlightColor;
public float taskBarViewAffiliationColorMinAlpha;
/**
* Task bar size & animations
*/
public int taskBarHeight;
public int taskBarDismissDozeDelaySeconds;
/**
* Nav bar scrim
*/
public int navBarScrimEnterDuration;
/**
* Launch states
*/
public boolean launchedWithAltTab;
public boolean launchedWithNoRecentTasks;
public boolean launchedFromAppWithThumbnail;
public boolean launchedFromHome;
public boolean launchedFromSearchHome;
public boolean launchedReuseTaskStackViews;
public boolean launchedHasConfigurationChanged;
public int launchedToTaskId;
public int launchedNumVisibleTasks;
public int launchedNumVisibleThumbnails;
/**
* Misc *
*/
public boolean useHardwareLayers;
public int altTabKeyDelay;
public boolean fakeShadows;
/**
* Dev options and global settings
*/
public boolean debugModeEnabled;
public int svelteLevel;
/**
* Private constructor
*/
private DeckViewConfig(Context context) {
// Properties that don't have to be reloaded with each configuration change can be loaded
// here.
// Interpolators
fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.fast_out_slow_in);
fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.fast_out_linear_in);
linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.linear_out_slow_in);
quintOutInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.decelerate_quint);
}
/**
* Updates the configuration to the current context
*/
public static DeckViewConfig reinitialize(Context context) {
if (sInstance == null) {
sInstance = new DeckViewConfig(context);
}
int configHashCode = context.getResources().getConfiguration().hashCode();
if (sPrevConfigurationHashCode != configHashCode) {
sInstance.update(context);
sPrevConfigurationHashCode = configHashCode;
}
sInstance.updateOnReinitialize(context);
return sInstance;
}
/**
* Returns the current recents configuration
*/
public static DeckViewConfig getInstance() {
return sInstance;
}
/**
* Updates the state, given the specified context
*/
void update(Context context) {
SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
// Debug mode
debugModeEnabled = settings.getBoolean(DVConstants.Values.App.Key_DebugModeEnabled, false);
// Layout
isLandscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
// Insets
displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
// Animations
animationPxMovementPerSecond =
res.getDimensionPixelSize(R.dimen.animation_movement_in_dps_per_second);
// Filtering
filteringCurrentViewsAnimDuration =
res.getInteger(R.integer.filter_animate_current_views_duration);
filteringNewViewsAnimDuration =
res.getInteger(R.integer.filter_animate_new_views_duration);
// Task stack
taskStackScrollDuration =
res.getInteger(R.integer.animate_deck_scroll_duration);
TypedValue widthPaddingPctValue = new TypedValue();
res.getValue(R.dimen.deck_width_padding_percentage, widthPaddingPctValue, true);
taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
TypedValue stackOverscrollPctValue = new TypedValue();
res.getValue(R.dimen.deck_overscroll_percentage, stackOverscrollPctValue, true);
taskStackOverscrollPct = stackOverscrollPctValue.getFloat();
taskStackMaxDim = res.getInteger(R.integer.max_deck_view_dim);
taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.deck_top_padding);
// Transition
transitionEnterFromAppDelay =
res.getInteger(R.integer.enter_from_app_transition_duration);
transitionEnterFromHomeDelay =
res.getInteger(R.integer.enter_from_home_transition_duration);
// Task view animation and styles
taskViewEnterFromAppDuration =
res.getInteger(R.integer.task_enter_from_app_duration);
taskViewEnterFromHomeDuration =
res.getInteger(R.integer.task_enter_from_home_duration);
taskViewEnterFromHomeStaggerDelay =
res.getInteger(R.integer.task_enter_from_home_stagger_delay);
taskViewExitToAppDuration =
res.getInteger(R.integer.task_exit_to_app_duration);
taskViewExitToHomeDuration =
res.getInteger(R.integer.task_exit_to_home_duration);
taskViewRemoveAnimDuration =
res.getInteger(R.integer.animate_task_view_remove_duration);
taskViewRemoveAnimTranslationXPx =
res.getDimensionPixelSize(R.dimen.task_view_remove_anim_translation_x);
taskViewRoundedCornerRadiusPx =
res.getDimensionPixelSize(R.dimen.task_view_rounded_corners_radius);
taskViewHighlightPx = res.getDimensionPixelSize(R.dimen.task_view_highlight);
taskViewTranslationZMinPx = res.getDimensionPixelSize(R.dimen.task_view_z_min);
taskViewTranslationZMaxPx = res.getDimensionPixelSize(R.dimen.task_view_z_max);
taskViewAffiliateGroupEnterOffsetPx =
res.getDimensionPixelSize(R.dimen.task_view_affiliate_group_enter_offset);
TypedValue thumbnailAlphaValue = new TypedValue();
res.getValue(R.dimen.task_view_thumbnail_alpha, thumbnailAlphaValue, true);
taskViewThumbnailAlpha = thumbnailAlphaValue.getFloat();
// Task bar colors
taskBarViewDefaultBackgroundColor =
res.getColor(R.color.task_bar_default_background_color);
taskBarViewLightTextColor =
res.getColor(R.color.task_bar_light_text_color);
taskBarViewDarkTextColor =
res.getColor(R.color.task_bar_dark_text_color);
taskBarViewHighlightColor =
res.getColor(R.color.task_bar_highlight_color);
TypedValue affMinAlphaPctValue = new TypedValue();
res.getValue(R.dimen.task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);
taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();
// Task bar size & animations
taskBarHeight = res.getDimensionPixelSize(R.dimen.deck_child_header_bar_height);
taskBarDismissDozeDelaySeconds =
res.getInteger(R.integer.task_bar_dismiss_delay_seconds);
// Nav bar scrim
navBarScrimEnterDuration =
res.getInteger(R.integer.nav_bar_scrim_enter_duration);
// Misc
useHardwareLayers = res.getBoolean(R.bool.config_use_hardware_layers);
altTabKeyDelay = res.getInteger(R.integer.deck_alt_tab_key_delay);
fakeShadows = res.getBoolean(R.bool.config_fake_shadows);
svelteLevel = res.getInteger(R.integer.deck_svelte_level);
}
/**
* Updates the system insets
*/
public void updateSystemInsets(Rect insets) {
systemInsets.set(insets);
}
/**
* Updates the search bar app widget
*/
public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
}
/**
* Updates the states that need to be re-read whenever we re-initialize.
*/
void updateOnReinitialize(Context context/*, SystemServicesProxy ssp*/) {
}
/**
* Called when the configuration has changed, and we want to reset any configuration specific
* members.
*/
public void updateOnConfigurationChange() {
// Reset this flag on configuration change to ensure that we recreate new task views
launchedReuseTaskStackViews = false;
// Set this flag to indicate that the configuration has changed since Recents last launched
launchedHasConfigurationChanged = true;
}
/**
* Returns the task stack bounds in the current orientation. These bounds do not account for
* the system insets.
*/
public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,
Rect taskStackBounds) {
taskStackBounds.set(0, 0, windowWidth, windowHeight);
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/helpers/DeckViewSwipeHelper.java
================================================
package com.appeaser.deckview.helpers;
/**
* Created by Vikram on 02/04/2015.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.LinearInterpolator;
/**
* This class facilitates swipe to dismiss. It defines an interface to be implemented by the
* by the class hosting the views that need to swiped, and, using this interface, handles touch
* events and translates / fades / animates the view as it is dismissed.
*/
public class DeckViewSwipeHelper {
static final String TAG = "DeckViewSwipeHelper";
private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
private static final boolean CONSTRAIN_SWIPE = true;
private static final boolean FADE_OUT_DURING_SWIPE = true;
private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
public static final int X = 0;
public static final int Y = 1;
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
// where fade starts
static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width
// beyond which alpha->0
private float mMinAlpha = 0f;
private float mPagingTouchSlop;
Callback mCallback;
private int mSwipeDirection;
private VelocityTracker mVelocityTracker;
private float mInitialTouchPos;
private boolean mDragging;
private View mCurrView;
private boolean mCanCurrViewBeDimissed;
private float mDensityScale;
public boolean mAllowSwipeTowardsStart = true;
public boolean mAllowSwipeTowardsEnd = true;
private boolean mRtl;
public DeckViewSwipeHelper(int swipeDirection, Callback callback, float densityScale,
float pagingTouchSlop) {
mCallback = callback;
mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
mDensityScale = densityScale;
mPagingTouchSlop = pagingTouchSlop;
}
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
public void setPagingTouchSlop(float pagingTouchSlop) {
mPagingTouchSlop = pagingTouchSlop;
}
public void cancelOngoingDrag() {
if (mDragging) {
if (mCurrView != null) {
mCallback.onDragCancelled(mCurrView);
setTranslation(mCurrView, 0);
mCallback.onSnapBackCompleted(mCurrView);
mCurrView = null;
}
mDragging = false;
}
}
public void resetTranslation(View v) {
setTranslation(v, 0);
}
private float getPos(MotionEvent ev) {
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
private float getTranslation(View v) {
return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
}
private float getVelocity(VelocityTracker vt) {
return mSwipeDirection == X ? vt.getXVelocity() :
vt.getYVelocity();
}
private ObjectAnimator createTranslationAnimation(View v, float newPos) {
ObjectAnimator anim = ObjectAnimator.ofFloat(v,
mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
return anim;
}
private float getPerpendicularVelocity(VelocityTracker vt) {
return mSwipeDirection == X ? vt.getYVelocity() :
vt.getXVelocity();
}
private void setTranslation(View v, float translate) {
if (mSwipeDirection == X) {
v.setTranslationX(translate);
} else {
v.setTranslationY(translate);
}
}
private float getSize(View v) {
final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();
return mSwipeDirection == X ? dm.widthPixels : dm.heightPixels;
}
public void setMinAlpha(float minAlpha) {
mMinAlpha = minAlpha;
}
float getAlphaForOffset(View view) {
float viewSize = getSize(view);
final float fadeSize = ALPHA_FADE_END * viewSize;
float result = 1.0f;
float pos = getTranslation(view);
if (pos >= viewSize * ALPHA_FADE_START) {
result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
} else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
}
result = Math.min(result, 1.0f);
result = Math.max(result, 0f);
return Math.max(mMinAlpha, result);
}
/**
* Determines whether the given view has RTL layout.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isLayoutRtl(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
} else {
return false;
}
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragging = false;
mCurrView = mCallback.getChildAtPosition(ev);
mVelocityTracker.clear();
if (mCurrView != null) {
mRtl = isLayoutRtl(mCurrView);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
} else {
mCanCurrViewBeDimissed = false;
}
break;
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
mVelocityTracker.addMovement(ev);
float pos = getPos(ev);
float delta = pos - mInitialTouchPos;
if (Math.abs(delta) > mPagingTouchSlop) {
mCallback.onBeginDrag(mCurrView);
mDragging = true;
mInitialTouchPos = pos - getTranslation(mCurrView);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDragging = false;
mCurrView = null;
break;
}
return mDragging;
}
/**
* @param view The view to be dismissed
* @param velocity The desired pixels/second speed at which the view should move
*/
private void dismissChild(final View view, float velocity) {
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
float newPos;
if (velocity < 0
|| (velocity == 0 && getTranslation(view) < 0)
// if we use the Menu to dismiss an item in landscape, animate up
|| (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {
newPos = -getSize(view);
} else {
newPos = getSize(view);
}
int duration = MAX_ESCAPE_ANIMATION_DURATION;
if (velocity != 0) {
duration = Math.min(duration,
(int) (Math.abs(newPos - getTranslation(view)) *
1000f / Math.abs(velocity)));
} else {
duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
}
ValueAnimator anim = createTranslationAnimation(view, newPos);
anim.setInterpolator(sLinearInterpolator);
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCallback.onChildDismissed(view);
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(1.f);
}
}
});
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(getAlphaForOffset(view));
}
}
});
anim.start();
}
private void snapChild(final View view, float velocity) {
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
ValueAnimator anim = createTranslationAnimation(view, 0);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.setInterpolator(DeckViewConfig.getInstance().linearOutSlowInInterpolator);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(getAlphaForOffset(view));
}
mCallback.onSwipeChanged(mCurrView, view.getTranslationX());
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(1.0f);
}
mCallback.onSnapBackCompleted(view);
}
});
anim.start();
}
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
if (!onInterceptTouchEvent(ev)) {
return mCanCurrViewBeDimissed;
}
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
float delta = getPos(ev) - mInitialTouchPos;
setSwipeAmount(delta);
mCallback.onSwipeChanged(mCurrView, delta);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mCurrView != null) {
endSwipe(mVelocityTracker);
}
break;
}
return true;
}
private void setSwipeAmount(float amount) {
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE
&& (!isValidSwipeDirection(amount) || !mCallback.canChildBeDismissed(mCurrView))) {
float size = getSize(mCurrView);
float maxScrollDistance = 0.15f * size;
if (Math.abs(amount) >= size) {
amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
amount = maxScrollDistance * (float) Math.sin((amount / size) * (Math.PI / 2));
}
}
setTranslation(mCurrView, amount);
if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
float alpha = getAlphaForOffset(mCurrView);
mCurrView.setAlpha(alpha);
}
}
private boolean isValidSwipeDirection(float amount) {
if (mSwipeDirection == X) {
if (mRtl) {
return (amount <= 0) ? mAllowSwipeTowardsEnd : mAllowSwipeTowardsStart;
} else {
return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;
}
}
// Vertical swipes are always valid.
return true;
}
private void endSwipe(VelocityTracker velocityTracker) {
velocityTracker.computeCurrentVelocity(1000 /* px/sec */);
float velocity = getVelocity(velocityTracker);
float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
float translation = getTranslation(mCurrView);
// Decide whether to dismiss the current view
boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
Math.abs(translation) > 0.6 * getSize(mCurrView);
boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
(Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
(velocity > 0) == (translation > 0);
boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
&& isValidSwipeDirection(translation)
&& (childSwipedFastEnough || childSwipedFarEnough);
if (dismissChild) {
// flingadingy
dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
} else {
// snappity
mCallback.onDragCancelled(mCurrView);
snapChild(mCurrView, velocity);
}
}
public interface Callback {
View getChildAtPosition(MotionEvent ev);
boolean canChildBeDismissed(View v);
void onBeginDrag(View v);
void onSwipeChanged(View v, float delta);
void onChildDismissed(View v);
void onSnapBackCompleted(View v);
void onDragCancelled(View v);
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/helpers/FakeShadowDrawable.java
================================================
package com.appeaser.deckview.helpers;
/**
* Created by Vikram on 01/04/2015.
*/
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.appeaser.deckview.R;
/**
* A rounded rectangle drawable which also includes a shadow around. This is mostly copied from
* frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/
* RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few
* modifications to suit our needs in SystemUI.
*/
public class FakeShadowDrawable extends Drawable {
// used to calculate content padding
final static double COS_45 = Math.cos(Math.toRadians(45));
final static float SHADOW_MULTIPLIER = 1.5f;
final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
Paint mCornerShadowPaint;
Paint mEdgeShadowPaint;
final RectF mCardBounds;
float mCornerRadius;
Path mCornerShadowPath;
// updated value with inset
float mMaxShadowSize;
// actual value set by developer
float mRawMaxShadowSize;
// multiplied value to account for shadow offset
float mShadowSize;
// actual value set by developer
float mRawShadowSize;
private boolean mDirty = true;
private final int mShadowStartColor;
private final int mShadowEndColor;
private boolean mAddPaddingForCorners = true;
/**
* If shadow size is set to a value above max shadow, we print a warning
*/
private boolean mPrintedShadowClipWarning = false;
public FakeShadowDrawable(Resources resources, DeckViewConfig config) {
mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color);
mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color);
mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset);
setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size),
resources.getDimensionPixelSize(R.dimen.fake_shadow_size));
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerShadowPaint.setDither(true);
mCornerRadius = config.taskViewRoundedCornerRadiusPx;
mCardBounds = new RectF();
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
}
@Override
public void setAlpha(int alpha) {
mCornerShadowPaint.setAlpha(alpha);
mEdgeShadowPaint.setAlpha(alpha);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mDirty = true;
}
void setShadowSize(float shadowSize, float maxShadowSize) {
if (shadowSize < 0 || maxShadowSize < 0) {
throw new IllegalArgumentException("invalid shadow size");
}
if (shadowSize > maxShadowSize) {
shadowSize = maxShadowSize;
if (!mPrintedShadowClipWarning) {
Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
+ "{CardView#setMaxCardElevation}.");
mPrintedShadowClipWarning = true;
}
}
if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
return;
}
mRawShadowSize = shadowSize;
mRawMaxShadowSize = maxShadowSize;
mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
mMaxShadowSize = maxShadowSize + mInsetShadow;
mDirty = true;
invalidateSelf();
}
@Override
public boolean getPadding(Rect padding) {
int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
mAddPaddingForCorners));
int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
mAddPaddingForCorners));
padding.set(hOffset, vOffset, hOffset, vOffset);
return true;
}
static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
boolean addPaddingForCorners) {
if (addPaddingForCorners) {
return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
} else {
return maxShadowSize * SHADOW_MULTIPLIER;
}
}
static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
boolean addPaddingForCorners) {
if (addPaddingForCorners) {
return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
} else {
return maxShadowSize;
}
}
@Override
public void setColorFilter(ColorFilter cf) {
mCornerShadowPaint.setColorFilter(cf);
mEdgeShadowPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public void draw(Canvas canvas) {
if (mDirty) {
buildComponents(getBounds());
mDirty = false;
}
canvas.translate(0, mRawShadowSize / 4);
drawShadow(canvas);
canvas.translate(0, -mRawShadowSize / 4);
}
private void drawShadow(Canvas canvas) {
final float edgeShadowTop = -mCornerRadius - mShadowSize;
final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
// LT
int saved = canvas.save();
canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawHorizontalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.width() - 2 * inset, -mCornerRadius,
mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
// RB
saved = canvas.save();
canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
canvas.rotate(180f);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawHorizontalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
// LB
saved = canvas.save();
canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
canvas.rotate(270f);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawVerticalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
// RT
saved = canvas.save();
canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
canvas.rotate(90f);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawVerticalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
}
private void buildShadowCorners() {
RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
RectF outerBounds = new RectF(innerBounds);
outerBounds.inset(-mShadowSize, -mShadowSize);
if (mCornerShadowPath == null) {
mCornerShadowPath = new Path();
} else {
mCornerShadowPath.reset();
}
mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
mCornerShadowPath.moveTo(-mCornerRadius, 0);
mCornerShadowPath.rLineTo(-mShadowSize, 0);
// outer arc
mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
// inner arc
mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
mCornerShadowPath.close();
float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
new float[]{0f, startRatio, 1f}
, Shader.TileMode.CLAMP));
// we offset the content shadowSize/2 pixels up to make it more realistic.
// this is why edge shadow shader has some extra space
// When drawing bottom edge shadow, we use that extra space.
mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
-mCornerRadius - mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
}
private void buildComponents(Rect bounds) {
// Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
// We could have different top-bottom offsets to avoid extra gap above but in that case
// center aligning Views inside the CardView would be problematic.
final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
buildShadowCorners();
}
float getMinWidth() {
final float content = 2 *
Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
return content + (mRawMaxShadowSize + mInsetShadow) * 2;
}
float getMinHeight() {
final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
+ mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/utilities/DVConstants.java
================================================
package com.appeaser.deckview.utilities;
/**
* Created by Vikram on 02/04/2015.
*/
public class DVConstants {
public static class DebugFlags {
// Enable this with any other debug flag to see more info
public static final boolean Verbose = false;
public static class App {
// Enables debug drawing for the transition thumbnail
public static final boolean EnableTransitionThumbnailDebugMode = false;
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = false;
// Enables clipping of tasks against each other
public static final boolean EnableTaskStackClipping = true;
// Enables tapping on the TaskBar to launch the task
public static final boolean EnableTaskBarTouchEvents = true;
// Enables app-info pane on long-pressing the icon
public static final boolean EnableDevAppInfoOnLongPress = true;
// Enables debug mode
public static final boolean EnableDebugMode = false;
// Enables the search bar layout
public static final boolean EnableSearchLayout = true;
// Enables the thumbnail alpha on the front-most task
public static final boolean EnableThumbnailAlphaOnFrontmost = false;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// Enables the simulated task affiliations
public static final boolean EnableSimulatedTaskGroups = false;
// Defines the number of mock task affiliations per group
public static final int TaskAffiliationsGroupCount = 12;
// Enables us to create mock recents tasks
public static final boolean EnableSystemServicesProxy = false;
// Defines the number of mock recents packages to create
public static final int SystemServicesProxyMockPackageCount = 3;
// Defines the number of mock recents tasks to create
public static final int SystemServicesProxyMockTaskCount = 100;
}
}
public static class Values {
public static class App {
public static int AppWidgetHostId = 1024;
public static String Key_SearchAppWidgetId = "searchAppWidgetId";
public static String Key_DebugModeEnabled = "debugModeEnabled";
public static String DebugModeVersion = "A";
}
public static class DView {
public static final int TaskStackMinOverscrollRange = 32;
public static final int TaskStackMaxOverscrollRange = 128;
public static final int FilterStartDelay = 25;
}
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/utilities/DVUtils.java
================================================
package com.appeaser.deckview.utilities;
import android.animation.Animator;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
import com.appeaser.deckview.helpers.DeckViewConfig;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* Created by Vikram on 02/04/2015.
*/
public class DVUtils {
// Reflection methods for altering shadows
private static Method sPropertyMethod;
static {
try {
Class> c = Class.forName("android.view.GLES20Canvas");
sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
/**
* Calculates a consistent animation duration (ms) for all animations depending on the movement
* of the object being animated.
*/
public static int calculateTranslationAnimationDuration(int distancePx) {
return calculateTranslationAnimationDuration(distancePx, 100);
}
public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {
DeckViewConfig config = DeckViewConfig.getInstance();
return Math.max(minDuration, (int) (1000f /* ms/s */ *
(Math.abs(distancePx) / config.animationPxMovementPerSecond)));
}
/**
* Scales a rect about its centroid
*/
public static void scaleRectAboutCenter(Rect r, float scale) {
if (scale != 1.0f) {
int cx = r.centerX();
int cy = r.centerY();
r.offset(-cx, -cy);
r.left = (int) (r.left * scale + 0.5f);
r.top = (int) (r.top * scale + 0.5f);
r.right = (int) (r.right * scale + 0.5f);
r.bottom = (int) (r.bottom * scale + 0.5f);
r.offset(cx, cy);
}
}
/**
* Maps a coorindate in a descendant view into the parent.
*/
public static float mapCoordInDescendentToSelf(View descendant, View root,
float[] coord, boolean includeRootScroll) {
ArrayList ancestorChain = new ArrayList();
float[] pt = {coord[0], coord[1]};
View v = descendant;
while (v != root && v != null) {
ancestorChain.add(v);
v = (View) v.getParent();
}
ancestorChain.add(root);
float scale = 1.0f;
int count = ancestorChain.size();
for (int i = 0; i < count; i++) {
View v0 = ancestorChain.get(i);
// For TextViews, scroll has a meaning which relates to the text position
// which is very strange... ignore the scroll.
if (v0 != descendant || includeRootScroll) {
pt[0] -= v0.getScrollX();
pt[1] -= v0.getScrollY();
}
v0.getMatrix().mapPoints(pt);
pt[0] += v0.getLeft();
pt[1] += v0.getTop();
scale *= v0.getScaleX();
}
coord[0] = pt[0];
coord[1] = pt[1];
return scale;
}
/**
* Maps a coordinate in the root to a descendent.
*/
public static float mapCoordInSelfToDescendent(View descendant, View root,
float[] coord, Matrix tmpInverseMatrix) {
ArrayList ancestorChain = new ArrayList();
float[] pt = {coord[0], coord[1]};
View v = descendant;
while (v != root) {
ancestorChain.add(v);
v = (View) v.getParent();
}
ancestorChain.add(root);
float scale = 1.0f;
int count = ancestorChain.size();
tmpInverseMatrix.set(IDENTITY_MATRIX);
for (int i = count - 1; i >= 0; i--) {
View ancestor = ancestorChain.get(i);
View next = i > 0 ? ancestorChain.get(i - 1) : null;
pt[0] += ancestor.getScrollX();
pt[1] += ancestor.getScrollY();
if (next != null) {
pt[0] -= next.getLeft();
pt[1] -= next.getTop();
next.getMatrix().invert(tmpInverseMatrix);
tmpInverseMatrix.mapPoints(pt);
scale *= next.getScaleX();
}
}
coord[0] = pt[0];
coord[1] = pt[1];
return scale;
}
/**
* Calculates the constrast between two colors, using the algorithm provided by the WCAG v2.
*/
public static float computeContrastBetweenColors(int bg, int fg) {
float bgR = Color.red(bg) / 255f;
float bgG = Color.green(bg) / 255f;
float bgB = Color.blue(bg) / 255f;
bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
float fgR = Color.red(fg) / 255f;
float fgG = Color.green(fg) / 255f;
float fgB = Color.blue(fg) / 255f;
fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
}
/**
* Returns the base color overlaid with another overlay color with a specified alpha.
*/
public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
return Color.rgb(
(int) (overlayAlpha * Color.red(baseColor) +
(1f - overlayAlpha) * Color.red(overlayColor)),
(int) (overlayAlpha * Color.green(baseColor) +
(1f - overlayAlpha) * Color.green(overlayColor)),
(int) (overlayAlpha * Color.blue(baseColor) +
(1f - overlayAlpha) * Color.blue(overlayColor)));
}
/**
* Sets some private shadow properties.
*/
public static void setShadowProperty(String property, String value)
throws IllegalAccessException, InvocationTargetException {
sPropertyMethod.invoke(null, property, value);
}
/**
* Cancels an animation ensuring that if it has listeners, onCancel and onEnd
* are not called.
*/
public static void cancelAnimationWithoutCallbacks(Animator animator) {
if (animator != null) {
animator.removeAllListeners();
animator.cancel();
}
}
public static Matrix IDENTITY_MATRIX = new Matrix() {
void oops() {
throw new IllegalStateException("Matrix can not be modified");
}
@Override
public void set(Matrix src) {
oops();
}
@Override
public void reset() {
oops();
}
@Override
public void setTranslate(float dx, float dy) {
oops();
}
@Override
public void setScale(float sx, float sy, float px, float py) {
oops();
}
@Override
public void setScale(float sx, float sy) {
oops();
}
@Override
public void setRotate(float degrees, float px, float py) {
oops();
}
@Override
public void setRotate(float degrees) {
oops();
}
@Override
public void setSinCos(float sinValue, float cosValue, float px, float py) {
oops();
}
@Override
public void setSinCos(float sinValue, float cosValue) {
oops();
}
@Override
public void setSkew(float kx, float ky, float px, float py) {
oops();
}
@Override
public void setSkew(float kx, float ky) {
oops();
}
@Override
public boolean setConcat(Matrix a, Matrix b) {
oops();
return false;
}
@Override
public boolean preTranslate(float dx, float dy) {
oops();
return false;
}
@Override
public boolean preScale(float sx, float sy, float px, float py) {
oops();
return false;
}
@Override
public boolean preScale(float sx, float sy) {
oops();
return false;
}
@Override
public boolean preRotate(float degrees, float px, float py) {
oops();
return false;
}
@Override
public boolean preRotate(float degrees) {
oops();
return false;
}
@Override
public boolean preSkew(float kx, float ky, float px, float py) {
oops();
return false;
}
@Override
public boolean preSkew(float kx, float ky) {
oops();
return false;
}
@Override
public boolean preConcat(Matrix other) {
oops();
return false;
}
@Override
public boolean postTranslate(float dx, float dy) {
oops();
return false;
}
@Override
public boolean postScale(float sx, float sy, float px, float py) {
oops();
return false;
}
@Override
public boolean postScale(float sx, float sy) {
oops();
return false;
}
@Override
public boolean postRotate(float degrees, float px, float py) {
oops();
return false;
}
@Override
public boolean postRotate(float degrees) {
oops();
return false;
}
@Override
public boolean postSkew(float kx, float ky, float px, float py) {
oops();
return false;
}
@Override
public boolean postSkew(float kx, float ky) {
oops();
return false;
}
@Override
public boolean postConcat(Matrix other) {
oops();
return false;
}
@Override
public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
oops();
return false;
}
@Override
public boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex,
int pointCount) {
oops();
return false;
}
@Override
public void setValues(float[] values) {
oops();
}
};
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/utilities/DozeTrigger.java
================================================
package com.appeaser.deckview.utilities;
/**
* Created by Vikram on 01/04/2015.
*/
import android.os.Handler;
/**
* A dozer is a class that fires a trigger after it falls asleep. You can occasionally poke it to
* wake it up, but it will fall asleep if left untouched.
*/
public class DozeTrigger {
Handler mHandler;
boolean mIsDozing;
boolean mHasTriggered;
int mDozeDurationSeconds;
Runnable mSleepRunnable;
// Sleep-runnable
Runnable mDozeRunnable = new Runnable() {
@Override
public void run() {
mSleepRunnable.run();
mIsDozing = false;
mHasTriggered = true;
}
};
public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
mHandler = new Handler();
mDozeDurationSeconds = dozeDurationSeconds;
mSleepRunnable = sleepRunnable;
}
/**
* Starts dozing. This also resets the trigger flag.
*/
public void startDozing() {
forcePoke();
mHasTriggered = false;
}
/**
* Stops dozing.
*/
public void stopDozing() {
mHandler.removeCallbacks(mDozeRunnable);
mIsDozing = false;
}
/**
* Poke this dozer to wake it up for a little bit, if it is dozing.
*/
public void poke() {
if (mIsDozing) {
forcePoke();
}
}
/**
* Poke this dozer to wake it up for a little bit.
*/
void forcePoke() {
mHandler.removeCallbacks(mDozeRunnable);
mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
mIsDozing = true;
}
/**
* Returns whether we are dozing or not.
*/
public boolean isDozing() {
return mIsDozing;
}
/**
* Returns whether the trigger has fired at least once.
*/
public boolean hasTriggered() {
return mHasTriggered;
}
/**
* Resets the doze trigger state.
*/
public void resetTrigger() {
mHasTriggered = false;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/utilities/ReferenceCountedTrigger.java
================================================
package com.appeaser.deckview.utilities;
/**
* Created by Vikram on 01/04/2015.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import java.util.ArrayList;
/**
* A ref counted trigger that does some logic when the count is first incremented, or last
* decremented. Not thread safe as it's not currently needed.
*/
public class ReferenceCountedTrigger {
Context mContext;
int mCount;
ArrayList mFirstIncRunnables = new ArrayList();
ArrayList mLastDecRunnables = new ArrayList();
Runnable mErrorRunnable;
// Convenience runnables
Runnable mIncrementRunnable = new Runnable() {
@Override
public void run() {
increment();
}
};
Runnable mDecrementRunnable = new Runnable() {
@Override
public void run() {
decrement();
}
};
public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable,
Runnable lastDecRunnable, Runnable errorRunanable) {
mContext = context;
if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable);
if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable);
mErrorRunnable = errorRunanable;
}
/**
* Increments the ref count
*/
public void increment() {
if (mCount == 0 && !mFirstIncRunnables.isEmpty()) {
int numRunnables = mFirstIncRunnables.size();
for (int i = 0; i < numRunnables; i++) {
mFirstIncRunnables.get(i).run();
}
}
mCount++;
}
/**
* Convenience method to increment this trigger as a runnable
*/
public Runnable incrementAsRunnable() {
return mIncrementRunnable;
}
/**
* Adds a runnable to the last-decrement runnables list.
*/
public void addLastDecrementRunnable(Runnable r) {
// To ensure that the last decrement always calls, we increment and decrement after setting
// the last decrement runnable
boolean ensureLastDecrement = (mCount == 0);
if (ensureLastDecrement) increment();
mLastDecRunnables.add(r);
if (ensureLastDecrement) decrement();
}
/**
* Decrements the ref count
*/
public void decrement() {
mCount--;
if (mCount == 0 && !mLastDecRunnables.isEmpty()) {
int numRunnables = mLastDecRunnables.size();
for (int i = 0; i < numRunnables; i++) {
mLastDecRunnables.get(i).run();
}
} else if (mCount < 0) {
if (mErrorRunnable != null) {
mErrorRunnable.run();
} else {
new Throwable("Invalid ref count").printStackTrace();
//Console.logError(mContext, "Invalid ref count");
}
}
}
/**
* Convenience method to decrement this trigger as a runnable.
*/
public Runnable decrementAsRunnable() {
return mDecrementRunnable;
}
/**
* Convenience method to decrement this trigger as a animator listener.
*/
public Animator.AnimatorListener decrementOnAnimationEnd() {
return new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
decrement();
}
};
}
/**
* Returns the current ref count
*/
public int getCount() {
return mCount;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/AnimateableDeckChildViewBounds.java
================================================
package com.appeaser.deckview.views;
import android.graphics.Outline;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.appeaser.deckview.helpers.DeckViewConfig;
/**
* Created by Vikram on 02/04/2015.
*/
/* An outline provider that has a clip and outline that can be animated. */
public class AnimateableDeckChildViewBounds extends ViewOutlineProvider {
DeckViewConfig mConfig;
DeckChildView mSourceView;
Rect mClipRect = new Rect();
Rect mClipBounds = new Rect();
int mCornerRadius;
float mAlpha = 1f;
final float mMinAlpha = 0.25f;
public AnimateableDeckChildViewBounds(DeckChildView source, int cornerRadius) {
mConfig = DeckViewConfig.getInstance();
mSourceView = source;
mCornerRadius = cornerRadius;
setClipBottom(getClipBottom());
}
@Override
public void getOutline(View view, Outline outline) {
outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
outline.setRoundRect(mClipRect.left, mClipRect.top,
mSourceView.getWidth() - mClipRect.right,
mSourceView.getHeight() - mClipRect.bottom,
mCornerRadius);
}
/**
* Sets the view outline alpha.
*/
void setAlpha(float alpha) {
if (Float.compare(alpha, mAlpha) != 0) {
mAlpha = alpha;
mSourceView.invalidateOutline();
}
}
/**
* Sets the bottom clip.
*/
public void setClipBottom(int bottom) {
if (bottom != mClipRect.bottom) {
mClipRect.bottom = bottom;
mSourceView.invalidateOutline();
updateClipBounds();
if (!mConfig.useHardwareLayers) {
mSourceView.mThumbnailView.updateThumbnailVisibility(
bottom - mSourceView.getPaddingBottom());
}
}
}
/**
* Returns the bottom clip.
*/
public int getClipBottom() {
return mClipRect.bottom;
}
private void updateClipBounds() {
mClipBounds.set(mClipRect.left, mClipRect.top,
mSourceView.getWidth() - mClipRect.right,
mSourceView.getHeight() - mClipRect.bottom);
mSourceView.setClipBounds(mClipBounds);
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckChildView.java
================================================
package com.appeaser.deckview.views;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import com.appeaser.deckview.R;
import com.appeaser.deckview.helpers.DeckChildViewTransform;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.helpers.FakeShadowDrawable;
import com.appeaser.deckview.utilities.DVConstants;
import com.appeaser.deckview.utilities.DVUtils;
/**
* Created by Vikram on 02/04/2015.
*/
/* A task view */
public class DeckChildView extends FrameLayout implements
View.OnClickListener, View.OnLongClickListener {
/**
* The TaskView callbacks
*/
interface DeckChildViewCallbacks {
public void onDeckChildViewAppIconClicked(DeckChildView dcv);
public void onDeckChildViewAppInfoClicked(DeckChildView dcv);
public void onDeckChildViewClicked(DeckChildView dcv, T key);
public void onDeckChildViewDismissed(DeckChildView dcv);
public void onDeckChildViewClipStateChanged(DeckChildView dcv);
public void onDeckChildViewFocusChanged(DeckChildView dcv, boolean focused);
}
DeckViewConfig mConfig;
float mTaskProgress;
ObjectAnimator mTaskProgressAnimator;
float mMaxDimScale;
int mDimAlpha;
AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
Paint mDimLayerPaint = new Paint();
T mKey;
boolean mTaskDataLoaded;
boolean mIsFocused;
boolean mFocusAnimationsEnabled;
boolean mClipViewInStack;
AnimateableDeckChildViewBounds mViewBounds;
View mContent;
DeckChildViewThumbnail mThumbnailView;
DeckChildViewHeader mHeaderView;
DeckChildViewCallbacks mCb;
public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
// Optimizations
ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setTaskProgress((Float) animation.getAnimatedValue());
}
};
public DeckChildView(Context context) {
this(context, null);
}
public DeckChildView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DeckChildView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DeckChildView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = DeckViewConfig.getInstance();
mMaxDimScale = mConfig.taskStackMaxDim / 255f;
mClipViewInStack = true;
mViewBounds = new AnimateableDeckChildViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
setTaskProgress(getTaskProgress());
setDim(getDim());
if (mConfig.fakeShadows) {
setBackground(new FakeShadowDrawable(context.getResources(), mConfig));
}
setOutlineProvider(mViewBounds);
}
/**
* Set callback
*/
void setCallbacks(DeckChildViewCallbacks cb) {
mCb = cb;
}
/**
* Resets this TaskView for reuse.
*/
void reset() {
resetViewProperties();
resetNoUserInteractionState();
setClipViewInStack(false);
setCallbacks(null);
}
/**
* Gets the task
*/
T getAttachedKey() {
return mKey;
}
/**
* Returns the view bounds.
*/
AnimateableDeckChildViewBounds getViewBounds() {
return mViewBounds;
}
@Override
protected void onFinishInflate() {
// Bind the views
mContent = findViewById(R.id.task_view_content);
mHeaderView = (DeckChildViewHeader) findViewById(R.id.task_view_bar);
mThumbnailView = (DeckChildViewThumbnail) findViewById(R.id.task_view_thumbnail);
mThumbnailView.updateClipToTaskBar(mHeaderView);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();
int heightWithoutPadding = height - getPaddingTop() - getPaddingBottom();
// Measure the content
mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
// Measure the bar view, and action button
mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
// Measure the thumbnail to be square
mThumbnailView.measure(
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
invalidateOutline();
}
/**
* Synchronizes this view's properties with the task's transform
*/
void updateViewPropertiesToTaskTransform(DeckChildViewTransform toTransform, int duration) {
updateViewPropertiesToTaskTransform(toTransform, duration, null);
}
void updateViewPropertiesToTaskTransform(DeckChildViewTransform toTransform, int duration,
ValueAnimator.AnimatorUpdateListener updateCallback) {
// Apply the transform
toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
!mConfig.fakeShadows, updateCallback);
// Update the task progress
DVUtils.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
if (duration <= 0) {
setTaskProgress(toTransform.p);
} else {
mTaskProgressAnimator = ObjectAnimator.ofFloat(this, "taskProgress", toTransform.p);
mTaskProgressAnimator.setDuration(duration);
mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);
mTaskProgressAnimator.start();
}
}
/**
* Resets this view's properties
*/
void resetViewProperties() {
setDim(0);
setLayerType(View.LAYER_TYPE_NONE, null);
DeckChildViewTransform.reset(this);
}
/**
* When we are un/filtering, this method will set up the transform that we are animating to,
* in order to hide the task.
*/
void prepareTaskTransformForFilterTaskHidden(DeckChildViewTransform toTransform) {
// Fade the view out and slide it away
toTransform.alpha = 0f;
toTransform.translationY += 200;
toTransform.translationZ = 0;
}
/**
* When we are un/filtering, this method will setup the transform that we are animating from,
* in order to show the task.
*/
void prepareTaskTransformForFilterTaskVisible(DeckChildViewTransform fromTransform) {
// Fade the view in
fromTransform.alpha = 0f;
}
/**
* Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time.
*/
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
boolean occludesLaunchTarget, int offscreenY) {
int initialDim = getDim();
if (mConfig.launchedHasConfigurationChanged) {
// Just load the views as-is
} else if (mConfig.launchedFromAppWithThumbnail) {
if (isTaskViewLaunchTargetTask) {
// Set the dim to 0 so we can animate it in
initialDim = 0;
} else if (occludesLaunchTarget) {
// Move the task view off screen (below) so we can animate it in
setTranslationY(offscreenY);
}
} else if (mConfig.launchedFromHome) {
// Move the task view off screen (below) so we can animate it in
setTranslationY(offscreenY);
setTranslationZ(0);
setScaleX(1f);
setScaleY(1f);
}
// Apply the current dim
setDim(initialDim);
// Prepare the thumbnail view alpha
mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
}
/**
* Animates this task view as it enters recents
*/
void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
Log.i(getClass().getSimpleName(), "startEnterRecentsAnimation");
final DeckChildViewTransform transform = ctx.currentTaskTransform;
int startDelay = 0;
if (mConfig.launchedFromHome) {
Log.i(getClass().getSimpleName(), "mConfig.launchedFromHome false");
// Animate the tasks up
int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
int delay = mConfig.transitionEnterFromHomeDelay +
frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay;
setScaleX(transform.scale);
setScaleY(transform.scale);
if (!mConfig.fakeShadows) {
animate().translationZ(transform.translationZ);
}
animate()
.translationY(transform.translationY)
.setStartDelay(delay)
.setUpdateListener(ctx.updateListener)
.setInterpolator(mConfig.quintOutInterpolator)
.setDuration(mConfig.taskViewEnterFromHomeDuration +
frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay)
.withEndAction(new Runnable() {
@Override
public void run() {
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
})
.start();
ctx.postAnimationTrigger.increment();
startDelay = delay;
}
// Enable the focus animations from this point onwards so that they aren't affected by the
// window transitions
postDelayed(new Runnable() {
@Override
public void run() {
enableFocusAnimations();
}
}, startDelay);
}
/**
* Animates this task view as it leaves recents by pressing home.
*/
void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
animate()
.translationY(ctx.offscreenTranslationY)
.setStartDelay(0)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
.setDuration(mConfig.taskViewExitToHomeDuration)
.withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
.start();
ctx.postAnimationTrigger.increment();
}
/**
* Animates this task view as it exits recents
*/
void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
boolean occludesLaunchTarget, boolean lockToTask) {
if (isLaunchingTask) {
// Animate the thumbnail alpha back into full opacity for the window animation out
mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
// Animate the dim
if (mDimAlpha > 0) {
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
anim.setDuration(mConfig.taskViewExitToAppDuration);
anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
anim.start();
}
} else {
// Hide the dismiss button
mHeaderView.startLaunchTaskDismissAnimation();
// If this is another view in the task grouping and is in front of the launch task,
// animate it away first
if (occludesLaunchTarget) {
animate().alpha(0f)
.translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)
.setStartDelay(0)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
.setDuration(mConfig.taskViewExitToAppDuration)
.start();
}
}
}
/**
* Animates the deletion of this task view
*/
void startDeleteTaskAnimation(final Runnable r) {
// Disabling clipping with the stack while the view is animating away
setClipViewInStack(false);
animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
.alpha(0f)
.setStartDelay(0)
.setUpdateListener(null)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.setDuration(mConfig.taskViewRemoveAnimDuration)
.withEndAction(new Runnable() {
@Override
public void run() {
// We just throw this into a runnable because starting a view property
// animation using layers can cause inconsisten results if we try and
// update the layers while the animation is running. In some cases,
// the runnabled passed in may start an animation which also uses layers
// so we defer all this by posting this.
r.run();
// Re-enable clipping with the stack (we will reuse this view)
setClipViewInStack(true);
}
})
.start();
}
/**
* Animates this task view if the user does not interact with the stack after a certain time.
*/
void startNoUserInteractionAnimation() {
mHeaderView.startNoUserInteractionAnimation();
}
/**
* Mark this task view that the user does has not interacted with the stack after a certain time.
*/
void setNoUserInteractionState() {
mHeaderView.setNoUserInteractionState();
}
/**
* Resets the state tracking that the user has not interacted with the stack after a certain time.
*/
void resetNoUserInteractionState() {
mHeaderView.resetNoUserInteractionState();
}
/**
* Dismisses this task.
*/
void dismissTask() {
// Animate out the view and call the callback
final DeckChildView tv = this;
startDeleteTaskAnimation(new Runnable() {
@Override
public void run() {
if (mCb != null) {
mCb.onDeckChildViewDismissed(tv);
}
}
});
}
/**
* Returns whether this view should be clipped, or any views below should clip against this
* view.
*/
boolean shouldClipViewInStack() {
return mClipViewInStack && (getVisibility() == View.VISIBLE);
}
/**
* Sets whether this view should be clipped, or clipped against.
*/
public void setClipViewInStack(boolean clip) {
if (clip != mClipViewInStack) {
mClipViewInStack = clip;
if (mCb != null) {
mCb.onDeckChildViewClipStateChanged(this);
}
}
}
/**
* Sets the current task progress.
*/
public void setTaskProgress(float p) {
mTaskProgress = p;
mViewBounds.setAlpha(p);
updateDimFromTaskProgress();
}
/**
* Returns the current task progress.
*/
public float getTaskProgress() {
return mTaskProgress;
}
/**
* Returns the current dim.
*/
public void setDim(int dim) {
mDimAlpha = dim;
if (mConfig.useHardwareLayers) {
// Defer setting hardware layers if we have not yet measured, or there is no dim to draw
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
mDimColorFilter =
new PorterDuffColorFilter(Color.argb(mDimAlpha, 0, 0, 0),
PorterDuff.Mode.SRC_ATOP);
mDimLayerPaint.setColorFilter(mDimColorFilter);
mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
}
} else {
float dimAlpha = mDimAlpha / 255.0f;
if (mThumbnailView != null) {
mThumbnailView.setDimAlpha(dimAlpha);
}
if (mHeaderView != null) {
mHeaderView.setDimAlpha(dim);
}
}
}
/**
* Returns the current dim.
*/
public int getDim() {
return mDimAlpha;
}
/**
* Animates the dim to the task progress.
*/
void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {
// Animate the dim into view as well
int toDim = getDimFromTaskProgress();
if (toDim != getDim()) {
ObjectAnimator anim = ObjectAnimator.ofInt(DeckChildView.this, "dim", toDim);
anim.setStartDelay(delay);
anim.setDuration(duration);
if (postAnimRunnable != null) {
anim.addListener(postAnimRunnable);
}
anim.start();
}
}
/**
* Compute the dim as a function of the scale of this view.
*/
int getDimFromTaskProgress() {
float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
return (int) (dim * 255);
}
/**
* Update the dim as a function of the scale of this view.
*/
void updateDimFromTaskProgress() {
setDim(getDimFromTaskProgress());
}
/**** View focus state ****/
/**
* Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
* if the view is not currently visible, or we are in touch state (where we still want to keep
* track of focus).
*/
public void setFocusedTask(boolean animateFocusedState) {
mIsFocused = true;
if (mFocusAnimationsEnabled) {
// Focus the header bar
mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
}
// Update the thumbnail alpha with the focus
mThumbnailView.onFocusChanged(true);
// Call the callback
if (mCb != null) {
mCb.onDeckChildViewFocusChanged(this, true);
}
// Workaround, we don't always want it focusable in touch mode, but we want the first task
// to be focused after the enter-recents animation, which can be triggered from either touch
// or keyboard
setFocusableInTouchMode(true);
requestFocus();
setFocusableInTouchMode(false);
invalidate();
}
/**
* Unsets the focused task explicitly.
*/
void unsetFocusedTask() {
mIsFocused = false;
if (mFocusAnimationsEnabled) {
// Un-focus the header bar
mHeaderView.onTaskViewFocusChanged(false, true);
}
// Update the thumbnail alpha with the focus
mThumbnailView.onFocusChanged(false);
// Call the callback
if (mCb != null) {
mCb.onDeckChildViewFocusChanged(this, false);
}
invalidate();
}
/**
* Updates the explicitly focused state when the view focus changes.
*/
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (!gainFocus) {
unsetFocusedTask();
}
}
/**
* Returns whether we have explicitly been focused.
*/
public boolean isFocusedTask() {
return mIsFocused || isFocused();
}
/**
* Enables all focus animations.
*/
void enableFocusAnimations() {
boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
mFocusAnimationsEnabled = true;
if (mIsFocused && !wasFocusAnimationsEnabled) {
// Re-notify the header if we were focused and animations were not previously enabled
mHeaderView.onTaskViewFocusChanged(true, true);
}
}
/**** TaskCallbacks Implementation ****/
/**
* Binds this task view to the task
*/
public void onTaskBound(T key) {
mKey = key;
}
private boolean isBound() {
return mKey != null;
}
/**
* Binds this task view to the task
*/
public void onTaskUnbound() {
mKey = null;
}
public Bitmap getThumbnail() {
if (mThumbnailView != null) {
return mThumbnailView.getThumbnail();
}
return null;
}
public void onDataLoaded(T key, Bitmap thumbnail, Drawable headerIcon,
String headerTitle, int headerBgColor) {
if (!isBound() || !mKey.equals(key))
return;
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(thumbnail);
mHeaderView.rebindToTask(headerIcon, headerTitle, headerBgColor);
// Rebind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(this);
mHeaderView.mDismissButton.setOnClickListener(this);
// TODO: Check if this functionality is needed
mHeaderView.mApplicationIcon.setOnLongClickListener(this);
}
mTaskDataLoaded = true;
}
public void onDataUnloaded() {
if (mThumbnailView != null && mHeaderView != null) {
// Unbind each of the views from the task data and remove the task callback
mThumbnailView.unbindFromTask();
mHeaderView.unbindFromTask();
// Unbind any listeners
mHeaderView.mApplicationIcon.setOnClickListener(null);
mHeaderView.mDismissButton.setOnClickListener(null);
if (DVConstants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
mHeaderView.mApplicationIcon.setOnLongClickListener(null);
}
}
mTaskDataLoaded = false;
}
/**
* Enables/disables handling touch on this task view.
*/
public void setTouchEnabled(boolean enabled) {
setOnClickListener(enabled ? this : null);
}
/**
* * View.OnClickListener Implementation ***
*/
@Override
public void onClick(final View v) {
final DeckChildView tv = this;
final boolean delayViewClick = (v != this);
if (delayViewClick) {
// We purposely post the handler delayed to allow for the touch feedback to draw
postDelayed(new Runnable() {
@Override
public void run() {
if (DVConstants.DebugFlags.App.EnableTaskFiltering
&& v == mHeaderView.mApplicationIcon) {
if (mCb != null) {
mCb.onDeckChildViewAppIconClicked(tv);
}
} else if (v == mHeaderView.mDismissButton) {
dismissTask();
}
}
}, 125);
} else {
if (mCb != null) {
mCb.onDeckChildViewClicked(tv, tv.getAttachedKey());
}
}
}
/**
* * View.OnLongClickListener Implementation ***
*/
@Override
public boolean onLongClick(View v) {
if (v == mHeaderView.mApplicationIcon) {
if (mCb != null) {
mCb.onDeckChildViewAppInfoClicked(this);
return true;
}
}
return false;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckChildViewHeader.java
================================================
package com.appeaser.deckview.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.appeaser.deckview.R;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.utilities.DVConstants;
import com.appeaser.deckview.utilities.DVUtils;
/**
* Created by Vikram on 02/04/2015.
*/
/* The task bar view */
public class DeckChildViewHeader extends FrameLayout {
DeckViewConfig mConfig;
// Header views
ImageView mDismissButton;
ImageView mApplicationIcon;
TextView mActivityDescription;
// Header drawables
boolean mCurrentPrimaryColorIsDark;
int mCurrentPrimaryColor;
int mBackgroundColor;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
RippleDrawable mBackground;
GradientDrawable mBackgroundColorDrawable;
AnimatorSet mFocusAnimator;
String mDismissContentDescription;
// Static highlight that we draw at the top of each view
static Paint sHighlightPaint;
// Header dim, which is only used when task view hardware layers are not used
Paint mDimLayerPaint = new Paint();
PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
public DeckChildViewHeader(Context context) {
this(context, null);
}
public DeckChildViewHeader(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DeckChildViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DeckChildViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = DeckViewConfig.getInstance();
setWillNotDraw(false);
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
});
// Load the dismiss resources
Resources res = context.getResources();
mLightDismissDrawable = res.getDrawable(R.drawable.deck_child_view_dismiss_light);
mDarkDismissDrawable = res.getDrawable(R.drawable.deck_child_view_dismiss_dark);
mDismissContentDescription =
res.getString(R.string.accessibility_item_will_be_dismissed);
// Configure the highlight paint
if (sHighlightPaint == null) {
sHighlightPaint = new Paint();
sHighlightPaint.setStyle(Paint.Style.STROKE);
sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
sHighlightPaint.setAntiAlias(true);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// We ignore taps on the task bar except on the filter and dismiss buttons
if (!DVConstants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
return super.onTouchEvent(event);
}
@Override
protected void onFinishInflate() {
// Initialize the icon and description views
mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
mActivityDescription = (TextView) findViewById(R.id.activity_description);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
// Hide the backgrounds if they are ripple drawables
if (!DVConstants.DebugFlags.App.EnableTaskFiltering) {
if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
mApplicationIcon.setBackground(null);
}
}
mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
.deck_child_view_header_bg_color);
// Copy the ripple drawable since we are going to be manipulating it
mBackground = (RippleDrawable)
getContext().getDrawable(R.drawable.deck_child_view_header_bg);
mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
mBackground.setColor(ColorStateList.valueOf(0));
mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
setBackground(mBackground);
}
@Override
protected void onDraw(Canvas canvas) {
// Draw the highlight at the top edge (but put the bottom edge just out of view)
float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
float radius = mConfig.taskViewRoundedCornerRadiusPx;
int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
canvas.restoreToCount(count);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
/**
* Sets the dim alpha, only used when we are not using hardware layers.
* (see RecentsConfiguration.useHardwareLayers)
*/
void setDimAlpha(int alpha) {
mDimColorFilter = new PorterDuffColorFilter(Color.argb(alpha, 0, 0, 0),
PorterDuff.Mode.SRC_ATOP);
mDimLayerPaint.setColorFilter(mDimColorFilter);
setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
}
/**
* Returns the secondary color for a primary color.
*/
int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
return DVUtils.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
}
/**
* Binds the bar view to the task
*/
//public void rebindToTask(Task t) {
public void rebindToTask(Drawable headerIcon, String headerTitle, int headerBgColor) {
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
// otherwise, we fall back to the application icon
mApplicationIcon.setImageDrawable(headerIcon);
mApplicationIcon.setContentDescription(headerTitle);
mActivityDescription.setText(headerTitle);
// Try and apply the system ui tint
int existingBgColor = (getBackground() instanceof ColorDrawable) ?
((ColorDrawable) getBackground()).getColor() : 0;
if (existingBgColor != headerBgColor) {
mBackgroundColorDrawable.setColor(headerBgColor);
mBackgroundColor = headerBgColor;
}
mCurrentPrimaryColor = headerBgColor;
//mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
mActivityDescription.setTextColor(mConfig.taskBarViewLightTextColor);
mDismissButton.setImageDrawable(mLightDismissDrawable);
mDismissButton.setContentDescription(String.format(mDismissContentDescription,
headerTitle));
}
/**
* Unbinds the bar view from the task
*/
void unbindFromTask() {
mApplicationIcon.setImageDrawable(null);
}
/**
* Animates this task bar dismiss button when launching a task.
*/
void startLaunchTaskDismissAnimation() {
if (mDismissButton.getVisibility() == View.VISIBLE) {
mDismissButton.animate().cancel();
mDismissButton.animate()
.alpha(0f)
.setStartDelay(0)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.setDuration(mConfig.taskViewExitToAppDuration)
.withLayer()
.start();
}
}
/**
* Animates this task bar if the user does not interact with the stack after a certain time.
*/
void startNoUserInteractionAnimation() {
if (mDismissButton.getVisibility() != View.VISIBLE) {
mDismissButton.setVisibility(View.VISIBLE);
mDismissButton.setAlpha(0f);
mDismissButton.animate()
.alpha(1f)
.setStartDelay(0)
.setInterpolator(mConfig.fastOutLinearInInterpolator)
.setDuration(mConfig.taskViewEnterFromAppDuration)
.withLayer()
.start();
}
}
/**
* Mark this task view that the user does has not interacted with the stack after a certain time.
*/
void setNoUserInteractionState() {
if (mDismissButton.getVisibility() != View.VISIBLE) {
mDismissButton.animate().cancel();
mDismissButton.setVisibility(View.VISIBLE);
mDismissButton.setAlpha(1f);
}
}
/**
* Resets the state tracking that the user has not interacted with the stack after a certain time.
*/
void resetNoUserInteractionState() {
mDismissButton.setVisibility(View.INVISIBLE);
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
// Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
// This is to prevent layer trashing when the view is pressed.
return new int[]{};
}
/**
* Notifies the associated TaskView has been focused.
*/
void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
// If we are not animating the visible state, just return
if (!animateFocusedState) return;
boolean isRunning = false;
if (mFocusAnimator != null) {
isRunning = mFocusAnimator.isRunning();
DVUtils.cancelAnimationWithoutCallbacks(mFocusAnimator);
}
if (focused) {
int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
int[][] states = new int[][]{
new int[]{android.R.attr.state_enabled},
new int[]{android.R.attr.state_pressed}
};
int[] newStates = new int[]{
android.R.attr.state_enabled,
android.R.attr.state_pressed
};
int[] colors = new int[]{
secondaryColor,
secondaryColor
};
mBackground.setColor(new ColorStateList(states, colors));
mBackground.setState(newStates);
// Pulse the background color
int currentColor = mBackgroundColor;
int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
currentColor, lightPrimaryColor);
backgroundColor.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mBackground.setState(new int[]{});
}
});
backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int color = (int) animation.getAnimatedValue();
mBackgroundColorDrawable.setColor(color);
mBackgroundColor = color;
}
});
backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
// Pulse the translation
ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
translation.setRepeatCount(ValueAnimator.INFINITE);
translation.setRepeatMode(ValueAnimator.REVERSE);
mFocusAnimator = new AnimatorSet();
mFocusAnimator.playTogether(backgroundColor, translation);
mFocusAnimator.setStartDelay(750);
mFocusAnimator.setDuration(750);
mFocusAnimator.start();
} else {
if (isRunning) {
// Restore the background color
int currentColor = mBackgroundColor;
ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
currentColor, mCurrentPrimaryColor);
backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int color = (int) animation.getAnimatedValue();
mBackgroundColorDrawable.setColor(color);
mBackgroundColor = color;
}
});
// Restore the translation
ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
mFocusAnimator = new AnimatorSet();
mFocusAnimator.playTogether(backgroundColor, translation);
mFocusAnimator.setDuration(150);
mFocusAnimator.start();
} else {
mBackground.setState(new int[]{});
setTranslationZ(0f);
}
}
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckChildViewThumbnail.java
================================================
package com.appeaser.deckview.views;
/**
* Created by Vikram on 02/04/2015.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LightingColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.utilities.DVUtils;
/**
* The task thumbnail view. It implements an image view that allows for animating the dim and
* alpha of the thumbnail image.
*/
public class DeckChildViewThumbnail extends View {
DeckViewConfig mConfig;
// Drawing
float mDimAlpha;
Matrix mScaleMatrix = new Matrix();
Paint mDrawPaint = new Paint();
RectF mBitmapRect = new RectF();
RectF mLayoutRect = new RectF();
BitmapShader mBitmapShader;
LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
// Thumbnail alpha
float mThumbnailAlpha;
ValueAnimator mThumbnailAlphaAnimator;
ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mThumbnailAlpha = (float) animation.getAnimatedValue();
updateThumbnailPaintFilter();
}
};
// Task bar clipping, the top of this thumbnail can be clipped against the opaque header
// bar that overlaps this thumbnail
View mTaskBar;
Rect mClipRect = new Rect();
// Visibility optimization, if the thumbnail height is less than the height of the header
// bar for the task view, then just mark this thumbnail view as invisible
boolean mInvisible;
public DeckChildViewThumbnail(Context context) {
this(context, null);
}
public DeckChildViewThumbnail(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DeckChildViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DeckChildViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = DeckViewConfig.getInstance();
mDrawPaint.setColorFilter(mLightingColorFilter);
mDrawPaint.setFilterBitmap(true);
mDrawPaint.setAntiAlias(true);
}
@Override
protected void onFinishInflate() {
mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
updateThumbnailPaintFilter();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mLayoutRect.set(0, 0, getWidth(), getHeight());
updateThumbnailScale();
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mInvisible) {
return;
}
// Draw the thumbnail with the rounded corners
canvas.drawRoundRect(0, 0, getWidth(), getHeight(),
mConfig.taskViewRoundedCornerRadiusPx,
mConfig.taskViewRoundedCornerRadiusPx, mDrawPaint);
}
/**
* Sets the thumbnail to a given bitmap.
*/
void setThumbnail(Bitmap bm) {
mThumbnail = bm;
if (bm != null) {
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
updateThumbnailScale();
} else {
mBitmapShader = null;
mDrawPaint.setShader(null);
}
updateThumbnailPaintFilter();
}
/**
* Updates the paint to draw the thumbnail.
*/
void updateThumbnailPaintFilter() {
if (mInvisible) {
return;
}
int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
if (mBitmapShader != null) {
mLightingColorFilter =
new LightingColorFilter(Color.argb(255, mul, mul, mul),
Color.argb(0, add, add, add));
mDrawPaint.setColorFilter(mLightingColorFilter);
mDrawPaint.setColor(0xffffffff);
} else {
int grey = mul + add;
mDrawPaint.setColorFilter(null);
mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
}
invalidate();
}
/**
* Updates the thumbnail shader's scale transform.
*/
void updateThumbnailScale() {
if (mBitmapShader != null) {
mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);
mBitmapShader.setLocalMatrix(mScaleMatrix);
}
}
/**
* Updates the clip rect based on the given task bar.
*/
void updateClipToTaskBar(View taskBar) {
mTaskBar = taskBar;
int top = (int) Math.max(0, taskBar.getTranslationY() +
taskBar.getMeasuredHeight() - 1);
mClipRect.set(0, top, getMeasuredWidth(), getMeasuredHeight());
setClipBounds(mClipRect);
}
/**
* Updates the visibility of the the thumbnail.
*/
void updateThumbnailVisibility(int clipBottom) {
boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
if (invisible != mInvisible) {
mInvisible = invisible;
if (!mInvisible) {
updateThumbnailPaintFilter();
}
invalidate();
}
}
/**
* Sets the dim alpha, only used when we are not using hardware layers.
* (see RecentsConfiguration.useHardwareLayers)
*/
public void setDimAlpha(float dimAlpha) {
mDimAlpha = dimAlpha;
updateThumbnailPaintFilter();
}
/**
* Binds the thumbnail view to the task
*/
//void rebindToTask(Task t) {
void rebindToTask(Bitmap thumbnail) {
if (thumbnail != null) {
setThumbnail(thumbnail);
} else {
setThumbnail(null);
}
}
/**
* Unbinds the thumbnail view from the task
*/
void unbindFromTask() {
setThumbnail(null);
}
Bitmap mThumbnail;
public Bitmap getThumbnail() {
return mThumbnail;
}
/**
* Handles focus changes.
*/
void onFocusChanged(boolean focused) {
if (focused) {
if (Float.compare(getAlpha(), 1f) != 0) {
startFadeAnimation(1f, 0, 150, null);
}
} else {
if (Float.compare(getAlpha(), mConfig.taskViewThumbnailAlpha) != 0) {
startFadeAnimation(mConfig.taskViewThumbnailAlpha, 0, 150, null);
}
}
}
/**
* Prepares for the enter recents animation, this gets called before the the view
* is first visible and will be followed by a startEnterRecentsAnimation() call.
*/
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
if (isTaskViewLaunchTargetTask) {
mThumbnailAlpha = 1f;
} else {
mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
}
updateThumbnailPaintFilter();
}
/**
* Animates this task thumbnail as it enters Recents.
*/
void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
startFadeAnimation(mConfig.taskViewThumbnailAlpha, delay,
mConfig.taskViewEnterFromAppDuration, postAnimRunnable);
}
/**
* Animates this task thumbnail as it exits Recents.
*/
void startLaunchTaskAnimation(Runnable postAnimRunnable) {
startFadeAnimation(1f, 0, mConfig.taskViewExitToAppDuration, postAnimRunnable);
}
/**
* Starts a new thumbnail alpha animation.
*/
void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
DVUtils.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
mThumbnailAlphaAnimator.setStartDelay(delay);
mThumbnailAlphaAnimator.setDuration(duration);
mThumbnailAlphaAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
if (postAnimRunnable != null) {
mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
postAnimRunnable.run();
}
});
}
mThumbnailAlphaAnimator.start();
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckView.java
================================================
package com.appeaser.deckview.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import com.appeaser.deckview.R;
import com.appeaser.deckview.helpers.DeckChildViewTransform;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.utilities.DVConstants;
import com.appeaser.deckview.utilities.DVUtils;
import com.appeaser.deckview.utilities.DozeTrigger;
import com.appeaser.deckview.utilities.ReferenceCountedTrigger;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* Created by Vikram on 02/04/2015.
*/
/* The visual representation of a task stack view */
public class DeckView extends FrameLayout implements /*TaskStack.TaskStackCallbacks,*/
DeckChildView.DeckChildViewCallbacks, DeckViewScroller.DeckViewScrollerCallbacks,
ViewPool.ViewPoolConsumer, T> {
DeckViewConfig mConfig;
DeckViewLayoutAlgorithm mLayoutAlgorithm;
DeckViewScroller mStackScroller;
DeckViewTouchHandler mTouchHandler;
ViewPool, T> mViewPool;
ArrayList mCurrentTaskTransforms = new ArrayList();
DozeTrigger mUIDozeTrigger;
Rect mTaskStackBounds = new Rect();
int mFocusedTaskIndex = -1;
int mPrevAccessibilityFocusedIndex = -1;
// Optimizations
int mStackViewsAnimationDuration;
boolean mStackViewsDirty = true;
boolean mStackViewsClipDirty = true;
boolean mAwaitingFirstLayout = true;
boolean mStartEnterAnimationRequestedAfterLayout;
boolean mStartEnterAnimationCompleted;
ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
int[] mTmpVisibleRange = new int[2];
float[] mTmpCoord = new float[2];
Matrix mTmpMatrix = new Matrix();
Rect mTmpRect = new Rect();
DeckChildViewTransform mTmpTransform = new DeckChildViewTransform();
HashMap mTmpTaskViewMap = new HashMap();
LayoutInflater mInflater;
// A convenience update listener to request updating clipping of tasks
ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
requestUpdateStackViewsClip();
}
};
public DeckView(Context context) {
this(context, null);
}
public DeckView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DeckView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DeckView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
DeckViewConfig.reinitialize(getContext());
mConfig = DeckViewConfig.getInstance();
}
public void initialize(Callback callback) {
mCallback = callback;
requestLayout();
mViewPool = new ViewPool, T>(getContext(), this);
mInflater = LayoutInflater.from(getContext());
mLayoutAlgorithm = new DeckViewLayoutAlgorithm(mConfig);
mStackScroller = new DeckViewScroller(getContext(), mConfig, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
mTouchHandler = new DeckViewTouchHandler(getContext(), this, mConfig, mStackScroller);
mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
@Override
public void run() {
// Show the task bar dismiss buttons
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
DeckChildView tv = (DeckChildView) getChildAt(i);
tv.startNoUserInteractionAnimation();
}
}
});
}
/**
* Resets this TaskStackView for reuse.
*/
void reset() {
// Reset the focused task
resetFocusedTask();
// Return all the views to the pool
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
DeckChildView tv = (DeckChildView) getChildAt(i);
mViewPool.returnViewToPool(tv);
}
// Mark each task view for relayout
if (mViewPool != null) {
Iterator> iter = mViewPool.poolViewIterator();
if (iter != null) {
while (iter.hasNext()) {
DeckChildView tv = iter.next();
tv.reset();
}
}
}
// Reset the stack state
mStackViewsDirty = true;
mStackViewsClipDirty = true;
mAwaitingFirstLayout = true;
mPrevAccessibilityFocusedIndex = -1;
if (mUIDozeTrigger != null) {
mUIDozeTrigger.stopDozing();
mUIDozeTrigger.resetTrigger();
}
mStackScroller.reset();
}
/**
* Requests that the views be synchronized with the model
*/
void requestSynchronizeStackViewsWithModel() {
requestSynchronizeStackViewsWithModel(0);
}
void requestSynchronizeStackViewsWithModel(int duration) {
if (!mStackViewsDirty) {
invalidate();
mStackViewsDirty = true;
}
if (mAwaitingFirstLayout) {
// Skip the animation if we are awaiting first layout
mStackViewsAnimationDuration = 0;
} else {
mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
}
}
/**
* Requests that the views clipping be updated.
*/
void requestUpdateStackViewsClip() {
if (!mStackViewsClipDirty) {
invalidate();
mStackViewsClipDirty = true;
}
}
/**
* Finds the child view given a specific task.
*/
public DeckChildView getChildViewForTask(T key) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
DeckChildView tv = (DeckChildView) getChildAt(i);
if (tv.getAttachedKey().equals(key)) {
return tv;
}
}
return null;
}
/**
* Returns the stack algorithm for this task stack.
*/
public DeckViewLayoutAlgorithm getStackAlgorithm() {
return mLayoutAlgorithm;
}
/**
* Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
*/
private boolean updateStackTransforms(ArrayList taskTransforms,
ArrayList data,
float stackScroll,
int[] visibleRangeOut,
boolean boundTranslationsToRect) {
int taskTransformCount = taskTransforms.size();
int taskCount = data.size();
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
// We can reuse the task transforms where possible to reduce object allocation
if (taskTransformCount < taskCount) {
// If there are less transforms than tasks, then add as many transforms as necessary
for (int i = taskTransformCount; i < taskCount; i++) {
taskTransforms.add(new DeckChildViewTransform());
}
} else if (taskTransformCount > taskCount) {
// If there are more transforms than tasks, then just subset the transform list
taskTransforms.subList(0, taskCount);
}
// Update the stack transforms
DeckChildViewTransform prevTransform = null;
for (int i = taskCount - 1; i >= 0; i--) {
DeckChildViewTransform transform =
mLayoutAlgorithm.getStackTransform(data.get(i),
stackScroll, taskTransforms.get(i), prevTransform);
if (transform.visible) {
if (frontMostVisibleIndex < 0) {
frontMostVisibleIndex = i;
}
backMostVisibleIndex = i;
} else {
if (backMostVisibleIndex != -1) {
// We've reached the end of the visible range, so going down the rest of the
// stack, we can just reset the transforms accordingly
while (i >= 0) {
taskTransforms.get(i).reset();
i--;
}
break;
}
}
if (boundTranslationsToRect) {
transform.translationY = Math.min(transform.translationY,
mLayoutAlgorithm.mViewRect.bottom);
}
prevTransform = transform;
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
visibleRangeOut[1] = backMostVisibleIndex;
}
return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
}
/**
* Synchronizes the views with the model
*/
boolean synchronizeStackViewsWithModel() {
if (mStackViewsDirty) {
// Get all the task transforms
ArrayList data = mCallback.getData();
float stackScroll = mStackScroller.getStackScroll();
int[] visibleRange = mTmpVisibleRange;
boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms,
data, stackScroll, visibleRange, false);
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
DeckChildView tv = (DeckChildView) getChildAt(i);
T key = tv.getAttachedKey();
int taskIndex = data.indexOf(key);
if (visibleRange[1] <= taskIndex
&& taskIndex <= visibleRange[0]) {
mTmpTaskViewMap.put(key, tv);
} else {
mViewPool.returnViewToPool(tv);
}
}
for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
T key = data.get(i);
DeckChildViewTransform transform = mCurrentTaskTransforms.get(i);
DeckChildView tv = mTmpTaskViewMap.get(key);
if (tv == null) {
// TODO Check
tv = mViewPool.pickUpViewFromPool(key, key);
if (mStackViewsAnimationDuration > 0) {
// For items in the list, put them in start animating them from the
// approriate ends of the list where they are expected to appear
if (Float.compare(transform.p, 0f) <= 0) {
mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
} else {
mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
}
tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
}
// Animate the task into place
tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(i),
mStackViewsAnimationDuration, mRequestUpdateClippingListener);
}
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
mStackViewsClipDirty = true;
return true;
}
return false;
}
/**
* Updates the clip for each of the task views.
*/
void clipTaskViews() {
// Update the clip on each task child
if (DVConstants.DebugFlags.App.EnableTaskStackClipping) {
int childCount = getChildCount();
for (int i = 0; i < childCount - 1; i++) {
DeckChildView tv = (DeckChildView) getChildAt(i);
DeckChildView nextTv = null;
DeckChildView tmpTv = null;
int clipBottom = 0;
if (tv.shouldClipViewInStack()) {
// Find the next view to clip against
int nextIndex = i;
while (nextIndex < getChildCount()) {
tmpTv = (DeckChildView) getChildAt(++nextIndex);
if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
nextTv = tmpTv;
break;
}
}
// Clip against the next view, this is just an approximation since we are
// stacked and we can make assumptions about the visibility of the this
// task relative to the ones in front of it.
if (nextTv != null) {
// Map the top edge of next task view into the local space of the current
// task view to find the clip amount in local space
mTmpCoord[0] = mTmpCoord[1] = 0;
DVUtils.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
DVUtils.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
- nextTv.getPaddingTop() - 1);
}
}
tv.getViewBounds().setClipBottom(clipBottom);
}
if (getChildCount() > 0) {
// The front most task should never be clipped
DeckChildView tv = (DeckChildView) getChildAt(getChildCount() - 1);
tv.getViewBounds().setClipBottom(0);
}
}
mStackViewsClipDirty = false;
}
/**
* The stack insets to apply to the stack contents
*/
public void setStackInsetRect(Rect r) {
mTaskStackBounds.set(r);
}
/**
* Updates the min and max virtual scroll bounds
*/
void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
boolean launchedFromHome) {
// Compute the min and max scroll values
mLayoutAlgorithm.computeMinMaxScroll(mCallback.getData(), launchedWithAltTab, launchedFromHome);
// Debug logging
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
}
}
/**
* Returns the scroller.
*/
public DeckViewScroller getScroller() {
return mStackScroller;
}
/**
* Focuses the task at the specified index in the stack
*/
void focusTask(int childIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
// Return early if the task is already focused
if (childIndex == mFocusedTaskIndex) return;
ArrayList data = mCallback.getData();
if (0 <= childIndex && childIndex < data.size()) {
mFocusedTaskIndex = childIndex;
// Focus the view if possible, otherwise, focus the view after we scroll into position
T key = data.get(childIndex);
DeckChildView tv = getChildViewForTask(key);
Runnable postScrollRunnable = null;
if (tv != null) {
tv.setFocusedTask(animateFocusedState);
} else {
postScrollRunnable = new Runnable() {
@Override
public void run() {
DeckChildView tv = getChildViewForTask(mCallback.getData().get(mFocusedTaskIndex));
if (tv != null) {
tv.setFocusedTask(animateFocusedState);
}
}
};
}
// Scroll the view into position (just center it in the curve)
if (scrollToNewPosition) {
float newScroll = mLayoutAlgorithm.getStackScrollForTask(key) - 0.5f;
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
} else {
if (postScrollRunnable != null) {
postScrollRunnable.run();
}
}
}
}
/**
* Ensures that there is a task focused, if nothing is focused, then we will use the task
* at the center of the visible stack.
*/
public boolean ensureFocusedTask() {
if (mFocusedTaskIndex < 0) {
// If there is no task focused, then find the task that is closes to the center
// of the screen and use that as the currently focused task
int x = mLayoutAlgorithm.mStackVisibleRect.centerX();
int y = mLayoutAlgorithm.mStackVisibleRect.centerY();
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
DeckChildView tv = (DeckChildView) getChildAt(i);
tv.getHitRect(mTmpRect);
if (mTmpRect.contains(x, y)) {
mFocusedTaskIndex = i;
break;
}
}
// If we can't find the center task, then use the front most index
if (mFocusedTaskIndex < 0 && childCount > 0) {
mFocusedTaskIndex = childCount - 1;
}
}
return mFocusedTaskIndex >= 0;
}
/**
* Focuses the next task in the stack.
*
* @param animateFocusedState determines whether to actually draw the highlight along with
* the change in focus, as well as whether to scroll to fit the
* task into view.
*/
public void focusNextTask(boolean forward, boolean animateFocusedState) {
// Find the next index to focus
int numTasks = mCallback.getData().size();
if (numTasks == 0) return;
int direction = (forward ? -1 : 1);
int newIndex = mFocusedTaskIndex + direction;
if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
focusTask(newIndex, true, animateFocusedState);
}
}
/**
* Dismisses the focused task.
*/
public void dismissFocusedTask() {
// Return early if the focused task index is invalid
if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mCallback.getData().size()) {
mFocusedTaskIndex = -1;
return;
}
//Long id = mAdapter.getItemId(mFocusedTaskIndex);
T key = mCallback.getData().get(mFocusedTaskIndex);
DeckChildView tv = getChildViewForTask(key);
tv.dismissTask();
}
/**
* Resets the focused task.
*/
void resetFocusedTask() {
if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mCallback.getData().size())) {
DeckChildView tv = getChildViewForTask(mCallback.getData().get(mFocusedTaskIndex));
if (tv != null) {
tv.unsetFocusedTask();
}
}
mFocusedTaskIndex = -1;
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
int childCount = getChildCount();
if (childCount > 0) {
DeckChildView backMostTask = (DeckChildView) getChildAt(0);
DeckChildView frontMostTask = (DeckChildView) getChildAt(childCount - 1);
event.setFromIndex(mCallback.getData().indexOf(backMostTask.getAttachedKey()));
event.setToIndex(mCallback.getData().indexOf(frontMostTask.getAttachedKey()));
}
event.setItemCount(mCallback.getData().size());
event.setScrollY(mStackScroller.mScroller.getCurrY());
event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mTouchHandler.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return mTouchHandler.onTouchEvent(ev);
}
@Override
public boolean onGenericMotionEvent(MotionEvent ev) {
return mTouchHandler.onGenericMotionEvent(ev);
}
@Override
public void computeScroll() {
mStackScroller.computeScroll();
// Synchronize the views
synchronizeStackViewsWithModel();
clipTaskViews();
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
/**
* Computes the stack and task rects
*/
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
boolean launchedWithAltTab, boolean launchedFromHome) {
// Compute the rects in the stack algorithm
mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
}
public int getCurrentChildIndex() {
if (getChildCount() == 0)
return -1;
DeckChildView frontMostChild = (DeckChildView) getChildAt(getChildCount() / 2);
if (frontMostChild != null) {
return mCallback.getData().indexOf(frontMostChild.getAttachedKey());
}
return -1;
}
/**
* Focuses the task at the specified index in the stack
*/
public void scrollToChild(int childIndex) {
if (getCurrentChildIndex() == childIndex)
return;
if (0 <= childIndex && childIndex < mCallback.getData().size()) {
// Scroll the view into position (just center it in the curve)
float newScroll = mLayoutAlgorithm.getStackScrollForTask(
mCallback.getData().get(childIndex)) - 0.5f;
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
mStackScroller.setStackScroll(newScroll);
//Alternate (animated) way
//mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, null);
}
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateMinMaxScrollForStack() is called first.
*/
public DeckViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
return mLayoutAlgorithm.computeStackVisibilityReport(mCallback.getData());
}
/**
* This is called with the full window width and height to allow stack view children to
* perform the full screen transition down.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
Rect _taskStackBounds = new Rect();
mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top,
mConfig.systemInsets.right, _taskStackBounds);
setStackInsetRect(_taskStackBounds);
// Compute our stack/task rects
Rect taskStackBounds = new Rect(mTaskStackBounds);
taskStackBounds.bottom -= mConfig.systemInsets.bottom;
computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
mConfig.launchedFromHome);
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately to load all the views
if (mAwaitingFirstLayout) {
mStackScroller.setStackScrollToInitialState();
requestSynchronizeStackViewsWithModel();
synchronizeStackViewsWithModel();
}
// Measure each of the TaskViews
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
DeckChildView tv = (DeckChildView) getChildAt(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
mTmpRect.setEmpty();
}
tv.measure(
MeasureSpec.makeMeasureSpec(
mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(
mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
}
/**
* This is called with the size of the space not including the top or right insets, or the
* search bar height in portrait (but including the search bar width in landscape, since we want
* to draw under it.
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Layout each of the children
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
DeckChildView tv = (DeckChildView) getChildAt(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
mTmpRect.setEmpty();
}
tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
}
if (mAwaitingFirstLayout) {
mAwaitingFirstLayout = false;
onFirstLayout();
}
}
/**
* Handler for the first layout.
*/
void onFirstLayout() {
int offscreenY = mLayoutAlgorithm.mViewRect.bottom -
(mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
int childCount = getChildCount();
// Prepare the first view for its enter animation
for (int i = childCount - 1; i >= 0; i--) {
DeckChildView tv = (DeckChildView) getChildAt(i);
// TODO: The false needs to go!
tv.prepareEnterRecentsAnimation(i == childCount - 1, false, offscreenY);
}
// If the enter animation started already and we haven't completed a layout yet, do the
// enter animation now
if (mStartEnterAnimationRequestedAfterLayout) {
startEnterRecentsAnimation(mStartEnterAnimationContext);
mStartEnterAnimationRequestedAfterLayout = false;
mStartEnterAnimationContext = null;
}
// When Alt-Tabbing, focus the previous task (but leave the animation until we finish the
// enter animation).
if (mConfig.launchedWithAltTab) {
if (mConfig.launchedFromAppWithThumbnail) {
focusTask(Math.max(0, mCallback.getData().size() - 2), false,
mConfig.launchedHasConfigurationChanged);
} else {
focusTask(Math.max(0, mCallback.getData().size() - 1), false,
mConfig.launchedHasConfigurationChanged);
}
}
// Start dozing
mUIDozeTrigger.startDozing();
}
void showDeck(Context context) {
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
// We have to increment/decrement the post animation trigger in case there are no children
// to ensure that it runs
ctx.postAnimationTrigger.increment();
startEnterRecentsAnimation(ctx);
ctx.postAnimationTrigger.decrement();
}
/**
* Requests this task stacks to start it's enter-recents animation
*/
public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
// If we are still waiting to layout, then just defer until then
if (mAwaitingFirstLayout) {
mStartEnterAnimationRequestedAfterLayout = true;
mStartEnterAnimationContext = ctx;
return;
}
if (mCallback.getData().size() > 0) {
int childCount = getChildCount();
// Animate all the task views into view
for (int i = childCount - 1; i >= 0; i--) {
DeckChildView tv = (DeckChildView) getChildAt(i);
T key = tv.getAttachedKey();
ctx.currentTaskTransform = new DeckChildViewTransform();
ctx.currentStackViewIndex = i;
ctx.currentStackViewCount = childCount;
ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
// TODO: this needs to go
ctx.currentTaskOccludesLaunchTarget = false;
ctx.updateListener = mRequestUpdateClippingListener;
mLayoutAlgorithm.getStackTransform(key, mStackScroller.getStackScroll(),
ctx.currentTaskTransform, null);
tv.startEnterRecentsAnimation(ctx);
}
// Add a runnable to the post animation ref counter to clear all the views
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
mStartEnterAnimationCompleted = true;
// Poke the dozer to restart the trigger after the animation completes
mUIDozeTrigger.poke();
}
});
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
// Update the configuration with the latest system insets and trigger a relayout
// mConfig.updateSystemInsets(insets.getSystemWindowInsets());
mConfig.updateSystemInsets(new Rect(insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom()));
requestLayout();
return insets.consumeSystemWindowInsets();
}
void hideDeck(Context context, Runnable finishRunnable) {
ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
null, finishRunnable, null);
ViewAnimation.TaskViewExitContext exitCtx =
new ViewAnimation.TaskViewExitContext(exitTrigger);
exitCtx.postAnimationTrigger.increment();
startExitToHomeAnimation(
new ViewAnimation.TaskViewExitContext(exitTrigger));
exitCtx.postAnimationTrigger.decrement();
}
/**
* Requests this task stacks to start it's exit-recents animation.
*/
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
// Stop any scrolling
mStackScroller.stopScroller();
mStackScroller.stopBoundScrollAnimation();
// Animate all the task views out of view
ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -
(mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
DeckChildView tv = (DeckChildView) getChildAt(i);
tv.startExitToHomeAnimation(ctx);
}
}
/**
* Animates a task view in this stack as it launches.
*/
public void startLaunchTaskAnimation(DeckChildView tv, Runnable r, boolean lockToTask) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
DeckChildView t = (DeckChildView) getChildAt(i);
if (t == tv) {
t.setClipViewInStack(false);
t.startLaunchTaskAnimation(r, true, true, lockToTask);
} else {
// TODO: the false needs to go
t.startLaunchTaskAnimation(null, false, false, lockToTask);
}
}
}
/**
* Final callback after Recents is finally hidden.
*/
void onRecentsHidden() {
reset();
}
public boolean isTransformedTouchPointInView(float x, float y, View child) {
// TODO: confirm if this is the right approach
if (child == null)
return false;
final Rect frame = new Rect();
child.getHitRect(frame);
return frame.contains((int) x, (int) y);
}
/**
* Pokes the dozer on user interaction.
*/
void onUserInteraction() {
// Poke the doze trigger if it is dozing
mUIDozeTrigger.poke();
}
/**
* * ViewPoolConsumer Implementation ***
*/
@Override
public DeckChildView createView(Context context) {
return (DeckChildView) mInflater.inflate(R.layout.deck_child_view, this, false);
}
@Override
public void prepareViewToEnterPool(DeckChildView tv) {
T key = tv.getAttachedKey();
mCallback.unloadViewData(key);
tv.onTaskUnbound();
tv.onDataUnloaded();
// Detach the view from the hierarchy
detachViewFromParent(tv);
// Reset the view properties
tv.resetViewProperties();
// Reset the clip state of the task view
tv.setClipViewInStack(false);
}
@Override
public void prepareViewToLeavePool(DeckChildView dcv, T key, boolean isNewView) {
// It is possible for a view to be returned to the view pool before it is laid out,
// which means that we will need to relayout the view when it is first used next.
boolean requiresRelayout = dcv.getWidth() <= 0 && !isNewView;
// Rebind the task and request that this task's data be filled into the TaskView
dcv.onTaskBound(key);
// Load the task data
mCallback.loadViewData(new WeakReference>(dcv), key);
// If the doze trigger has already fired, then update the state for this task view
if (mUIDozeTrigger.hasTriggered()) {
dcv.setNoUserInteractionState();
}
// If we've finished the start animation, then ensure we always enable the focus animations
if (mStartEnterAnimationCompleted) {
dcv.enableFocusAnimations();
}
// Find the index where this task should be placed in the stack
int insertIndex = -1;
int position = mCallback.getData().indexOf(key);
if (position != -1) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
T otherKey = ((DeckChildView) getChildAt(i)).getAttachedKey();
int pos = mCallback.getData().indexOf(otherKey);
if (position < pos) {
insertIndex = i;
break;
}
}
}
// Add/attach the view to the hierarchy
if (isNewView) {
addView(dcv, insertIndex);
} else {
attachViewToParent(dcv, insertIndex, dcv.getLayoutParams());
if (requiresRelayout) {
dcv.requestLayout();
}
}
// Set the new state for this view, including the callbacks and view clipping
dcv.setCallbacks(this);
dcv.setTouchEnabled(true);
dcv.setClipViewInStack(true);
}
@Override
public boolean hasPreferredData(DeckChildView tv, T preferredData) {
return (tv.getAttachedKey() != null && tv.getAttachedKey().equals(preferredData));
}
/**
* * DeckChildCallbacks Implementation ***
*/
@Override
public void onDeckChildViewAppIconClicked(DeckChildView tv) {
//
}
@Override
public void onDeckChildViewAppInfoClicked(DeckChildView tv) {
//
}
@Override
public void onDeckChildViewClicked(DeckChildView dcv, T key) {
// Cancel any doze triggers
mUIDozeTrigger.stopDozing();
mCallback.onItemClick(key);
}
@Override
public void onDeckChildViewDismissed(DeckChildView dcv) {
boolean taskWasFocused = dcv.isFocusedTask();
T key = dcv.getAttachedKey();
int taskIndex = mCallback.getData().indexOf(key);
onStackTaskRemoved(dcv);
// If the dismissed task was focused, then we should focus the new task in the same index
if (taskIndex != -1 && taskWasFocused) {
int nextTaskIndex = Math.min(mCallback.getData().size() - 1, taskIndex - 1);
if (nextTaskIndex >= 0) {
DeckChildView nextTv = getChildViewForTask(mCallback.getData().get(nextTaskIndex));
if (nextTv != null) {
// Focus the next task, and only animate the visible state if we are launched
// from Alt-Tab
nextTv.setFocusedTask(mConfig.launchedWithAltTab);
}
}
}
}
public void onStackTaskRemoved(DeckChildView removedView) {
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
if (removedView != null) {
T key = removedView.getAttachedKey();
int removedPosition = mCallback.getData().indexOf(key);
mViewPool.returnViewToPool(removedView);
// Notify the callback that we've removed the task and it can clean up after it
mCallback.onViewDismissed(key);
}
/*
// Get the stack scroll of the task to anchor to (since we are removing something, the front
// most task will be our anchor task)
T anchorTask = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = mCallback.getData().size() > 0;
if (pullStackForward) {
anchorTask = mCallback.getData().get(mCallback.getData().size() - 1);
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
// Offset the stack by as much as the anchor task would otherwise move back
if (pullStackForward) {
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- prevAnchorTaskScroll));
mStackScroller.boundScroll();
}
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
T newFrontMostTask = mCallback.getData().get(mCallback.getData().size() - 1);
// Update the new front most task
if (newFrontMostTask != null) {
DeckChildView frontTv = getChildViewForTask(newFrontMostTask);
if (frontTv != null) {
frontTv.onTaskBound(newFrontMostTask);
}
}
// If there are no remaining tasks
if (mCallback.getData().size() == 0) {
mCallback.onNoViewsToDeck();
}
*/
}
public void notifyDataSetChanged() {
// Get the stack scroll of the task to anchor to (since we are removing something, the front
// most task will be our anchor task)
T anchorTask = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = mCallback.getData().size() > 0;
if (pullStackForward) {
anchorTask = mCallback.getData().get(mCallback.getData().size() - 1);
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
// Offset the stack by as much as the anchor task would otherwise move back
if (pullStackForward) {
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- prevAnchorTaskScroll));
mStackScroller.boundScroll();
}
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
T newFrontMostTask = mCallback.getData().size() > 0 ?
mCallback.getData().get(mCallback.getData().size() - 1)
: null;
// Update the new front most task
if (newFrontMostTask != null) {
DeckChildView frontTv = getChildViewForTask(newFrontMostTask);
if (frontTv != null) {
frontTv.onTaskBound(newFrontMostTask);
}
}
// If there are no remaining tasks
if (mCallback.getData().size() == 0) {
mCallback.onNoViewsToDeck();
}
}
@Override
public void onDeckChildViewClipStateChanged(DeckChildView tv) {
if (!mStackViewsDirty) {
invalidate();
}
}
@Override
public void onDeckChildViewFocusChanged(DeckChildView tv, boolean focused) {
if (focused) {
mFocusedTaskIndex = mCallback.getData().indexOf(tv.getAttachedKey());
}
}
/**
* * TaskStackViewScroller.TaskStackViewScrollerCallbacks ***
*/
@Override
public void onScrollChanged(float p) {
mUIDozeTrigger.poke();
requestSynchronizeStackViewsWithModel();
postInvalidateOnAnimation();
}
public void notifyDataSetChangedOld() {
ArrayList data = mCallback.getData();
// Get the stack scroll of the task to anchor to (since we are removing something, the front
// most task will be our anchor task)
T anchorTask = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = data.size() > 0;
if (pullStackForward) {
anchorTask = data.get(data.size() - 1);
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
// Offset the stack by as much as the anchor task would otherwise move back
if (pullStackForward) {
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- prevAnchorTaskScroll));
mStackScroller.boundScroll();
}
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
T newFrontMostTask = data.get(data.size() - 1);
// Update the new front most task
if (newFrontMostTask != null) {
DeckChildView frontTv = getChildViewForTask(newFrontMostTask);
if (frontTv != null) {
frontTv.onTaskBound(newFrontMostTask);
}
}
// If there are no remaining tasks
if (mCallback.getData().size() == 0)
mCallback.onNoViewsToDeck();
}
Callback mCallback;
public interface Callback {
public ArrayList getData();
public void loadViewData(WeakReference> dcv, T item);
public void unloadViewData(T item);
public void onViewDismissed(T item);
public void onItemClick(T item);
public void onNoViewsToDeck();
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckViewLayoutAlgorithm.java
================================================
package com.appeaser.deckview.views;
import android.graphics.Rect;
import com.appeaser.deckview.helpers.DeckChildViewTransform;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.utilities.DVUtils;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Created by Vikram on 02/04/2015.
*/
/* The layout logic for a TaskStackView.
*
* We are using a curve that defines the curve of the tasks as that go back in the recents list.
* The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the
* stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.
*/
public class DeckViewLayoutAlgorithm {
// These are all going to change
static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
// A report of the visibility state of the stack
public class VisibilityReport {
public int numVisibleTasks;
public int numVisibleThumbnails;
/**
* Package level ctor
*/
VisibilityReport(int tasks, int thumbnails) {
numVisibleTasks = tasks;
numVisibleThumbnails = thumbnails;
}
}
DeckViewConfig mConfig;
// The various rects that define the stack view
public Rect mViewRect = new Rect();
Rect mStackVisibleRect = new Rect();
Rect mStackRect = new Rect();
Rect mTaskRect = new Rect();
// The min/max scroll progress
float mMinScrollP;
float mMaxScrollP;
float mInitialScrollP;
int mWithinAffiliationOffset;
int mBetweenAffiliationOffset;
HashMap mTaskProgressMap = new HashMap();
// Log function
static final float XScale = 1.75f; // The large the XScale, the longer the flat area of the curve
static final float LogBase = 3000;
static final int PrecisionSteps = 250;
static float[] xp;
static float[] px;
public DeckViewLayoutAlgorithm(DeckViewConfig config) {
mConfig = config;
// Precompute the path
initializeCurve();
}
/**
* Computes the stack and task rects
*/
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
// Compute the stack rects
mViewRect.set(0, 0, windowWidth, windowHeight);
mStackRect.set(taskStackBounds);
mStackVisibleRect.set(taskStackBounds);
mStackVisibleRect.bottom = mViewRect.bottom;
int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
int heightPadding = mConfig.taskStackTopPaddingPx;
mStackRect.inset(widthPadding, heightPadding);
// Compute the task rect
int size = mStackRect.width();
int left = mStackRect.left + (mStackRect.width() - size) / 2;
mTaskRect.set(left, mStackRect.top,
left + size, mStackRect.top + size);
// Update the affiliation offsets
float visibleTaskPct = 0.5f;
mWithinAffiliationOffset = mConfig.taskBarHeight;
mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());
}
/**
* Computes the minimum and maximum scroll progress values. This method may be called before
* the RecentsConfiguration is set, so we need to pass in the alt-tab state.
*/
void computeMinMaxScroll(ArrayList data, boolean launchedWithAltTab,
boolean launchedFromHome) {
// Clear the progress map
mTaskProgressMap.clear();
// Return early if we have no tasks
if (data.isEmpty()) {
mMinScrollP = mMaxScrollP = 0;
return;
}
// Note that we should account for the scale difference of the offsets at the screen bottom
int taskHeight = mTaskRect.height();
float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
mWithinAffiliationOffset);
float scale = curveProgressToScale(pWithinAffiliateTop);
int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
mWithinAffiliationOffset + scaleYOffset);
float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
float pBetweenAffiliateOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
float pTaskHeightOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
float pNavBarOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
mStackRect.bottom));
// Update the task offsets
float pAtBackMostCardTop = 0.5f;
float pAtFrontMostCardTop = pAtBackMostCardTop;
int taskCount = data.size();
for (int i = 0; i < taskCount; i++) {
//Task task = tasks.get(i);
//mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
mTaskProgressMap.put(data.get(i), pAtFrontMostCardTop);
if (i < (taskCount - 1)) {
// Increment the peek height
// TODO: Might need adjustments
//float pPeek = task.group.isFrontMostTask(task) ?
//pBetweenAffiliateOffset : pWithinAffiliateOffset;
float pPeek = pBetweenAffiliateOffset;
pAtFrontMostCardTop += pPeek;
}
}
mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
mMinScrollP = data.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
if (launchedWithAltTab && launchedFromHome) {
// Center the top most task, since that will be focused first
mInitialScrollP = mMaxScrollP;
} else {
mInitialScrollP = pAtFrontMostCardTop - 0.825f;
}
mInitialScrollP = Math.min(mMaxScrollP, Math.max(0, mInitialScrollP));
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* computeMinMaxScroll() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList data) {
if (data.size() <= 1) {
return new VisibilityReport(1, 1);
}
// Walk backwards in the task stack and count the number of tasks and visible thumbnails
int taskHeight = mTaskRect.height();
int numVisibleTasks = 1;
int numVisibleThumbnails = 1;
//float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
float progress = mTaskProgressMap.get(data.get(data.size() - 1)) - mInitialScrollP;
int prevScreenY = curveProgressToScreenY(progress);
for (int i = data.size() - 2; i >= 0; i--) {
//Task task = tasks.get(i);
//progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
progress = mTaskProgressMap.get(data.get(i)) - mInitialScrollP;
if (progress < 0) {
break;
}
// TODO: Might need adjustments
//boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
boolean isFrontMostTaskInGroup = true;
if (isFrontMostTaskInGroup) {
float scaleAtP = curveProgressToScale(progress);
int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
numVisibleTasks++;
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
// walk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
progress = mTaskProgressMap.get(data.get(i)) - mInitialScrollP;
if (progress < 0) {
break;
}
}
break;
}
} else if (!isFrontMostTaskInGroup) {
// Affiliated task, no thumbnail
numVisibleTasks++;
}
}
return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
}
/**
* Update/get the transform
*/
public DeckChildViewTransform getStackTransform(T key, float stackScroll,
DeckChildViewTransform transformOut,
DeckChildViewTransform prevTransform) {
// Return early if we have an invalid index
if (!mTaskProgressMap.containsKey(key)) {
transformOut.reset();
return transformOut;
}
return getStackTransform(mTaskProgressMap.get(key), stackScroll, transformOut,
prevTransform);
}
/**
* Update/get the transform
*/
public DeckChildViewTransform getStackTransform(float taskProgress, float stackScroll,
DeckChildViewTransform transformOut,
DeckChildViewTransform prevTransform) {
float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
// If the task top is outside of the bounds below the screen, then immediately reset it
if (pTaskRelative > 1f) {
transformOut.reset();
transformOut.rect.set(mTaskRect);
return transformOut;
}
// The check for the top is trickier, since we want to show the next task if it is at all
// visible, even if p < 0.
if (pTaskRelative < 0f) {
if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
transformOut.reset();
transformOut.rect.set(mTaskRect);
return transformOut;
}
}
float scale = curveProgressToScale(pBounded);
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
int minZ = mConfig.taskViewTranslationZMinPx;
int maxZ = mConfig.taskViewTranslationZMaxPx;
transformOut.scale = scale;
transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -
scaleYOffset;
transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(0, transformOut.translationY);
DVUtils.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
return transformOut;
}
/**
* Returns the untransformed task view size.
*/
public Rect getUntransformedTaskViewSize() {
Rect tvSize = new Rect(mTaskRect);
tvSize.offsetTo(0, 0);
return tvSize;
}
/**
* Returns the scroll to such task top = 1f;
*/
public float getStackScrollForTask(T key) {
if (!mTaskProgressMap.containsKey(key)) return 0f;
return mTaskProgressMap.get(key);
}
/**
* Initializes the curve.
*/
public static void initializeCurve() {
if (xp != null && px != null) return;
xp = new float[PrecisionSteps + 1];
px = new float[PrecisionSteps + 1];
// Approximate f(x)
float[] fx = new float[PrecisionSteps + 1];
float step = 1f / PrecisionSteps;
float x = 0;
for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
fx[xStep] = logFunc(x);
x += step;
}
// Calculate the arc length for x:1->0
float pLength = 0;
float[] dx = new float[PrecisionSteps + 1];
dx[0] = 0;
for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
pLength += dx[xStep];
}
// Approximate p(x), a function of cumulative progress with x, normalized to 0..1
float p = 0;
px[0] = 0f;
px[PrecisionSteps] = 1f;
for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
p += Math.abs(dx[xStep] / pLength);
px[xStep] = p;
}
// Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
// function.
int xStep = 0;
p = 0;
xp[0] = 0f;
xp[PrecisionSteps] = 1f;
for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
// Walk forward in px and find the x where px <= p && p < px+1
while (xStep < PrecisionSteps) {
if (px[xStep] > p) break;
xStep++;
}
// Now, px[xStep-1] <= p < px[xStep]
if (xStep == 0) {
xp[pStep] = 0;
} else {
// Find x such that proportionally, x is correct
float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
x = (xStep - 1 + fraction) * step;
xp[pStep] = x;
}
p += step;
}
}
/**
* Reverses and scales out x.
*/
static float reverse(float x) {
return (-x * XScale) + 1;
}
/**
* The log function describing the curve.
*/
static float logFunc(float x) {
return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
}
/**
* The inverse of the log function describing the curve.
*/
float invLogFunc(float y) {
return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
}
/**
* Converts from the progress along the curve to a screen coordinate.
*/
int curveProgressToScreenY(float p) {
if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
float pIndex = p * PrecisionSteps;
int pFloorIndex = (int) Math.floor(pIndex);
int pCeilIndex = (int) Math.ceil(pIndex);
float xFraction = 0;
if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
}
float x = xp[pFloorIndex] + xFraction;
return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
}
/**
* Converts from the progress along the curve to a scale.
*/
float curveProgressToScale(float p) {
if (p < 0) return StackPeekMinScale;
if (p > 1) return 1f;
float scaleRange = (1f - StackPeekMinScale);
float scale = StackPeekMinScale + (p * scaleRange);
return scale;
}
/**
* Converts from a screen coordinate to the progress along the curve.
*/
float screenYToCurveProgress(int screenY) {
float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
if (x < 0 || x > 1) return x;
float xIndex = x * PrecisionSteps;
int xFloorIndex = (int) Math.floor(xIndex);
int xCeilIndex = (int) Math.ceil(xIndex);
float pFraction = 0;
if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
}
return px[xFloorIndex] + pFraction;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckViewScroller.java
================================================
package com.appeaser.deckview.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.widget.OverScroller;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.utilities.DVUtils;
/**
* Created by Vikram on 02/04/2015.
*/
/* The scrolling logic for a TaskStackView */
public class DeckViewScroller {
public interface DeckViewScrollerCallbacks {
public void onScrollChanged(float p);
}
DeckViewConfig mConfig;
DeckViewLayoutAlgorithm mLayoutAlgorithm;
DeckViewScrollerCallbacks mCb;
float mStackScrollP;
OverScroller mScroller;
ObjectAnimator mScrollAnimator;
float mFinalAnimatedScroll;
public DeckViewScroller(Context context, DeckViewConfig config,
DeckViewLayoutAlgorithm layoutAlgorithm) {
mConfig = config;
mScroller = new OverScroller(context);
mLayoutAlgorithm = layoutAlgorithm;
setStackScroll(getStackScroll());
}
/**
* Resets the task scroller.
*/
public void reset() {
mStackScrollP = 0f;
}
/**
* Sets the callbacks
*/
public void setCallbacks(DeckViewScrollerCallbacks cb) {
mCb = cb;
}
/**
* Gets the current stack scroll
*/
public float getStackScroll() {
return mStackScrollP;
}
/**
* Sets the current stack scroll
*/
public void setStackScroll(float s) {
mStackScrollP = s;
if (mCb != null) {
mCb.onScrollChanged(mStackScrollP);
}
}
/**
* Sets the current stack scroll without calling the callback.
*/
void setStackScrollRaw(float s) {
mStackScrollP = s;
}
/**
* Sets the current stack scroll to the initial state when you first enter recents.
*
* @return whether the stack progress changed.
*/
public boolean setStackScrollToInitialState() {
float prevStackScrollP = mStackScrollP;
setStackScroll(getBoundedStackScroll(mLayoutAlgorithm.mInitialScrollP));
return Float.compare(prevStackScrollP, mStackScrollP) != 0;
}
/**
* Bounds the current scroll if necessary
*/
public boolean boundScroll() {
float curScroll = getStackScroll();
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {
setStackScroll(newScroll);
return true;
}
return false;
}
/**
* Bounds the current scroll if necessary, but does not synchronize the stack view with the model.
*/
public boolean boundScrollRaw() {
float curScroll = getStackScroll();
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {
setStackScrollRaw(newScroll);
return true;
}
return false;
}
/**
* Returns the bounded stack scroll
*/
float getBoundedStackScroll(float scroll) {
return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
}
/**
* Returns the amount that the aboslute value of how much the scroll is out of bounds.
*/
float getScrollAmountOutOfBounds(float scroll) {
if (scroll < mLayoutAlgorithm.mMinScrollP) {
return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
} else if (scroll > mLayoutAlgorithm.mMaxScrollP) {
return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP);
}
return 0f;
}
/**
* Returns whether the specified scroll is out of bounds
*/
boolean isScrollOutOfBounds() {
return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;
}
/**
* Animates the stack scroll into bounds
*/
ObjectAnimator animateBoundScroll() {
float curScroll = getStackScroll();
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {
// Start a new scroll animation
animateScroll(curScroll, newScroll, null);
}
return mScrollAnimator;
}
/**
* Animates the stack scroll
*/
void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) {
// Finish any current scrolling animations
if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
setStackScroll(mFinalAnimatedScroll);
mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0);
}
stopScroller();
stopBoundScrollAnimation();
mFinalAnimatedScroll = newScroll;
mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
mScrollAnimator.setDuration(mConfig.taskStackScrollDuration);
mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator);
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setStackScroll((Float) animation.getAnimatedValue());
}
});
mScrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (postRunnable != null) {
postRunnable.run();
}
mScrollAnimator.removeAllListeners();
}
});
mScrollAnimator.start();
}
/**
* Aborts any current stack scrolls
*/
void stopBoundScrollAnimation() {
DVUtils.cancelAnimationWithoutCallbacks(mScrollAnimator);
}
/**
* * OverScroller ***
*/
int progressToScrollRange(float p) {
return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height());
}
float scrollRangeToProgress(int s) {
return (float) s / mLayoutAlgorithm.mStackVisibleRect.height();
}
/**
* Called from the view draw, computes the next scroll.
*/
boolean computeScroll() {
if (mScroller.computeScrollOffset()) {
float scroll = scrollRangeToProgress(mScroller.getCurrY());
setStackScrollRaw(scroll);
if (mCb != null) {
mCb.onScrollChanged(scroll);
}
return true;
}
return false;
}
/**
* Returns whether the overscroller is scrolling.
*/
boolean isScrolling() {
return !mScroller.isFinished();
}
/**
* Stops the scroller and any current fling.
*/
void stopScroller() {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/DeckViewTouchHandler.java
================================================
package com.appeaser.deckview.views;
import android.content.Context;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import com.appeaser.deckview.helpers.DeckViewConfig;
import com.appeaser.deckview.helpers.DeckViewSwipeHelper;
import com.appeaser.deckview.utilities.DVConstants;
/**
* Created by Vikram on 02/04/2015.
*/
/* Handles touch events for a TaskStackView. */
public class DeckViewTouchHandler implements DeckViewSwipeHelper.Callback {
static int INACTIVE_POINTER_ID = -1;
DeckViewConfig mConfig;
DeckView mDeckView;
DeckViewScroller mScroller;
VelocityTracker mVelocityTracker;
boolean mIsScrolling;
float mInitialP;
float mLastP;
float mTotalPMotion;
int mInitialMotionX, mInitialMotionY;
int mLastMotionX, mLastMotionY;
int mActivePointerId = INACTIVE_POINTER_ID;
DeckChildView mActiveDeckChildView = null;
int mMinimumVelocity;
int mMaximumVelocity;
// The scroll touch slop is used to calculate when we start scrolling
int mScrollTouchSlop;
// The page touch slop is used to calculate when we start swiping
float mPagingTouchSlop;
DeckViewSwipeHelper mSwipeHelper;
boolean mInterceptedBySwipeHelper;
public DeckViewTouchHandler(Context context, DeckView dv,
DeckViewConfig config, DeckViewScroller scroller) {
ViewConfiguration configuration = ViewConfiguration.get(context);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mScrollTouchSlop = configuration.getScaledTouchSlop();
mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
mDeckView = dv;
mScroller = scroller;
mConfig = config;
float densityScale = context.getResources().getDisplayMetrics().density;
mSwipeHelper = new DeckViewSwipeHelper(DeckViewSwipeHelper.X, this,
densityScale, mPagingTouchSlop);
mSwipeHelper.setMinAlpha(1f);
}
/**
* Velocity tracker helpers
*/
void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}
void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* Returns the view at the specified coordinates
*/
DeckChildView findViewAtPoint(int x, int y) {
int childCount = mDeckView.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
DeckChildView tv = (DeckChildView) mDeckView.getChildAt(i);
if (tv.getVisibility() == View.VISIBLE) {
if (mDeckView.isTransformedTouchPointInView(x, y, tv)) {
return tv;
}
}
}
return null;
}
/**
* Constructs a simulated motion event for the current stack scroll.
*/
MotionEvent createMotionEventForStackScroll(MotionEvent ev) {
MotionEvent pev = MotionEvent.obtainNoHistory(ev);
pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
return pev;
}
/**
* Touch preprocessing for handling below
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Return early if we have no children
boolean hasChildren = (mDeckView.getChildCount() > 0);
if (!hasChildren) {
return false;
}
// Pass through to swipe helper if we are swiping
mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
return true;
}
boolean wasScrolling = mScroller.isScrolling() ||
(mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
mInitialMotionX = mLastMotionX = (int) ev.getX();
mInitialMotionY = mLastMotionY = (int) ev.getY();
mInitialP = mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
mActivePointerId = ev.getPointerId(0);
mActiveDeckChildView = findViewAtPoint(mLastMotionX, mLastMotionY);
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
break;
}
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
// Initialize the velocity tracker if necessary
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int x = (int) ev.getX(activePointerIndex);
if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
// Save the touch move info
mIsScrolling = true;
// Disallow parents from intercepting touch events
final ViewParent parent = mDeckView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
mLastMotionX = x;
mLastMotionY = y;
mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// Animate the scroll back if we've cancelled
mScroller.animateBoundScroll();
// Reset the drag state and the velocity tracker
mIsScrolling = false;
mActivePointerId = INACTIVE_POINTER_ID;
mActiveDeckChildView = null;
mTotalPMotion = 0;
recycleVelocityTracker();
break;
}
}
return wasScrolling || mIsScrolling;
}
/**
* Handles touch events once we have intercepted them
*/
public boolean onTouchEvent(MotionEvent ev) {
// Short circuit if we have no children
boolean hasChildren = (mDeckView.getChildCount() > 0);
if (!hasChildren) {
return false;
}
// Pass through to swipe helper if we are swiping
if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
return true;
}
// Update the velocity tracker
initVelocityTrackerIfNotExists();
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
mInitialMotionX = mLastMotionX = (int) ev.getX();
mInitialMotionY = mLastMotionY = (int) ev.getY();
mInitialP = mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
mActivePointerId = ev.getPointerId(0);
mActiveDeckChildView = findViewAtPoint(mLastMotionX, mLastMotionY);
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
// Disallow parents from intercepting touch events
final ViewParent parent = mDeckView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mActivePointerId = ev.getPointerId(index);
mLastMotionX = (int) ev.getX(index);
mLastMotionY = (int) ev.getY(index);
mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
break;
}
case MotionEvent.ACTION_MOVE: {
if (mActivePointerId == INACTIVE_POINTER_ID) break;
mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int x = (int) ev.getX(activePointerIndex);
int y = (int) ev.getY(activePointerIndex);
int yTotal = Math.abs(y - mInitialMotionY);
float curP = mDeckView.getStackAlgorithm().screenYToCurveProgress(y);
float deltaP = mLastP - curP;
if (!mIsScrolling) {
if (yTotal > mScrollTouchSlop) {
mIsScrolling = true;
// Disallow parents from intercepting touch events
final ViewParent parent = mDeckView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
if (mIsScrolling) {
float curStackScroll = mScroller.getStackScroll();
float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
if (Float.compare(overScrollAmount, 0f) != 0) {
// Bound the overscroll to a fixed amount, and inversely scale the y-movement
// relative to how close we are to the max overscroll
float maxOverScroll = mConfig.taskStackOverscrollPct;
deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
/ maxOverScroll));
}
mScroller.setStackScroll(curStackScroll + deltaP);
}
mLastMotionX = x;
mLastMotionY = y;
mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
mTotalPMotion += Math.abs(deltaP);
break;
}
case MotionEvent.ACTION_UP: {
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
(DVConstants.Values.DView.TaskStackMaxOverscrollRange -
DVConstants.Values.DView.TaskStackMinOverscrollRange));
mScroller.mScroller.fling(0,
mScroller.progressToScrollRange(mScroller.getStackScroll()),
0, velocity,
0, 0,
mScroller.progressToScrollRange(mDeckView.getStackAlgorithm().mMinScrollP),
mScroller.progressToScrollRange(mDeckView.getStackAlgorithm().mMaxScrollP),
0, DVConstants.Values.DView.TaskStackMinOverscrollRange +
overscrollRange);
// Invalidate to kick off computeScroll
mDeckView.invalidate();
} else if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
mScroller.animateBoundScroll();
}
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
mTotalPMotion = 0;
recycleVelocityTracker();
break;
}
case MotionEvent.ACTION_POINTER_UP: {
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// Select a new active pointer id and reset the motion state
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastMotionX = (int) ev.getX(newPointerIndex);
mLastMotionY = (int) ev.getY(newPointerIndex);
mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);
mVelocityTracker.clear();
}
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mScroller.isScrollOutOfBounds()) {
// Animate the scroll back into bounds
mScroller.animateBoundScroll();
}
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
mTotalPMotion = 0;
recycleVelocityTracker();
break;
}
}
return true;
}
/**
* Handles generic motion events
*/
public boolean onGenericMotionEvent(MotionEvent ev) {
if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
InputDevice.SOURCE_CLASS_POINTER) {
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_SCROLL:
// Find the front most task and scroll the next task to the front
float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vScroll > 0) {
if (mDeckView.ensureFocusedTask()) {
mDeckView.focusNextTask(true, false);
}
} else {
if (mDeckView.ensureFocusedTask()) {
mDeckView.focusNextTask(false, false);
}
}
return true;
}
}
return false;
}
/**
* * SwipeHelper Implementation ***
*/
@Override
public View getChildAtPosition(MotionEvent ev) {
return findViewAtPoint((int) ev.getX(), (int) ev.getY());
}
@Override
public boolean canChildBeDismissed(View v) {
return true;
}
@Override
public void onBeginDrag(View v) {
DeckChildView tv = (DeckChildView) v;
// Disable clipping with the stack while we are swiping
tv.setClipViewInStack(false);
// Disallow touch events from this task view
tv.setTouchEnabled(false);
// Disallow parents from intercepting touch events
final ViewParent parent = mDeckView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
@Override
public void onSwipeChanged(View v, float delta) {
// Do nothing
}
@Override
public void onChildDismissed(View v) {
DeckChildView tv = (DeckChildView) v;
// Re-enable clipping with the stack (we will reuse this view)
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
// Remove the task view from the stack
mDeckView.onDeckChildViewDismissed(tv);
}
@Override
public void onSnapBackCompleted(View v) {
DeckChildView tv = (DeckChildView) v;
// Re-enable clipping with the stack
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
}
@Override
public void onDragCancelled(View v) {
// Do nothing
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/FixedSizeImageView.java
================================================
package com.appeaser.deckview.views;
/**
* Created by Vikram on 01/04/2015.
*/
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when
* setting the image to Null.
*/
public class FixedSizeImageView extends ImageView {
boolean mAllowRelayout = true;
boolean mAllowInvalidate = true;
public FixedSizeImageView(Context context) {
this(context, null);
}
public FixedSizeImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void requestLayout() {
if (mAllowRelayout) {
super.requestLayout();
}
}
@Override
public void invalidate() {
if (mAllowInvalidate) {
super.invalidate();
}
}
@Override
public void setImageDrawable(Drawable drawable) {
boolean isNullBitmapDrawable = (drawable instanceof BitmapDrawable) &&
(((BitmapDrawable) drawable).getBitmap() == null);
if (drawable == null || isNullBitmapDrawable) {
mAllowRelayout = false;
mAllowInvalidate = false;
}
super.setImageDrawable(drawable);
mAllowRelayout = true;
mAllowInvalidate = true;
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/ViewAnimation.java
================================================
package com.appeaser.deckview.views;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import com.appeaser.deckview.helpers.DeckChildViewTransform;
import com.appeaser.deckview.utilities.ReferenceCountedTrigger;
/**
* Created by Vikram on 01/04/2015.
*/
/* Common code related to view animations */
public class ViewAnimation {
/* The animation context for a task view animation into Recents */
public static class TaskViewEnterContext {
// A trigger to run some logic when all the animations complete. This works around the fact
// that it is difficult to coordinate ViewPropertyAnimators
public ReferenceCountedTrigger postAnimationTrigger;
// An update listener to notify as the enter animation progresses (used for the home transition)
public ValueAnimator.AnimatorUpdateListener updateListener;
// These following properties are updated for each task view we start the enter animation on
// Whether or not the current task occludes the launch target
boolean currentTaskOccludesLaunchTarget;
// The task rect for the current stack
Rect currentTaskRect;
// The transform of the current task view
public DeckChildViewTransform currentTaskTransform;
// The view index of the current task view
public int currentStackViewIndex;
// The total number of task views
public int currentStackViewCount;
public TaskViewEnterContext(ReferenceCountedTrigger t) {
postAnimationTrigger = t;
}
}
/* The animation context for a task view animation out of Recents */
public static class TaskViewExitContext {
// A trigger to run some logic when all the animations complete. This works around the fact
// that it is difficult to coordinate ViewPropertyAnimators
public ReferenceCountedTrigger postAnimationTrigger;
// The translationY to apply to a TaskView to move it off the bottom of the task stack
public int offscreenTranslationY;
public TaskViewExitContext(ReferenceCountedTrigger t) {
postAnimationTrigger = t;
}
}
}
================================================
FILE: deckview/src/main/java/com/appeaser/deckview/views/ViewPool.java
================================================
package com.appeaser.deckview.views;
import android.content.Context;
import java.util.Iterator;
import java.util.LinkedList;
/**
* Created by Vikram on 01/04/2015.
*/
/* A view pool to manage more views than we can visibly handle */
public class ViewPool {
/* An interface to the consumer of a view pool */
public interface ViewPoolConsumer {
public V createView(Context context);
public void prepareViewToEnterPool(V v);
public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
public boolean hasPreferredData(V v, T preferredData);
}
Context mContext;
ViewPoolConsumer mViewCreator;
LinkedList mPool = new LinkedList();
/**
* Initializes the pool with a fixed predetermined pool size
*/
public ViewPool(Context context, ViewPoolConsumer viewCreator) {
mContext = context;
mViewCreator = viewCreator;
}
/**
* Returns a view into the pool
*/
void returnViewToPool(V v) {
mViewCreator.prepareViewToEnterPool(v);
mPool.push(v);
}
/**
* Gets a view from the pool and prepares it
*/
V pickUpViewFromPool(T preferredData, T prepareData) {
V v = null;
boolean isNewView = false;
if (mPool.isEmpty()) {
v = mViewCreator.createView(mContext);
isNewView = true;
} else {
// Try and find a preferred view
Iterator iter = mPool.iterator();
while (iter.hasNext()) {
V vpv = iter.next();
if (mViewCreator.hasPreferredData(vpv, preferredData)) {
v = vpv;
iter.remove();
break;
}
}
// Otherwise, just grab the first view
if (v == null) {
v = mPool.pop();
}
}
mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
return v;
}
/**
* Returns an iterator to the list of the views in the pool.
*/
Iterator poolViewIterator() {
if (mPool != null) {
return mPool.iterator();
}
return null;
}
}
================================================
FILE: deckview/src/main/res/drawable-v21/deck_child_view_button_bg.xml
================================================
================================================
FILE: deckview/src/main/res/drawable-v21/deck_child_view_dismiss_dark.xml
================================================
================================================
FILE: deckview/src/main/res/drawable-v21/deck_child_view_dismiss_light.xml
================================================
================================================
FILE: deckview/src/main/res/drawable-v21/deck_child_view_header_bg.xml
================================================
================================================
FILE: deckview/src/main/res/drawable-v21/deck_child_view_header_bg_color.xml
================================================
================================================
FILE: deckview/src/main/res/interpolator-v21/decelerate_quint.xml
================================================
================================================
FILE: deckview/src/main/res/interpolator-v21/fast_out_linear_in.xml
================================================
================================================
FILE: deckview/src/main/res/interpolator-v21/fast_out_slow_in.xml
================================================
================================================
FILE: deckview/src/main/res/interpolator-v21/linear_out_slow_in.xml
================================================
================================================
FILE: deckview/src/main/res/layout/deck_child_view.xml
================================================
================================================
FILE: deckview/src/main/res/layout/deck_child_view_header.xml
================================================
================================================
FILE: deckview/src/main/res/values/colors.xml
================================================
#ffe6e6e6
#ffeeeeee
#cc000000
#28ffffff
#44000000
#03000000
================================================
FILE: deckview/src/main/res/values/config.xml
================================================
250
250
225
96
325
100
200
225
12
125
225
250
1
400
false
200
false
212
0
================================================
FILE: deckview/src/main/res/values/dimens.xml
================================================
800dp
- 0.03333
- 0.0875
16dp
100dp
2dp
1.5dp
20dp
80dp
64dp
- 0.6
56dp
- 0.9
1dp
8dp
48dp
================================================
FILE: deckview/src/main/res/values/strings.xml
================================================
DeckViewLibrary
Nothing here
Dismiss %s.
================================================
FILE: deckview/src/main/res/values-land/dimens.xml
================================================
- 0.26
================================================
FILE: deckview/src/main/res/values-sw600dp/dimens.xml
================================================
- 0.075
================================================
FILE: deckview/src/main/res/values-sw600dp-land/dimens.xml
================================================
- 0.25
================================================
FILE: deckview/src/main/res/values-sw720dp/dimens.xml
================================================
3dp
================================================
FILE: deckviewsample/.gitignore
================================================
/build
================================================
FILE: deckviewsample/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.appeaser.deckviewsample"
minSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup.picasso:picasso:2.5.2'
compile project(':deckview')
}
================================================
FILE: deckviewsample/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:/Users/Vikram/Documents/Android/adt-bundle-windows-x86-20130219/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: deckviewsample/src/androidTest/java/com/appeaser/deckviewsample/ApplicationTest.java
================================================
package com.appeaser.deckviewsample;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: deckviewsample/src/main/AndroidManifest.xml
================================================
================================================
FILE: deckviewsample/src/main/java/com/appeaser/deckviewsample/Datum.java
================================================
package com.appeaser.deckviewsample;
import android.os.Parcel;
import android.os.Parcelable;
import com.squareup.picasso.Target;
/**
* Simple model class
* One important requirement for DeckView to function
* is that all items in the dataset *must be* uniquely
* identifiable. No two items can be such
* that `item1.equals(item2)` returns `true`.
* See equals() implementation below.
* `id` is generated using `DeckViewSampleActivity#generateuniqueKey()`
* Implementing `Parcelable` serves only one purpose - to persist data
* on configuration change.
*/
public class Datum implements Parcelable {
public int id;
public String headerTitle, link;
public Target target;
public Datum() {
// Nothing
}
@Override
public int describeContents() {
return 0;
}
public Datum(Parcel in) {
readFromParcel(in);
}
public void readFromParcel(Parcel in) {
id = in.readInt();
headerTitle = in.readString();
link = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(headerTitle);
dest.writeString(link);
}
public static final Creator CREATOR = new Creator() {
public Datum createFromParcel(Parcel in) {
return new Datum(in);
}
public Datum[] newArray(int size) {
return new Datum[size];
}
};
@Override
public boolean equals(Object o) {
return ((Datum) o).id == this.id;
}
}
================================================
FILE: deckviewsample/src/main/java/com/appeaser/deckviewsample/DeckViewSampleActivity.java
================================================
package com.appeaser.deckviewsample;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.appeaser.deckview.views.DeckChildView;
import com.appeaser.deckview.views.DeckView;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Random;
/**
* Basic sample for DeckView.
* Images are downloaded and cached using
* Picasso "http://square.github.io/picasso/".
* DeckView is *very* young & can only
* afford basic functionality.
*/
public class DeckViewSampleActivity extends Activity {
// View that stacks its children like a deck of cards
DeckView mDeckView;
Drawable mDefaultHeaderIcon;
ArrayList mEntries;
// Placeholder for when the image is being downloaded
Bitmap mDefaultThumbnail;
// Retain position on configuration change
// imageSize to pass to http://lorempixel.com
int scrollToChildIndex = -1, imageSize = 500;
// SavedInstance bundle keys
final String CURRENT_SCROLL = "current.scroll", CURRENT_LIST = "current.list";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_deck_view_sample);
mDeckView = (DeckView) findViewById(R.id.deckview);
mDefaultThumbnail = BitmapFactory.decodeResource(getResources(),
R.drawable.default_thumbnail);
mDefaultHeaderIcon = getResources().getDrawable(R.drawable.default_header_icon);
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(CURRENT_LIST)) {
mEntries = savedInstanceState.getParcelableArrayList(CURRENT_LIST);
}
if (savedInstanceState.containsKey(CURRENT_SCROLL)) {
scrollToChildIndex = savedInstanceState.getInt(CURRENT_SCROLL);
}
}
if (mEntries == null) {
mEntries = new ArrayList<>();
for (int i = 1; i < 100; i++) {
Datum datum = new Datum();
datum.id = generateUniqueKey();
datum.link = "http://lorempixel.com/" + imageSize + "/" + imageSize
+ "/sports/" + "ID " + datum.id + "/";
datum.headerTitle = "Image ID " + datum.id;
mEntries.add(datum);
}
}
// Callback implementation
DeckView.Callback deckViewCallback = new DeckView.Callback() {
@Override
public ArrayList getData() {
return mEntries;
}
@Override
public void loadViewData(WeakReference> dcv, Datum item) {
loadViewDataInternal(item, dcv);
}
@Override
public void unloadViewData(Datum item) {
Picasso.with(DeckViewSampleActivity.this).cancelRequest(item.target);
}
@Override
public void onViewDismissed(Datum item) {
mEntries.remove(item);
mDeckView.notifyDataSetChanged();
}
@Override
public void onItemClick(Datum item) {
Toast.makeText(DeckViewSampleActivity.this,
"Item with title: '" + item.headerTitle + "' clicked",
Toast.LENGTH_SHORT).show();
}
@Override
public void onNoViewsToDeck() {
Toast.makeText(DeckViewSampleActivity.this,
"No views to show",
Toast.LENGTH_SHORT).show();
}
};
mDeckView.initialize(deckViewCallback);
if (scrollToChildIndex != -1) {
mDeckView.post(new Runnable() {
@Override
public void run() {
// Restore scroll position
mDeckView.scrollToChild(scrollToChildIndex);
}
});
}
}
void loadViewDataInternal(final Datum datum,
final WeakReference> weakView) {
// datum.target can be null
Picasso.with(DeckViewSampleActivity.this).cancelRequest(datum.target);
datum.target = new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// Pass loaded Bitmap to view
if (weakView.get() != null) {
weakView.get().onDataLoaded(datum, bitmap,
mDefaultHeaderIcon, datum.headerTitle, Color.DKGRAY);
}
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
// Loading failed. Pass default thumbnail instead
if (weakView.get() != null) {
weakView.get().onDataLoaded(datum, mDefaultThumbnail,
mDefaultHeaderIcon, datum.headerTitle + " Failed", Color.DKGRAY);
}
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
// Pass the default thumbnail for now. It will
// be replaced once the target Bitmap has been loaded
if (weakView.get() != null) {
weakView.get().onDataLoaded(datum, mDefaultThumbnail,
mDefaultHeaderIcon, "Loading...", Color.DKGRAY);
}
}
};
// Begin loading
Picasso.with(DeckViewSampleActivity.this).load(datum.link).into(datum.target);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_deck_view_sample, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
// Add a new item to the end of the list
if (id == R.id.action_add) {
Datum datum = new Datum();
datum.id = generateUniqueKey();
datum.headerTitle = "(New) Image ID " + datum.id;
datum.link = "http://lorempixel.com/" + imageSize + "/" + imageSize
+ "/sports/" + "ID " + datum.id + "/";
mEntries.add(datum);
mDeckView.notifyDataSetChanged();
return true;
} else if (id == R.id.action_add_multiple) {
// Add multiple items (between 5 & 10 items)
// at random indices
Random rand = new Random();
// adding between 5 and 10 items
int numberOfItemsToAdd = rand.nextInt(6) + 5;
for (int i = 0; i < numberOfItemsToAdd; i++) {
int atIndex = mEntries.size() > 0 ?
rand.nextInt(mEntries.size()) : 0;
Datum datum = new Datum();
datum.id = generateUniqueKey();
datum.link = "http://lorempixel.com/" + imageSize + "/" + imageSize
+ "/sports/" + "ID " + datum.id + "/";
datum.headerTitle = "(New) Image ID " + datum.id;
mEntries.add(atIndex, datum);
}
mDeckView.notifyDataSetChanged();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save current scroll and the list
int currentChildIndex = mDeckView.getCurrentChildIndex();
outState.putInt(CURRENT_SCROLL, currentChildIndex);
outState.putParcelableArrayList(CURRENT_LIST, mEntries);
super.onSaveInstanceState(outState);
}
// Generates a key that will remain unique
// during the application's lifecycle
private static int generateUniqueKey() {
return ++KEY;
}
private static int KEY = 0;
}
================================================
FILE: deckviewsample/src/main/res/drawable-v21/box.xml
================================================
================================================
FILE: deckviewsample/src/main/res/layout/activity_deck_view_sample.xml
================================================
================================================
FILE: deckviewsample/src/main/res/menu/menu_deck_view_sample.xml
================================================
================================================
FILE: deckviewsample/src/main/res/values/dimens.xml
================================================
16dp
16dp
================================================
FILE: deckviewsample/src/main/res/values/strings.xml
================================================
DeckViewSample
Hello world!
Settings
================================================
FILE: deckviewsample/src/main/res/values/styles.xml
================================================
================================================
FILE: deckviewsample/src/main/res/values-v21/styles.xml
================================================
================================================
FILE: deckviewsample/src/main/res/values-w820dp/dimens.xml
================================================
64dp
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':app', ':deckview', ':deckviewsample'