Repository: danielzeller/Depth-LIB-Android- Branch: master Commit: 8962cd7c2d6f Files: 98 Total size: 214.8 KB Directory structure: gitextract_4xerzip3/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── no/ │ │ └── agens/ │ │ └── depth/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── no/ │ │ └── agens/ │ │ └── depth/ │ │ ├── BearSceneView.java │ │ ├── Foam.java │ │ ├── MenuAnimation.java │ │ ├── PlayGroundActivity.java │ │ ├── RenderableBear.java │ │ ├── RenderableTree.java │ │ ├── RootActivity.java │ │ ├── SeekBarProgressChangeListener.java │ │ ├── Smoke.java │ │ ├── TransitionHelper.java │ │ ├── Water.java │ │ ├── WaterFragment.java │ │ ├── WaterSceneView.java │ │ └── WindFragment.java │ └── res/ │ ├── drawable/ │ │ ├── bear_bg_gradient.xml │ │ ├── circle.xml │ │ ├── fab_bg.xml │ │ ├── menu_btn.xml │ │ ├── menu_btn2.xml │ │ ├── menu_btn3.xml │ │ └── menu_btn4.xml │ ├── layout/ │ │ ├── activity_root.xml │ │ ├── fragment_playground.xml │ │ ├── fragment_water.xml │ │ ├── fragment_wind.xml │ │ └── menu_item.xml │ ├── menu/ │ │ └── menu_root.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── lib/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── no/ │ │ └── agens/ │ │ └── depth/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── no/ │ │ │ └── agens/ │ │ │ └── depth/ │ │ │ └── lib/ │ │ │ ├── CircularSplashView.java │ │ │ ├── ColorAnimator.java │ │ │ ├── CustomShadow.java │ │ │ ├── DepthLayout.java │ │ │ ├── DepthRendrer.java │ │ │ ├── MaterialMenuDrawable.java │ │ │ ├── MathHelper.java │ │ │ ├── RectEvaluator.java │ │ │ ├── RectFEvaluator.java │ │ │ ├── RippleHelper.java │ │ │ ├── headers/ │ │ │ │ ├── AuraDrawable.java │ │ │ │ ├── NoiseEffect.java │ │ │ │ ├── Particle.java │ │ │ │ ├── ParticleSystem.java │ │ │ │ ├── PathBitmapMesh.java │ │ │ │ └── Renderable.java │ │ │ └── tween/ │ │ │ ├── FrameRateCounter.java │ │ │ ├── TRectFEvaluator.java │ │ │ └── interpolators/ │ │ │ ├── BackIn.java │ │ │ ├── BackInOut.java │ │ │ ├── BackOut.java │ │ │ ├── CircIn.java │ │ │ ├── CircInOut.java │ │ │ ├── CircOut.java │ │ │ ├── ElasticIn.java │ │ │ ├── ElasticInOut.java │ │ │ ├── ElasticOut.java │ │ │ ├── ExpoIn.java │ │ │ ├── ExpoInOut.java │ │ │ ├── ExpoOut.java │ │ │ ├── QuadIn.java │ │ │ ├── QuadInOut.java │ │ │ ├── QuadOut.java │ │ │ ├── QuartIn.java │ │ │ ├── QuartInOut.java │ │ │ ├── QuartOut.java │ │ │ ├── QuintIn.java │ │ │ ├── QuintInOut.java │ │ │ ├── QuintOut.java │ │ │ ├── SineIn.java │ │ │ ├── SineInOut.java │ │ │ └── SineOut.java │ │ └── res/ │ │ └── values/ │ │ ├── dept_view.xml │ │ └── values.xml │ └── test/ │ └── java/ │ └── no/ │ └── agens/ │ └── depth/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle /local.properties .DS_Store /build /captures .idea *.iml ================================================ FILE: .travis.yml ================================================ language: android jdk: - oraclejdk7 android: components: - platform-tools - tools - build-tools-23.0.3 - android-23 - extra-android-support - extra-android-m2repository # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ sudo: false before_install: - export JAVA7_HOME=/usr/lib/jvm/java-7-oracle - export JAVA_HOME=$JAVA7_HOME script: - ./gradlew clean assemble check ================================================ FILE: LICENSE ================================================ Authors: Daniel Zeller The MIT License (MIT) Copyright (c) 2016 Agens AS (http://agens.no/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Depth-LIB-Android This library adds depth/thickness to views. [](https://youtu.be/d2H1z6bmD9w) [See demo on youtube](https://youtu.be/d2H1z6bmD9w) ## Demo The demo code contains some examples of Canvas drawing techniques to create particle systems, waves and grain effect. I wasn't really happy with the default shadows in Android because they start misbehaving when they are rotated so I made my own shadow solution. The project also contains various tweening functions for animations, and uses the Facebook rebound lib for some tweens. Don't expect too much from this lib, it was written as a quick prototype, so the visuals look good but the code behind it could have been better. ## More Libraries Make sure to also checkout Metaballs LIB: [](https://github.com/danielzeller/MetaBalls-LIB-Android) [LINK](https://github.com/danielzeller/MetaBalls-LIB-Android) and Blur LIB: [](https://github.com/danielzeller/Blur-LIB-Android) [LINK](https://github.com/danielzeller/Blur-LIB-Android) ## Contact You can reach me on twitter as [@zellah](https://twitter.com/zellah) or [danielzeller.no](http://danielzeller.no/). ## Who's behind this? Developed by [@zellah](https://twitter.com/zellah) ================================================ FILE: app/.gitignore ================================================ /build app.iml ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "no.agens.depth" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:support-v4:28.0.0' implementation 'com.android.support:design:28.0.0' implementation project(':lib') } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/danielzeller/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/no/agens/depth/ApplicationTest.java ================================================ package no.agens.depth; 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/java/no/agens/depth/BearSceneView.java ================================================ package no.agens.depth; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import no.agens.depth.lib.MathHelper; import no.agens.depth.lib.headers.AuraDrawable; import no.agens.depth.lib.headers.NoiseEffect; import no.agens.depth.lib.headers.ParticleSystem; import no.agens.depth.lib.headers.Renderable; import no.agens.depth.lib.tween.FrameRateCounter; /** * Created by danielzeller on 01.10.14. */ public class BearSceneView extends View { public static final int WIND_RANDOMIZE_INTERVAL = 300; private Renderable[] renderables; public BearSceneView(Context context) { super(context); } private float wind = 10f; float windRanomizerTarget; float windRanomizerEased; ParticleSystem flames; ParticleSystem sparks; Smoke smoke; public BearSceneView(Context context, AttributeSet attrs) { super(context, attrs); } public BearSceneView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (renderables == null) { init(); } } private void init() { renderables = new Renderable[17]; Bitmap treeB = BitmapFactory.decodeResource(getResources(), R.mipmap.tree); addTree(treeB,getMeasuredWidth() * 0.18f, getMeasuredHeight() * -0.65f, 0.28f, 0.46f); addTree(treeB,getMeasuredWidth() * 0.6f, getMeasuredHeight() * -0.65f, 0.33f, 0.46f); addTree(treeB,getMeasuredWidth() * 0.45f, getMeasuredHeight() * -0.45f, 0.5f, 0.8f); addTree(treeB,getMeasuredWidth() * 0.13f, getMeasuredHeight() * -0.65f, 0.3f, 0.46f); addTree(treeB,getMeasuredWidth() * 0.83f, getMeasuredHeight() * -0.2f, 0.5f, 1f); addTree(treeB,getMeasuredWidth() * 0.02f, getMeasuredHeight() * -0.1f, 0.8f, 1f); addTree(treeB,getMeasuredWidth() * 0.18f, getMeasuredHeight() * 0.15f, 0.8f, 1f); addTree(treeB,getMeasuredWidth() * 0.7f, getMeasuredHeight() * -0.1f, 0.8f, 1f); Bitmap bear1 = BitmapFactory.decodeResource(getResources(), R.mipmap.bear_1); Bitmap bear2 = BitmapFactory.decodeResource(getResources(), R.mipmap.bear_2); Bitmap bear3 = BitmapFactory.decodeResource(getResources(), R.mipmap.bear_white); Bitmap stones = BitmapFactory.decodeResource(getResources(), R.drawable.stones); Bitmap smoke = BitmapFactory.decodeResource(getResources(), R.drawable.smoke); Bitmap grunge = BitmapFactory.decodeResource(getResources(), R.drawable.grunge); addFire(smoke, stones, getMeasuredWidth() * 0.61f, getMeasuredHeight() * 0.8f); addBear(getMeasuredWidth() * 0.636f, getMeasuredHeight() * 0.59f, bear1, bear2); addWhiteBear(getMeasuredWidth() * 0.44f, getMeasuredHeight() * 0.66f, bear3); setLayerType(View.LAYER_TYPE_HARDWARE, null); addGrunge(grunge); } private void addGrunge(Bitmap grunge) { NoiseEffect noise = new NoiseEffect(grunge, 100, 2.5f); renderables[index] = noise; noise.setNoiseIntensity(0.25f); index += 1; } private void addFire(Bitmap smoke, Bitmap stones, float x, float y) { renderables[index] = new AuraDrawable(getResources().getDrawable(R.drawable.aura_gradient), new Rect((int) (getMeasuredWidth() * 0.44f), (int) (getMeasuredHeight() * 0.4f), (int) (getMeasuredWidth() * 0.8f), (int) (getMeasuredHeight() * 1.1f))); index += 1; renderables[index] = new AuraDrawable(getResources().getDrawable(R.drawable.aura_gradient_inner), new Rect((int) (getMeasuredWidth() * 0.5f), (int) (getMeasuredHeight() * 0.6f), (int) (getMeasuredWidth() * 0.72f), (int) (getMeasuredHeight() * 1f))); index += 1; float density = getResources().getDisplayMetrics().density; float randomXPlacement = 5f * density; flames = new ParticleSystem(x, y, 30, -30f * density, randomXPlacement); sparks = new ParticleSystem(x, y, 600, -30f * density, randomXPlacement); renderables[index] = flames; flames.setParticleSize((int) (8f * density)); flames.setRandomMovementX(20f * density); flames.setRandomMovementY(1.5f * density); flames.setColors(getResources().getColor(R.color.fire_start_color), getResources().getColor(R.color.fire_end_color)); index += 1; renderables[index] = sparks; sparks.setParticleSize((int) (1f * density)); sparks.setRandomMovementX(25f * density); sparks.setRandomMovementY(2.5f * density); sparks.setRandomMovementChangeInterval(900); sparks.setColors(getResources().getColor(R.color.fire_start_color), getResources().getColor(R.color.fire_start_color)); sparks.setMinYCoord(0); index += 1; renderables[index] = new Renderable(stones, x - randomXPlacement * 2f, y); index += 1; this.smoke = new Smoke(smoke, x, getMeasuredHeight() * 0.68f, 110 * density, 60 * density, 8, density); renderables[index] = this.smoke; index += 1; } private void addWhiteBear(float v, float v1, Bitmap bear3) { renderables[index] = new Renderable(bear3, v, v1); index += 1; } private void addBear(float v, float v1, Bitmap bear1, Bitmap bear2) { renderables[index] = new RenderableBear(new Bitmap[]{bear1, bear2}, v, v1); index += 1; } int index = 0; void addTree(Bitmap bitmap, float x, float y, float scale, float alpha) { renderables[index] = new RenderableTree(bitmap, x, y, alpha); renderables[index].setScale(scale, scale); index += 1; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); destroyResources(); } private void destroyResources() { for (Renderable renderable: renderables) renderable.destroy(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (renderables == null && getWidth() != 0) init(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float deltaTime = FrameRateCounter.timeStep(); windRanomizerEased += ((windRanomizerTarget - windRanomizerEased) * 4f) * deltaTime; for (Renderable renderable : renderables) { renderable.draw(canvas); if (renderable instanceof Smoke || renderable instanceof ParticleSystem) renderable.update(deltaTime, wind); else renderable.update(deltaTime, wind + windRanomizerEased); } if (lastWindRandomChange + WIND_RANDOMIZE_INTERVAL < System.currentTimeMillis()) { lastWindRandomChange = System.currentTimeMillis(); float randomSpeedInterval = Math.max(wind / 2, 1); windRanomizerTarget = (float) MathHelper.rand.nextInt((int) randomSpeedInterval) - randomSpeedInterval / 2f; } if (!pause) invalidate(); } public void setPause(boolean pause) { this.pause = pause; if (!pause) { FrameRateCounter.timeStep(); invalidate(); for (Renderable renderable : renderables) renderable.resume(); } else { for (Renderable renderable : renderables) renderable.pause(); } } private boolean pause = false; long lastWindRandomChange; public void setWind(int wind) { this.wind = wind; } float LOWEST_FLAMES_COORD = 0.8f; float HIGHEST_FLAMES_COORD = 0.4f; float HIGHEST_SMOKE_COORD = 0.6f; public void setFlamesHeight(int progress) { float flamesHeight = getYCoordByPercent(LOWEST_FLAMES_COORD - ((LOWEST_FLAMES_COORD - HIGHEST_FLAMES_COORD) * ((float) progress / 100f))); flames.setMinYCoord(flamesHeight); float smokeYCoord = getYCoordByPercent(LOWEST_FLAMES_COORD - ((LOWEST_FLAMES_COORD - HIGHEST_SMOKE_COORD) * ((float) progress / 100f))); smoke.setY(smokeYCoord); } private float getYCoordByPercent(float percent) { return getHeight() * percent; } } ================================================ FILE: app/src/main/java/no/agens/depth/Foam.java ================================================ package no.agens.depth; import android.graphics.Bitmap; import android.graphics.Path; import android.graphics.PathMeasure; import no.agens.depth.lib.MathHelper; import no.agens.depth.lib.headers.PathBitmapMesh; public class Foam extends PathBitmapMesh { private float verticalOffset; void update(float deltaTime) { for (int i = 0; i < foamCoords.length; i++) { easedFoamCoords[i] += ((foamCoords[i] - easedFoamCoords[i])) * deltaTime; } } float[] foamCoords; float[] easedFoamCoords; float minHeight, maxHeight; public Foam(int horizontalSlices, int verticalSlices, Bitmap bitmap, float minHeight, float maxHeight, int animDuration) { super(horizontalSlices, verticalSlices, bitmap,animDuration); setupFoam(horizontalSlices); this.minHeight = minHeight; this.maxHeight = maxHeight; } private void setupFoam(int verts) { foamCoords = new float[verts]; easedFoamCoords = new float[verts]; for (int i = 0; i < verts; i++) { foamCoords[i] = 0; easedFoamCoords[i] = 0; } } void calcWave() { for (int i = 0; i < foamCoords.length; i++) { foamCoords[i] = MathHelper.randomRange(minHeight, maxHeight); } } public void matchVertsToPath(Path path, float extraOffset) { PathMeasure pm = new PathMeasure(path, false); int index = 0; for (int i = 0; i < staticVerts.length / 2; i++) { float yIndexValue = staticVerts[i * 2 + 1]; float xIndexValue = staticVerts[i * 2]; float percentOffsetX = (0.000001f + xIndexValue) / bitmap.getWidth(); float percentOffsetX2 = (0.000001f + xIndexValue) / (bitmap.getWidth() + extraOffset); percentOffsetX2 += pathOffsetPercent; pm.getPosTan(pm.getLength() * (1f - percentOffsetX), coords, null); pm.getPosTan(pm.getLength() * (1f - percentOffsetX2), coords2, null); if (yIndexValue == 0) { setXY(drawingVerts, i, coords[0], coords2[1] + verticalOffset); } else { float desiredYCoord = Math.max(coords2[1], coords2[1] + easedFoamCoords[Math.min(easedFoamCoords.length - 1, index)]); setXY(drawingVerts, i, coords[0], desiredYCoord + verticalOffset); index += 1; } } } public void setVerticalOffset(float verticalOffset) { this.verticalOffset = verticalOffset; } } ================================================ FILE: app/src/main/java/no/agens/depth/MenuAnimation.java ================================================ package no.agens.depth; public interface MenuAnimation { void animateTOMenu(); void revertFromMenu(); void exitFromMenu(); } ================================================ FILE: app/src/main/java/no/agens/depth/PlayGroundActivity.java ================================================ package no.agens.depth; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import android.widget.SeekBar; import no.agens.depth.lib.DepthLayout; import no.agens.depth.lib.MaterialMenuDrawable; public class PlayGroundActivity extends Activity { private DepthLayout depthView; private static final float MAX_ROTATION_X = 90; private static final float MAX_ROTATION_Y = 90; private static final float MAX_ROTATION_Z = 360; private static final float MAX_ELEVATION = 50; private static final float MAX_DEPTH = 20; private static final float CAMERA_DISTANCE = 6000f; private int seekBarColor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_playground); seekBarColor = getResources().getColor(R.color.fab); depthView = (DepthLayout) findViewById(R.id.depth_view); depthView.setCameraDistance((CAMERA_DISTANCE * getResources().getDisplayMetrics().density)); setupSeekBars(); makeAppFullscreen(); setupMenuButton(); } private void setupMenuButton() { ImageView menu = (ImageView) findViewById(R.id.menu); menu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); MaterialMenuDrawable menuIcon = new MaterialMenuDrawable(this, Color.WHITE, MaterialMenuDrawable.Stroke.THIN, WaterFragment.TRANSFORM_DURATION); menu.setImageDrawable(menuIcon); menuIcon.setIconState(MaterialMenuDrawable.IconState.ARROW); } private void setupSeekBars() { setupRotationXSeekbar(); setupRotationYSeekbar(); setupRotationZSeekbar(); setupElevationSeekbar(); setupDepthSeekbar(); } private void makeAppFullscreen() { getWindow().setStatusBarColor(Color.TRANSPARENT); getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } private void setupDepthSeekbar() { SeekBar depth = (SeekBar) findViewById(R.id.depth_seekbar); WindFragment.setProgressBarColor(depth, seekBarColor); depth.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { depthView.setDepth(MAX_DEPTH * getResources().getDisplayMetrics().density * ((float) progress / (float) seekBar.getMax())); } }); depth.setProgress((int) (depth.getMax() * 0.1f)); } private SeekBar setupRotationXSeekbar() { SeekBar rotationX = (SeekBar) findViewById(R.id.rotation_x_seekbar); WindFragment.setProgressBarColor(rotationX, seekBarColor); rotationX.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { depthView.setRotationX(-MAX_ROTATION_X + (MAX_ROTATION_X * 2f) * ((float) progress / (float) seekBar.getMax())); } }); rotationX.setProgress((int) (rotationX.getMax() * 0.1f)); return rotationX; } private void setupRotationYSeekbar() { SeekBar rotationY = (SeekBar) findViewById(R.id.rotation_y_seekbar); rotationY.setProgress((int) (rotationY.getMax() * 0.5f)); WindFragment.setProgressBarColor(rotationY, seekBarColor); rotationY.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { depthView.setRotationY(-MAX_ROTATION_Y + (MAX_ROTATION_Y * 2f) * ((float) progress / (float) seekBar.getMax())); } }); } private void setupRotationZSeekbar() { SeekBar rotation = (SeekBar) findViewById(R.id.rotation_z_seekbar); rotation.setProgress(0); WindFragment.setProgressBarColor(rotation, seekBarColor); rotation.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { depthView.setRotation(-MAX_ROTATION_Z * ((float) progress / (float) seekBar.getMax())); } }); } private void setupElevationSeekbar() { SeekBar elevation = (SeekBar) findViewById(R.id.elevation_seekbar); elevation.setProgress(0); WindFragment.setProgressBarColor(elevation, seekBarColor); elevation.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { depthView.setCustomShadowElevation((MAX_ELEVATION * ((float) progress / (float) seekBar.getMax())) * getResources().getDisplayMetrics().density); } }); elevation.setProgress((int) (elevation.getMax() * 0.5f)); } } ================================================ FILE: app/src/main/java/no/agens/depth/RenderableBear.java ================================================ package no.agens.depth; import android.graphics.Bitmap; import android.graphics.Canvas; import no.agens.depth.lib.headers.Renderable; public class RenderableBear extends Renderable { public static final int FRAME_DELAY = 2500; long lastFrameChange; Bitmap[] bitmaps; int bitmapIndex = 0; public RenderableBear(Bitmap[] bitmaps, float x, float y) { super(null, x, y); lastFrameChange = System.currentTimeMillis(); this.bitmaps = bitmaps; } @Override public void draw(Canvas canvas) { canvas.save(); canvas.drawBitmap(bitmaps[bitmapIndex], x + translationX / 2, y + translationY, null); canvas.restore(); } @Override public void update(float deltaTime, float wind) { super.update(deltaTime, wind); if (lastFrameChange + FRAME_DELAY < System.currentTimeMillis()) { lastFrameChange = System.currentTimeMillis(); bitmapIndex += 1; if (bitmapIndex == bitmaps.length) bitmapIndex = 0; } } public void destroy() { for (Bitmap bitmap : bitmaps) { if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } } } ================================================ FILE: app/src/main/java/no/agens/depth/RenderableTree.java ================================================ package no.agens.depth; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import no.agens.depth.lib.headers.Renderable; /** * Created by danielzeller on 01.10.14. */ public class RenderableTree extends Renderable { private final float[] drawingVerts = new float[TOTAL_SLICES_COUNT * 2]; private final float[] staticVerts = new float[TOTAL_SLICES_COUNT * 2]; private static final int HORIZONTAL_SLICES = 1; private static final int VERTICAL_SLICES = 95; private static final int TOTAL_SLICES_COUNT = (HORIZONTAL_SLICES + 1) * (VERTICAL_SLICES + 1); private Paint p = new Paint(); private float offsetInPercent; private Path pathLeft = new Path(); private Path pathRight = new Path(); private boolean isBounceAnimatin = false; private Paint paint = new Paint(); public RenderableTree(Bitmap bitmap, float x, float y, float alpha) { super(bitmap, x, y ); p.setColor(Color.BLACK); p.setStrokeWidth(6); p.setStyle(Paint.Style.STROKE); createVerts(); paint.setAlpha((int) (255*alpha)); } public void setScale(float scaleX, float scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; } private void createVerts() { float xDimesion = (float) bitmap.getWidth(); float yDimesion = (float) bitmap.getHeight(); int index = 0; for (int y = 0; y <= VERTICAL_SLICES; y++) { float fy = yDimesion * y / VERTICAL_SLICES; for (int x = 0; x <= HORIZONTAL_SLICES; x++) { float fx = xDimesion * x / HORIZONTAL_SLICES; setXY(drawingVerts, index, fx, fy); setXY(staticVerts, index, fx, fy); index += 1; } } } public void setXY(float[] array, int index, float x, float y) { array[index * 2 + 0] = x; array[index * 2 + 1] = y; } public void setXA(float[] array, int index, float x) { array[index * 2 + 0] = x; } public void setYA(float[] array, int index, float y) { array[index * 2 + 1] = staticVerts[index * 2 + 1] + y; } @Override public void draw(Canvas canvas) { createPath(); // alphaCanvas.drawPath(pathLeft, debugPaint); // alphaCanvas.drawPath(pathRight, debugPaint); canvas.save(); if (scaleX != 1.f || scaleY != 1f) { canvas.scale(scaleX, scaleY, x + bitmap.getWidth() / 2, y + bitmap.getHeight()); } canvas.drawBitmapMesh(bitmap, HORIZONTAL_SLICES, VERTICAL_SLICES, drawingVerts, 0, null, 0, paint); canvas.restore(); } private void createPath() { pathLeft.reset(); pathLeft.moveTo(x, y + bitmap.getHeight()); pathLeft.cubicTo(x, y + bitmap.getHeight(), x, y, x + bitmap.getWidth() * 1.5f * offsetInPercent, y); pathRight.reset(); pathRight.moveTo(x + bitmap.getWidth(), y + bitmap.getHeight()); pathRight.cubicTo(x + bitmap.getWidth(), y + bitmap.getHeight(), x + bitmap.getWidth(), y + bitmap.getWidth() * 0.3f * offsetInPercent, x + bitmap.getWidth() + bitmap.getWidth() / 2 * offsetInPercent, y + bitmap.getWidth() * 0.8f * offsetInPercent); matchVertsToPath(); } private void matchVertsToPath() { PathMeasure pmLeft = new PathMeasure(pathLeft, false); PathMeasure pmRight = new PathMeasure(pathRight, false); float[] coords = new float[2]; for (int i = 0; i < staticVerts.length / 2; i++) { float yIndexValue = staticVerts[i * 2 + 1]; float xIndexValue = staticVerts[i * 2]; if (xIndexValue == 0) { float percentOffsetY = (0.000001f + yIndexValue) / bitmap.getHeight(); pmLeft.getPosTan(pmLeft.getLength() * (1f - percentOffsetY), coords, null); setXY(drawingVerts, i, coords[0], coords[1]); } else { float percentOffsetY = (0.000001f + yIndexValue) / bitmap.getHeight(); pmRight.getPosTan(pmRight.getLength() * (1f - percentOffsetY), coords, null); setXY(drawingVerts, i, coords[0], coords[1]); } } } public void setOffsetPercent(float offset) { if (!isBounceAnimatin) offsetInPercent = offset ; } public boolean isBounceAnimatin() { return isBounceAnimatin; } public void cancelBounce() { isBounceAnimatin = false; } @Override public void update(float deltaTime, float wind) { super.update(deltaTime, wind); setOffsetPercent(wind/100f); } } ================================================ FILE: app/src/main/java/no/agens/depth/RootActivity.java ================================================ package no.agens.depth; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.app.Activity; import android.app.Fragment; import android.content.Intent; import android.graphics.BitmapFactory; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import no.agens.depth.lib.CircularSplashView; import no.agens.depth.lib.tween.interpolators.ExpoIn; import no.agens.depth.lib.tween.interpolators.QuintOut; public class RootActivity extends Activity { public static final int WATER_SCREEN_MENU_INDEX = 0; public static final int WIND_SCREEN_MENU_INDEX = 1; public static final int PLAYGROUND_SCREEN_MENU_INDEX = 2; public static final int ABOUT_SCREEN_MENU_INDEX = 3; Fragment currentFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_root); makeAppFullscreen(); if (savedInstanceState == null) { currentFragment = new WaterFragment(); getFragmentManager().beginTransaction().add(R.id.fragment_container, currentFragment).commit(); } setupMenu(); } public void setCurrentMenuIndex(int currentMenuIndex) { this.currentMenuIndex = currentMenuIndex; } int currentMenuIndex = 0; private void makeAppFullscreen() { getWindow().setStatusBarColor(Color.TRANSPARENT); getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } boolean isMenuVisible = false; ViewGroup menu; @Override public void onBackPressed() { if (isMenuVisible) { hideMenu(); ((MenuAnimation) currentFragment).revertFromMenu(); } else super.onBackPressed(); } public void showMenu() { isMenuVisible = true; ObjectAnimator translationY = ObjectAnimator.ofFloat(menu, View.TRANSLATION_Y, menu.getHeight(), 0); translationY.setDuration(1000); translationY.setInterpolator(new QuintOut()); translationY.setStartDelay(150); translationY.start(); selectMenuItem(currentMenuIndex, ((TextView) menu.getChildAt(currentMenuIndex).findViewById(R.id.item_text)).getCurrentTextColor()); ((MenuAnimation) currentFragment).animateTOMenu(); } public void hideMenu() { isMenuVisible = false; ObjectAnimator translationY = ObjectAnimator.ofFloat(menu, View.TRANSLATION_Y, menu.getHeight()); translationY.setDuration(750); translationY.setInterpolator(new ExpoIn()); translationY.start(); } private void setupMenu() { menu = (ViewGroup) findViewById(R.id.menu_container); int color = getResources().getColor(R.color.splash1); addMenuItem(menu, "Water And Noise", R.drawable.splash1, color, R.drawable.menu_btn, WATER_SCREEN_MENU_INDEX); addMenuItem(menu, "Two Bears", R.drawable.splash2, getResources().getColor(R.color.splash2), R.drawable.menu_btn2, WIND_SCREEN_MENU_INDEX); addMenuItem(menu, "Depth Playground", R.drawable.splash3, getResources().getColor(R.color.splash3), R.drawable.menu_btn3, PLAYGROUND_SCREEN_MENU_INDEX); addMenuItem(menu, "About", R.drawable.splash4, getResources().getColor(R.color.splash4), R.drawable.menu_btn4, ABOUT_SCREEN_MENU_INDEX); selectMenuItem(WATER_SCREEN_MENU_INDEX, color); menu.setTranslationY(20000); } private void addMenuItem(ViewGroup menu, String text, int drawableResource, int splashColor, int menu_btn, int menuIndex) { ViewGroup item = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.menu_item, menu, false); ((TextView) item.findViewById(R.id.item_text)).setText(text); CircularSplashView ic = (CircularSplashView) item.findViewById(R.id.circle); ic.setSplash(BitmapFactory.decodeResource(getResources(), drawableResource)); ic.setSplashColor(splashColor); item.setOnClickListener(getMenuItemCLick(menuIndex, splashColor)); if (menuIndex == WATER_SCREEN_MENU_INDEX) { int padding = (int) getResources().getDimension(R.dimen.menu_item_height_padding); menu.addView(item, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.menu_item_height) + padding)); item.setPadding(0, padding, 0, 0); } else if (menuIndex == ABOUT_SCREEN_MENU_INDEX) { int padding = (int) getResources().getDimension(R.dimen.menu_item_height_padding); menu.addView(item, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.menu_item_height) + padding)); item.setPadding(0, 0, 0, padding); } else menu.addView(item, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(R.dimen.menu_item_height))); item.setBackground(getResources().getDrawable(menu_btn, null)); } private View.OnClickListener getMenuItemCLick(final int menuIndex, final int color) { return new View.OnClickListener() { @Override public void onClick(View v) { if (menuIndex == currentMenuIndex) onBackPressed(); else if (menuIndex == WATER_SCREEN_MENU_INDEX && !(currentFragment instanceof WaterFragment)) { showWaterFragment(menuIndex, color); } else if (menuIndex == WIND_SCREEN_MENU_INDEX && !(currentFragment instanceof WindFragment)) { showWindFragment(menuIndex, color); } else if (menuIndex == PLAYGROUND_SCREEN_MENU_INDEX) { startActivity(new Intent(RootActivity.this, PlayGroundActivity.class)); onBackPressed(); } } }; } private void showWindFragment(int menuIndex, int color) { ((MenuAnimation) currentFragment).exitFromMenu(); WindFragment windFragment = new WindFragment(); windFragment.setIntroAnimate(true); goToFragment(windFragment); hideMenu(); selectMenuItem(menuIndex, color); } private void showWaterFragment(int menuIndex, int color) { ((MenuAnimation) currentFragment).exitFromMenu(); WaterFragment waterFragment = new WaterFragment(); waterFragment.setIntroAnimate(true); goToFragment(waterFragment); hideMenu(); selectMenuItem(menuIndex, color); } private void selectMenuItem(int menuIndex, int color) { for (int i = 0; i < menu.getChildCount(); i++) { View menuItem = menu.getChildAt(i); if (i == menuIndex) select(menuItem, color); else unSelect(menuItem); } currentMenuIndex = menuIndex; } private void unSelect(View menuItem) { final View circle = menuItem.findViewById(R.id.circle); circle.animate().scaleX(0).scaleY(0).setDuration(150).withEndAction(new Runnable() { @Override public void run() { circle.setVisibility(View.INVISIBLE); } }).start(); fadeColorTo(Color.BLACK, (TextView) menuItem.findViewById(R.id.item_text)); } private void fadeColorTo(int newColor, TextView view) { ObjectAnimator color = ObjectAnimator.ofObject(view, "TextColor", new ArgbEvaluator(), view.getCurrentTextColor(), newColor); color.setDuration(200); color.start(); } private void select(View menuItem, int color) { final CircularSplashView circle = (CircularSplashView) menuItem.findViewById(R.id.circle); circle.setScaleX(1f); circle.setScaleY(1f); circle.setVisibility(View.VISIBLE); circle.introAnimate(); fadeColorTo(color, (TextView) menuItem.findViewById(R.id.item_text)); } public void goToFragment(final Fragment newFragment) { getFragmentManager().beginTransaction().add(R.id.fragment_container, newFragment).commit(); final Fragment removeFragment = currentFragment; currentFragment = newFragment; getWindow().getDecorView().postDelayed(new Runnable() { @Override public void run() { getFragmentManager().beginTransaction().remove(removeFragment).commit(); } }, 2000); } } ================================================ FILE: app/src/main/java/no/agens/depth/SeekBarProgressChangeListener.java ================================================ package no.agens.depth; import android.widget.SeekBar; public abstract class SeekBarProgressChangeListener implements SeekBar.OnSeekBarChangeListener { @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } } ================================================ FILE: app/src/main/java/no/agens/depth/Smoke.java ================================================ package no.agens.depth; import android.animation.ValueAnimator; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import no.agens.depth.lib.headers.Renderable; public class Smoke extends Renderable { public static final float WIND_SENSITIVITY = 7f; float height, width; int numberOfTurns; float density; private final float[] drawingVerts = new float[TOTAL_SLICES_COUNT * 2]; private final float[] staticVerts = new float[TOTAL_SLICES_COUNT * 2]; private static final int HORIZONTAL_SLICES = 1; private static final int VERTICAL_SLICES = 80; private static final int TOTAL_SLICES_COUNT = (HORIZONTAL_SLICES + 1) * (VERTICAL_SLICES + 1); private void createVerts() { float xDimesion = (float) bitmap.getWidth(); float yDimesion = (float) bitmap.getHeight(); int index = 0; for (int y = 0; y <= VERTICAL_SLICES; y++) { float fy = yDimesion * y / VERTICAL_SLICES; for (int x = 0; x <= HORIZONTAL_SLICES; x++) { float fx = xDimesion * x / HORIZONTAL_SLICES; setXY(drawingVerts, index, fx, fy); setXY(staticVerts, index, fx, fy); index += 1; } } } public void setXY(float[] array, int index, float x, float y) { array[index * 2 + 0] = x; array[index * 2 + 1] = y; } public Smoke(Bitmap bitmap, float x, float y, float height, float width, int numberOfTurns, float density) { super(bitmap, x, y); this.height = height; this.width = width; this.numberOfTurns = numberOfTurns; paint.setStyle(Paint.Style.STROKE); this.density = density; createVerts(); createPath(); pathPointOffsetAnim = ValueAnimator.ofFloat(0, ((bitmap.getHeight() / (float) numberOfTurns) * 2f) / bitmap.getHeight()).setDuration(1500); pathPointOffsetAnim.setRepeatCount(ValueAnimator.INFINITE); pathPointOffsetAnim.setRepeatMode(ValueAnimator.RESTART); pathPointOffsetAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { pathPointOffset = (float) animation.getAnimatedValue(); } }); pathPointOffsetAnim.setInterpolator(new LinearInterpolator()); pathPointOffsetAnim.start(); createPath(); } ValueAnimator pathPointOffsetAnim; public void destroy() { super.destroy(); pathPointOffsetAnim.cancel(); } @Override public void pause() { super.pause(); pathPointOffsetAnim.pause(); } @Override public void resume() { super.resume(); pathPointOffsetAnim.resume(); } Path smokePath = new Path(); Paint paint = new Paint(); float pathPointOffset = 1; @Override public void draw(Canvas canvas) { // alphaCanvas.drawPath(smokePath, paint); canvas.drawBitmapMesh(bitmap, HORIZONTAL_SLICES, VERTICAL_SLICES, drawingVerts, 0, null, 0, paint); } public void setY(float y) { this.y = y; createPath(); } @Override public void update(float deltaTime, float wind) { matchVertsToPath(wind); } private void createPath() { smokePath.reset(); smokePath.moveTo(x, y); int step = (int) (height / numberOfTurns); boolean goLeft = true; for (int i = 0; i < numberOfTurns; i++) { if (goLeft) smokePath.cubicTo(x, y - step * i, x + width, y - step * i - step / 2, x, y - step * i - step); else smokePath.cubicTo(x, y - step * i, x - width, y - step * i - step / 2, x, y - step * i - step); goLeft = !goLeft; } } float[] coords = new float[2]; float[] coords2 = new float[2]; private void matchVertsToPath(float wind) { PathMeasure pm = new PathMeasure(smokePath, false); for (int i = 0; i < staticVerts.length / 2; i++) { float yIndexValue = staticVerts[i * 2 + 1]; float xIndexValue = staticVerts[i * 2]; float percentOffsetY = (0.000001f + yIndexValue) / bitmap.getHeight(); float percentOffsetY2 = (0.000001f + yIndexValue) / (bitmap.getHeight() + ((bitmap.getHeight() / numberOfTurns) * 4f)); percentOffsetY2 += pathPointOffset; pm.getPosTan(pm.getLength() * (1f - percentOffsetY), coords, null); pm.getPosTan(pm.getLength() * (1f - percentOffsetY2), coords2, null); if (xIndexValue == 0) { float desiredXCoord = coords2[0] - (bitmap.getWidth()) / 2; desiredXCoord -= (desiredXCoord - x) * percentOffsetY; desiredXCoord += (wind / 3f) * density + ((wind * WIND_SENSITIVITY) * (1f - smokeExponentionWindStuff.getInterpolation(percentOffsetY))); setXY(drawingVerts, i, desiredXCoord, coords[1]); } else { float desiredXCoord = coords2[0] + (bitmap.getWidth()) / 2; desiredXCoord -= (desiredXCoord - x) * percentOffsetY; desiredXCoord += (wind / 3f) * density + ((wind * WIND_SENSITIVITY) * (1f - smokeExponentionWindStuff.getInterpolation(percentOffsetY))); setXY(drawingVerts, i, desiredXCoord, coords[1]); } } } DecelerateInterpolator smokeExponentionWindStuff = new DecelerateInterpolator(1f); } ================================================ FILE: app/src/main/java/no/agens/depth/TransitionHelper.java ================================================ package no.agens.depth; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.support.annotation.NonNull; import android.view.View; import no.agens.depth.lib.DepthLayout; import no.agens.depth.lib.tween.interpolators.BackOut; import no.agens.depth.lib.tween.interpolators.CircInOut; import no.agens.depth.lib.tween.interpolators.ExpoIn; import no.agens.depth.lib.tween.interpolators.ExpoOut; import no.agens.depth.lib.tween.interpolators.QuadInOut; import no.agens.depth.lib.tween.interpolators.QuintInOut; import no.agens.depth.lib.tween.interpolators.QuintOut; public class TransitionHelper { public static final float TARGET_SCALE = 0.5f; public static final float TARGET_ROTATION = -50f; public static final float TARGET_ROTATION_X = 60f; public static final int MOVE_Y_STEP = 15; public static final int DURATION = 1100; public static final QuintOut VALUEinterpolator = new QuintOut(); public static final int FISRTDELAY = 300; public static void startIntroAnim(View root, AnimatorListenerAdapter introEndListener) { introAnimate((DepthLayout) root.findViewById(R.id.root_dl), 0, 30f, 15, 180); introAnimate((DepthLayout) root.findViewById(R.id.appbar), MOVE_Y_STEP, 20f, 30, 170); introAnimate((DepthLayout) root.findViewById(R.id.fab_container), MOVE_Y_STEP * 2f, 20f, 45, 190); introAnimate((DepthLayout) root.findViewById(R.id.dl2), MOVE_Y_STEP, 20f, 60, 200); introAnimate((DepthLayout) root.findViewById(R.id.dl3), MOVE_Y_STEP * 2, 20f, 75, 210).addListener(introEndListener); } static ObjectAnimator introAnimate(final DepthLayout target, final float moveY, final float customElevation, long delay, int subtractDelay) { target.setPivotY(getDistanceToCenter(target)); target.setPivotX(getDistanceToCenterX(target)); target.setCameraDistance(10000 * target.getResources().getDisplayMetrics().density); ObjectAnimator translationY2 = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, target.getResources().getDisplayMetrics().heightPixels, -moveY * target.getResources().getDisplayMetrics().density).setDuration(800); translationY2.setInterpolator(new ExpoOut()); translationY2.setStartDelay(700 + subtractDelay); translationY2.start(); target.setTranslationY(target.getResources().getDisplayMetrics().heightPixels); ObjectAnimator translationX2 = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, -target.getResources().getDisplayMetrics().widthPixels, 0).setDuration(800); translationX2.setInterpolator(new ExpoOut()); translationX2.setStartDelay(700 + subtractDelay); translationX2.start(); target.setTranslationX(-target.getResources().getDisplayMetrics().widthPixels); ObjectAnimator translationY = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, 0).setDuration(700); translationY.setInterpolator(new BackOut()); translationY.setStartDelay(700 + 800); translationY.start(); ObjectAnimator rotationX = ObjectAnimator.ofFloat(target, View.ROTATION_X, TARGET_ROTATION_X, 0).setDuration(1000); rotationX.setInterpolator(new QuintInOut()); rotationX.setStartDelay(700 + FISRTDELAY + subtractDelay); rotationX.start(); target.setRotationX(TARGET_ROTATION_X); ObjectAnimator elevation = ObjectAnimator.ofFloat(target, "CustomShadowElevation", customElevation * target.getResources().getDisplayMetrics().density, target.getCustomShadowElevation()).setDuration(1000); elevation.setInterpolator(new QuintInOut()); elevation.setStartDelay(700 + FISRTDELAY + subtractDelay * 2); elevation.start(); target.setCustomShadowElevation(customElevation * target.getResources().getDisplayMetrics().density); ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, TARGET_SCALE, target.getScaleX()).setDuration(1000); scaleX.setInterpolator(new CircInOut()); scaleX.setStartDelay(700 + FISRTDELAY + subtractDelay); scaleX.start(); target.setScaleX(TARGET_SCALE); ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, TARGET_SCALE, target.getScaleY()).setDuration(1000); scaleY.setInterpolator(new CircInOut()); scaleY.setStartDelay(700 + FISRTDELAY + subtractDelay); scaleY.start(); target.setScaleY(TARGET_SCALE); ObjectAnimator rotation = ObjectAnimator.ofFloat(target, View.ROTATION, TARGET_ROTATION, 0).setDuration(1400); rotation.setInterpolator(new QuadInOut()); rotation.setStartDelay(FISRTDELAY + subtractDelay); rotation.start(); target.setRotation(TARGET_ROTATION); rotation.addListener(getShowStatusBarListener(target)); return scaleY; } public static void startExitAnim(View root) { exitAnimate((DepthLayout) root.findViewById(R.id.root_dl), 0, 30f, 15, 190, true); exitAnimate((DepthLayout) root.findViewById(R.id.appbar), MOVE_Y_STEP, 20f, 30, 170, true); exitAnimate((DepthLayout) root.findViewById(R.id.fab_container), MOVE_Y_STEP * 2f, 20f, 45, 210, true); exitAnimate((DepthLayout) root.findViewById(R.id.dl2), MOVE_Y_STEP, 20f, 60, 230, true); exitAnimate((DepthLayout) root.findViewById(R.id.dl3), MOVE_Y_STEP * 2, 20f, 75, 250, true); hideStatusBar(root); } private static void hideStatusBar(View root) { // View decorView = ((Activity) root.getContext()).getWindow().getDecorView(); // int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; // decorView.setSystemUiVisibility(uiOptions); } static ValueAnimator exitAnimate(final DepthLayout target, final float moveY, final float customElevation, long delay, int subtractDelay, boolean continueOffscreen) { target.setPivotY(getDistanceToCenter(target)); target.setPivotX(getDistanceToCenterX(target)); target.setCameraDistance(10000 * target.getResources().getDisplayMetrics().density); ObjectAnimator rotationX = ObjectAnimator.ofFloat(target, View.ROTATION_X, TARGET_ROTATION_X).setDuration(DURATION); rotationX.setInterpolator(VALUEinterpolator); rotationX.setStartDelay(delay); rotationX.start(); ObjectAnimator elevation = ObjectAnimator.ofFloat(target, "CustomShadowElevation", target.getCustomShadowElevation(), customElevation * target.getResources().getDisplayMetrics().density).setDuration(DURATION); elevation.setInterpolator(VALUEinterpolator); elevation.setStartDelay(delay); elevation.start(); ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, TARGET_SCALE).setDuration(DURATION); scaleX.setInterpolator(new QuintOut()); scaleX.setStartDelay(delay); scaleX.start(); ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, TARGET_SCALE).setDuration(DURATION); scaleY.setInterpolator(new QuintOut()); scaleY.setStartDelay(delay); scaleY.start(); ObjectAnimator rotation = ObjectAnimator.ofFloat(target, View.ROTATION, TARGET_ROTATION).setDuration(1600); rotation.setInterpolator(VALUEinterpolator); rotation.setStartDelay(delay); rotation.start(); ObjectAnimator translationY = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, -moveY * target.getResources().getDisplayMetrics().density).setDuration(subtractDelay); translationY.setInterpolator(VALUEinterpolator); translationY.setStartDelay(delay); translationY.start(); if (continueOffscreen) { continueOutToRight(target, moveY, subtractDelay); } return scaleY; } private static void continueOutToRight(DepthLayout target, float moveY, int subtractDelay) { ObjectAnimator translationY2 = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, -moveY * target.getResources().getDisplayMetrics().density, -target.getResources().getDisplayMetrics().heightPixels).setDuration(900); translationY2.setInterpolator(new ExpoIn()); translationY2.setStartDelay(0 + subtractDelay); translationY2.start(); ObjectAnimator translationX2 = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, target.getTranslationX(), target.getResources().getDisplayMetrics().widthPixels).setDuration(900); translationX2.setInterpolator(new ExpoIn()); translationX2.setStartDelay(0 + subtractDelay); translationX2.start(); } public static float getDistanceToCenter(View target) { float viewCenter = target.getTop() + target.getHeight() / 2f; float rootCenter = ((View) target.getParent()).getHeight() / 2; return target.getHeight() / 2f + rootCenter - viewCenter; } public static float getDistanceToCenterX(View target) { float viewCenter = target.getLeft() + target.getWidth() / 2f; float rootCenter = ((View) target.getParent()).getWidth() / 2; return target.getWidth() / 2f + rootCenter - viewCenter; } public static void animateToMenuState(View root, AnimatorListenerAdapter onMenuAnimFinished) { hideStatusBar(root); exitAnimate((DepthLayout) root.findViewById(R.id.root_dl), 0, 30f, 15, 190, false); exitAnimate((DepthLayout) root.findViewById(R.id.appbar), MOVE_Y_STEP, 20f, 30, 170, false); exitAnimate((DepthLayout) root.findViewById(R.id.fab_container), MOVE_Y_STEP * 2f, 20f, 45, 210, false); exitAnimate((DepthLayout) root.findViewById(R.id.dl2), MOVE_Y_STEP, 20f, 60, 230, false); exitAnimate((DepthLayout) root.findViewById(R.id.dl3), MOVE_Y_STEP * 2, 20f, 75, 250, false).addListener(onMenuAnimFinished); ObjectAnimator translationY = ObjectAnimator.ofFloat(root, View.TRANSLATION_Y, -90f * root.getResources().getDisplayMetrics().density).setDuration(DURATION); translationY.setInterpolator(VALUEinterpolator); translationY.start(); } private static void makeAppFullscreen(View target) { // ((Activity) target.getContext()).getWindow().setStatusBarColor(Color.TRANSPARENT); // ((Activity) target.getContext()).getWindow().getDecorView().setSystemUiVisibility( // View.SYSTEM_UI_FLAG_LAYOUT_STABLE // | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } public static void animateMenuOut(View root) { continueOutToRight((DepthLayout) root.findViewById(R.id.root_dl), 0, 20); continueOutToRight((DepthLayout) root.findViewById(R.id.appbar), MOVE_Y_STEP, 0); continueOutToRight((DepthLayout) root.findViewById(R.id.fab_container), MOVE_Y_STEP * 2f, 40); continueOutToRight((DepthLayout) root.findViewById(R.id.dl2), MOVE_Y_STEP, 60); continueOutToRight((DepthLayout) root.findViewById(R.id.dl3), MOVE_Y_STEP * 2, 80); } public static void startRevertFromMenu(View root, AnimatorListenerAdapter animatorListenerAdapter) { revertFromMenu((DepthLayout) root.findViewById(R.id.root_dl), 30f, 10, 0); revertFromMenu((DepthLayout) root.findViewById(R.id.appbar), 20f, 0, 0); revertFromMenu((DepthLayout) root.findViewById(R.id.fab_container), 20f, 20, 6); revertFromMenu((DepthLayout) root.findViewById(R.id.dl2), 20f, 30, 1); revertFromMenu((DepthLayout) root.findViewById(R.id.dl3), 20f, 40, 2).addListener(animatorListenerAdapter); ObjectAnimator translationY = ObjectAnimator.ofFloat(root, View.TRANSLATION_Y, 0).setDuration(DURATION); translationY.setInterpolator(new QuintInOut()); translationY.start(); } static ObjectAnimator revertFromMenu(final DepthLayout target, final float customElevation, int subtractDelay, float targetElevation) { target.setPivotY(getDistanceToCenter(target)); target.setPivotX(getDistanceToCenterX(target)); target.setCameraDistance(10000 * target.getResources().getDisplayMetrics().density); ObjectAnimator translationY = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, 0).setDuration(700); translationY.setInterpolator(new BackOut()); translationY.setStartDelay(250 + FISRTDELAY + subtractDelay); translationY.start(); ObjectAnimator rotationX = ObjectAnimator.ofFloat(target, View.ROTATION_X, target.getRotationX(), 0).setDuration(1000); rotationX.setInterpolator(new QuintInOut()); rotationX.setStartDelay(FISRTDELAY + subtractDelay); rotationX.start(); target.setRotationX(TARGET_ROTATION_X); ObjectAnimator elevation = ObjectAnimator.ofFloat(target, "CustomShadowElevation", target.getCustomShadowElevation(), targetElevation * target.getResources().getDisplayMetrics().density).setDuration(1000); elevation.setInterpolator(new QuintInOut()); elevation.setStartDelay(FISRTDELAY + subtractDelay * 2); elevation.start(); target.setCustomShadowElevation(customElevation * target.getResources().getDisplayMetrics().density); ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, target.getScaleX(), 1f).setDuration(1000); scaleX.setInterpolator(new CircInOut()); scaleX.setStartDelay(FISRTDELAY + subtractDelay); scaleX.start(); target.setScaleX(TARGET_SCALE); ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, target.getScaleY(), 1f).setDuration(1000); scaleY.setInterpolator(new CircInOut()); scaleY.setStartDelay(FISRTDELAY + subtractDelay); scaleY.start(); target.setScaleY(TARGET_SCALE); ObjectAnimator rotation = ObjectAnimator.ofFloat(target, View.ROTATION, target.getRotation(), 0).setDuration(1100); rotation.setInterpolator(new QuintInOut()); rotation.setStartDelay(subtractDelay); rotation.start(); rotation.addListener(getShowStatusBarListener(target)); return scaleY; } @NonNull private static AnimatorListenerAdapter getShowStatusBarListener(final DepthLayout target) { return new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); makeAppFullscreen(target); } }; } } ================================================ FILE: app/src/main/java/no/agens/depth/Water.java ================================================ package no.agens.depth; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import no.agens.depth.lib.headers.PathBitmapMesh; import no.agens.depth.lib.headers.Renderable; public class Water extends Renderable { public static final int VERTS = 6; private float height, width; Foam[] foams = new Foam[4]; int numberOfWaves; PathBitmapMesh water; private float waveHeight; public Water(Bitmap bitmap, Bitmap foam, float y, float height, float width, int waves) { super(bitmap, 0, y); this.height = height; this.width = width; numberOfWaves = waves; debugPaint.setColor(Color.RED); debugPaint.setStyle(Paint.Style.STROKE); lastEmit = System.currentTimeMillis(); water = new PathBitmapMesh(VERTS, 1, bitmap, 1500); foams[0] = new Foam(VERTS, 1, foam, 0, height / 12, 1500); foams[1] = new Foam(VERTS, 1, foam, -height / 5, height / 5, 1500); foams[1].setAlpha(100); foams[2] = new Foam(VERTS, 1, foam, -height / 12, height / 12, 1450); foams[2].setVerticalOffset(height / 7); foams[3] = new Foam(VERTS, 1, foam, -height / 12, height / 12, 1400); foams[3].setVerticalOffset(height / 4); waveHeight = height / 10; createPath(); } @Override public void pause() { super.pause(); water.pause(); for (Foam foam : foams) foam.pause(); } @Override public void resume() { super.resume(); water.resume(); for (Foam foam : foams) foam.resume(); } @Override public void destroy() { super.destroy(); for (Foam foam : foams) foam.destroy(); } Paint debugPaint = new Paint(); @Override public void draw(Canvas canvas) { water.draw(canvas); for (Foam foam : foams) { foam.draw(canvas); } } @Override public void update(float deltaTime, float wind) { super.update(deltaTime, wind); for (Foam foam : foams) { foam.update(deltaTime); } water.matchVertsToPath(waterPath, height, ((bitmap.getWidth() / numberOfWaves) * 4f)); for (Foam foam : foams) { foam.matchVertsToPath(waterPath, foam.getBitmap().getWidth() / numberOfWaves * 4f); } if (lastEmit + emitInterWall < System.currentTimeMillis()) { for (Foam foam : foams) { foam.calcWave(); } lastEmit = System.currentTimeMillis(); } } private Path waterPath = new Path(); private void createPath() { waterPath.reset(); waterPath.moveTo(0, y); int step = (int) (width / numberOfWaves); boolean goLeft = true; for (int i = 0; i < numberOfWaves; i++) { if (goLeft) waterPath.cubicTo(x + step * i, y, x + step * i + step / 2f, y + waveHeight, x + step * i + step, y); else waterPath.cubicTo(x + step * i, y, x + step * i + step / 2f, y - waveHeight, x + step * i + step, y); goLeft = !goLeft; } } long lastEmit; private int emitInterWall = 1000; public void setWaveHeight(float waveHeight) { this.waveHeight = waveHeight; createPath(); } } ================================================ FILE: app/src/main/java/no/agens/depth/WaterFragment.java ================================================ package no.agens.depth; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.app.Fragment; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.ImageView; import android.widget.SeekBar; import no.agens.depth.lib.MaterialMenuDrawable; public class WaterFragment extends Fragment implements MenuAnimation { public static final int TRANSFORM_DURATION = 900; private boolean introAnimate; public WaterFragment() { } public void setIntroAnimate(boolean introAnimate) { this.introAnimate = introAnimate; } View root; MaterialMenuDrawable menuIcon; WaterSceneView waterScene; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { root = inflater.inflate(R.layout.fragment_water, container, false); waterScene = (WaterSceneView) root.findViewById(R.id.water_scene); setupFab(); introAnimate(); setupSeekbars(); setupMenuButton(); ((RootActivity) getActivity()).setCurrentMenuIndex(RootActivity.WATER_SCREEN_MENU_INDEX); return root; } private void setupSeekbars() { SeekBar waveSeekBar = (SeekBar) root.findViewById(R.id.wave_seekbar); SeekBar noiseSeekBar = (SeekBar) root.findViewById(R.id.noise_seekbar); WindFragment.setProgressBarColor(waveSeekBar, getResources().getColor(R.color.fab)); WindFragment.setProgressBarColor(noiseSeekBar, getResources().getColor(R.color.fab)); noiseSeekBar.setProgress(50); waveSeekBar.setProgress(50); waveSeekBar.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { waterScene.setWaveHeight(progress / 4f * getResources().getDisplayMetrics().density); } }); noiseSeekBar.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { waterScene.setNoiseIntensity((float) progress / 100f); } }); } private void setupMenuButton() { ImageView menu = (ImageView) root.findViewById(R.id.menu); menu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!((RootActivity) getActivity()).isMenuVisible) ((RootActivity) getActivity()).showMenu(); else ((RootActivity) getActivity()).onBackPressed(); } }); menuIcon = new MaterialMenuDrawable(getActivity(), Color.WHITE, MaterialMenuDrawable.Stroke.THIN, TRANSFORM_DURATION); menu.setImageDrawable(menuIcon); } private void introAnimate() { if (introAnimate) root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { root.getViewTreeObserver().removeOnGlobalLayoutListener(this); TransitionHelper.startIntroAnim(root, showShadowListener); hideShadow(); waterScene.postDelayed(new Runnable() { @Override public void run() { waterScene.setPause(true); } }, 10); } }); } private void setupFab() { root.findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { root.getViewTreeObserver().removeOnGlobalLayoutListener(this); TransitionHelper.startExitAnim(root); } }); WindFragment windFragment = new WindFragment(); windFragment.setIntroAnimate(true); ((RootActivity) getActivity()).goToFragment(windFragment); if (((RootActivity) getActivity()).isMenuVisible) ((RootActivity) getActivity()).hideMenu(); hideShadow(); waterScene.setPause(true); } }); } AnimatorListenerAdapter showShadowListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); showShadow(); waterScene.setPause(false); } }; private void hideShadow() { View actionbarShadow = root.findViewById(R.id.actionbar_shadow); actionbarShadow.setVisibility(View.GONE); } private void showShadow() { View actionbarShadow = root.findViewById(R.id.actionbar_shadow); actionbarShadow.setVisibility(View.VISIBLE); ObjectAnimator.ofFloat(actionbarShadow, View.ALPHA, 0, 0.8f).setDuration(400).start(); } @Override public void animateTOMenu() { TransitionHelper.animateToMenuState(root, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); waterScene.setPause(false); } }); menuIcon.animateIconState(MaterialMenuDrawable.IconState.ARROW); hideShadow(); waterScene.setPause(true); } @Override public void revertFromMenu() { TransitionHelper.startRevertFromMenu(root, showShadowListener); menuIcon.animateIconState(MaterialMenuDrawable.IconState.BURGER); waterScene.setPause(true); } @Override public void exitFromMenu() { TransitionHelper.animateMenuOut(root); waterScene.setPause(true); } } ================================================ FILE: app/src/main/java/no/agens/depth/WaterSceneView.java ================================================ package no.agens.depth; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; import no.agens.depth.lib.headers.NoiseEffect; import no.agens.depth.lib.headers.Renderable; import no.agens.depth.lib.tween.FrameRateCounter; /** * Created by danielzeller on 01.10.14. */ public class WaterSceneView extends View { private Renderable[] renderables; private Water water; private NoiseEffect noiseScratchEffect; private NoiseEffect noise; public WaterSceneView(Context context) { super(context); } public WaterSceneView(Context context, AttributeSet attrs) { super(context, attrs); } public WaterSceneView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (renderables == null) { init(); } } private void init() { renderables = new Renderable[4]; Bitmap waterBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.water); Bitmap foam = BitmapFactory.decodeResource(getResources(), R.drawable.foam); setLayerType(View.LAYER_TYPE_HARDWARE, null); water = new Water(waterBitmap, foam, getYCoordByPercent(0.65f), getYCoordByPercent(1f), getXCoordByPercent(1f), 6); renderables[0] = water; Bitmap aura = BitmapFactory.decodeResource(getResources(), R.drawable.sun_aura); renderables[1] = new Renderable(aura, getXCoordByPercent(0.5f), getYCoordByPercent(0.35f)); Bitmap noiseScratch = BitmapFactory.decodeResource(getResources(), R.drawable.noise_scratch); Bitmap noiseReg = BitmapFactory.decodeResource(getResources(), R.drawable.noise); noiseScratchEffect = new NoiseEffect(noiseScratch, 100, 2f); renderables[2] = noiseScratchEffect; noise = new NoiseEffect(noiseReg, 30, 1.5f); renderables[3] = noise; setNoiseIntensity(0.5f); setWaveHeight(50); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); destroyResources(); } private void destroyResources() { for (Renderable renderable : renderables) renderable.destroy(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (renderables == null && getWidth() != 0) init(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float deltaTime = FrameRateCounter.timeStep(); for (Renderable renderable : renderables) { renderable.draw(canvas); renderable.update(deltaTime, 0); } if (!pasuse) invalidate(); } public void setPause(boolean pause) { // this.pasuse = pause; // if (!pause) { // FrameRateCounter.timeStep(); // invalidate(); // for (Renderable renderable : renderables) // renderable.resume(); // } else { // for (Renderable renderable : renderables) // renderable.pause(); // } } private boolean pasuse = false; private float getYCoordByPercent(float percent) { return getHeight() * percent; } private float getXCoordByPercent(float percent) { return getWidth() * percent; } public void setWaveHeight(float height) { water.setWaveHeight(height); } public void setNoiseIntensity(float noiseAmount) { noiseScratchEffect.setNoiseIntensity(noiseAmount); noise.setNoiseIntensity(noiseAmount); } } ================================================ FILE: app/src/main/java/no/agens/depth/WindFragment.java ================================================ package no.agens.depth; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.app.Fragment; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.ImageView; import android.widget.SeekBar; import no.agens.depth.lib.MaterialMenuDrawable; public class WindFragment extends Fragment implements MenuAnimation { public static final int FLAMES_INITIAL_HEIGHT = 50; private boolean introAnimate; public WindFragment() { } View root; MaterialMenuDrawable menuIcon; BearSceneView bearsScene; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { root = inflater.inflate(R.layout.fragment_wind, container, false); bearsScene = (BearSceneView) root.findViewById(R.id.water_scene); doIntroAnimation(); setupFabButton(); setupMenuButton(); ((RootActivity) getActivity()).setCurrentMenuIndex(RootActivity.WIND_SCREEN_MENU_INDEX); setupSliders(); return root; } private void setupSliders() { SeekBar windSeekbar = (SeekBar) root.findViewById(R.id.wind_seekbar); final SeekBar flamesSeekbar = (SeekBar) root.findViewById(R.id.flames_seekbar); setProgressBarColor(windSeekbar, getResources().getColor(R.color.fab2)); setProgressBarColor(flamesSeekbar, getResources().getColor(R.color.fab2)); windSeekbar.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { bearsScene.setWind(progress); } }); flamesSeekbar.setOnSeekBarChangeListener(new SeekBarProgressChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { bearsScene.setFlamesHeight(progress); } }); bearsScene.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { bearsScene.getViewTreeObserver().removeOnGlobalLayoutListener(this); flamesSeekbar.setProgress(FLAMES_INITIAL_HEIGHT); } }); } private void doIntroAnimation() { if (introAnimate) root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { root.getViewTreeObserver().removeOnGlobalLayoutListener(this); TransitionHelper.startIntroAnim(root, showShadowListener); hideShadow(); bearsScene.postDelayed(new Runnable() { @Override public void run() { bearsScene.setPause(true); } }, 10); } }); } private void setupFabButton() { root.findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { root.getViewTreeObserver().removeOnGlobalLayoutListener(this); TransitionHelper.startExitAnim(root); } }); WaterFragment waterFragment = new WaterFragment(); waterFragment.setIntroAnimate(true); ((RootActivity) getActivity()).goToFragment(waterFragment); if (((RootActivity) getActivity()).isMenuVisible) ((RootActivity) getActivity()).hideMenu(); hideShadow(); bearsScene.setPause(true); } }); } private void setupMenuButton() { ImageView menu = (ImageView) root.findViewById(R.id.menu); menu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!((RootActivity) getActivity()).isMenuVisible) ((RootActivity) getActivity()).showMenu(); else getActivity().onBackPressed(); } }); menuIcon = new MaterialMenuDrawable(getActivity(), Color.WHITE, MaterialMenuDrawable.Stroke.THIN, WaterFragment.TRANSFORM_DURATION); menu.setImageDrawable(menuIcon); } public static void setProgressBarColor(SeekBar progressBar, int newColor) { if (progressBar.getProgressDrawable() instanceof StateListDrawable) { StateListDrawable ld = (StateListDrawable) progressBar.getProgressDrawable(); ld.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); progressBar.getThumb().setColorFilter(newColor, PorterDuff.Mode.SRC_IN); } else if (progressBar.getProgressDrawable() instanceof LayerDrawable) { LayerDrawable ld = (LayerDrawable) progressBar.getProgressDrawable(); for (int i = 0; i < ld.getNumberOfLayers(); i++) { Drawable d1 = ld.getDrawable(i); d1.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); } progressBar.getThumb().setColorFilter(newColor, PorterDuff.Mode.SRC_IN); } } public void setIntroAnimate(boolean introAnimate) { this.introAnimate = introAnimate; } @Override public void animateTOMenu() { TransitionHelper.animateToMenuState(root, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); bearsScene.setPause(false); } }); menuIcon.animateIconState(MaterialMenuDrawable.IconState.ARROW); hideShadow(); bearsScene.setPause(true); } private void hideShadow() { View actionbarShadow = root.findViewById(R.id.actionbar_shadow); actionbarShadow.setVisibility(View.GONE); } @Override public void revertFromMenu() { TransitionHelper.startRevertFromMenu(root, showShadowListener); menuIcon.animateIconState(MaterialMenuDrawable.IconState.BURGER); bearsScene.setPause(true); } AnimatorListenerAdapter showShadowListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); showShadow(); bearsScene.setPause(false); } }; private void showShadow() { View actionbarShadow = root.findViewById(R.id.actionbar_shadow); actionbarShadow.setVisibility(View.VISIBLE); ObjectAnimator.ofFloat(actionbarShadow, View.ALPHA, 0, 0.8f).setDuration(400).start(); } @Override public void exitFromMenu() { TransitionHelper.animateMenuOut(root); bearsScene.setPause(true); } } ================================================ FILE: app/src/main/res/drawable/bear_bg_gradient.xml ================================================ ================================================ FILE: app/src/main/res/drawable/circle.xml ================================================ ================================================ FILE: app/src/main/res/drawable/fab_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/menu_btn.xml ================================================ ================================================ FILE: app/src/main/res/drawable/menu_btn2.xml ================================================ ================================================ FILE: app/src/main/res/drawable/menu_btn3.xml ================================================ ================================================ FILE: app/src/main/res/drawable/menu_btn4.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_root.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_playground.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_water.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_wind.xml ================================================ ================================================ FILE: app/src/main/res/layout/menu_item.xml ================================================ ================================================ FILE: app/src/main/res/menu/menu_root.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #8C9CA4 #FFFFFF #DBDBDB #FFFFFF #f9aab0 #CB747B #323453 #4E8186 #282942 #3E676B #727272 #000000 #DC8F9A #F19BA2 #323453 #649692 #C68C94 #E89183 #FCFBEB ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 16dp 16dp 16dp 80dp 10dp 11dp 12dp 8dp 53dp 6dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ Depth Hello world! Settings Depth Demo ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Oct 16 12:59:36 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-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: lib/.gitignore ================================================ /build ================================================ FILE: lib/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' android { compileSdkVersion 28 defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.12' } ext { bintrayRepo = 'maven' bintrayName = 'depth-lib-android' publishedGroupId = 'no.agens' libraryName = 'Depth-Lib-Android' artifact = 'depthlib' libraryDescription = 'This library adds depth/thickness to views.' siteUrl = 'https://github.com/danielzeller/Depth-LIB-Android-' gitUrl = 'https://github.com/danielzeller/Depth-LIB-Android-.git' libraryVersion = '1.0.1' developerId = 'danielzeller' developerName = 'Daniel Zeller' developerEmail = 'daniel@danielzeller.no' licenseName = 'The Apache Software License, Version 2.0' licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' allLicenses = ["Apache-2.0"] } group = publishedGroupId version = libraryVersion install { repositories.mavenInstaller { pom.project { packaging 'aar' groupId publishedGroupId artifactId artifact name libraryName description libraryDescription url siteUrl licenses { license { name licenseName url licenseUrl } } developers { developer { id developerId name developerName email developerEmail } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } task sourcesJar(type: Jar) { classifier = 'sources' from android.sourceSets.main.java.srcDirs } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { archives javadocJar archives sourcesJar } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) bintray { user = properties.getProperty("bintray.user") key = properties.getProperty("bintray.apikey") configurations = ['archives'] pkg { repo = bintrayRepo name = bintrayName desc = libraryDescription websiteUrl = siteUrl vcsUrl = gitUrl licenses = allLicenses dryRun = false publish = true override = false publicDownloadNumbers = true version { desc = libraryDescription } } } ================================================ FILE: lib/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in C:\Users\Jawn\Develop\Android\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: lib/src/androidTest/java/no/agens/depth/ApplicationTest.java ================================================ package no.agens.depth; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: lib/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/src/main/java/no/agens/depth/lib/CircularSplashView.java ================================================ package no.agens.depth.lib; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; import no.agens.depth.lib.tween.interpolators.QuintOut; /** * Created by danielzeller on 03.09.14. */ public class CircularSplashView extends View { private final List circles = new ArrayList<>(); public CircularSplashView(Context context) { super(context); } public CircularSplashView(Context context, AttributeSet attrs) { super(context, attrs); } public CircularSplashView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setSplash(Bitmap splash) { this.splash = splash; } public void setSplashColor(int splashColor) { this.splashColor = splashColor; } private Bitmap splash; private int splashColor; @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (circles.size() == 0) { introAnimate(); } } public void introAnimate() { circles.clear(); RectF expandedSizeFloat = new RectF(0, 0, getWidth(), getHeight()); Rect expandedSizeInt = new Rect(0, 0, getWidth(), getHeight()); RectF biggerRect = new RectF(-1, -1, getWidth() + 1, getHeight() + 1); circles.add(new CircleColorExpand(expandedSizeFloat, 0, 600, splashColor)); circles.add(new CircleColorExpand(biggerRect, 70, 600, Color.WHITE)); circles.add(new CircleBitmapExpand(expandedSizeInt, 130, 800, splash)); for (CircledDrawable c : circles) c.startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (CircledDrawable circle : circles) circle.draw(canvas); } interface CircledDrawable { void startAnim(); void draw(Canvas canvas); } public class CircleBitmapExpand implements CircledDrawable { Rect targetSize; Rect drawingRect; long startDelay; long animDuration; Bitmap bitmap; public CircleBitmapExpand(Rect targetSize, long startDelay, long animDuration, Bitmap inBitmap) { this.targetSize = targetSize; this.startDelay = startDelay; this.animDuration = animDuration; bitmap = inBitmap; } public Bitmap GetBitmapClippedCircle(Bitmap bitmap) { final int width = bitmap.getWidth(); final int height = bitmap.getHeight(); final Bitmap outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Path path = new Path(); path.addCircle( (float) (width / 2) , (float) (height / 2) , (float) Math.min(width, (height / 2)) , Path.Direction.CCW); final Canvas canvas = new Canvas(outputBitmap); canvas.clipPath(path); canvas.drawBitmap(bitmap, 0, 0, null); bitmap.recycle(); return outputBitmap; } public void startAnim() { Rect startRect = new Rect(targetSize.centerX(), targetSize.centerY(), targetSize.centerX(), targetSize.centerY()); drawingRect = startRect; ValueAnimator rectSize = ValueAnimator.ofObject(new RectEvaluator(), startRect, targetSize); rectSize.setDuration(animDuration); rectSize.setInterpolator(new QuintOut()); rectSize.setStartDelay(startDelay); rectSize.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { drawingRect = (Rect) animation.getAnimatedValue(); invalidate(); } }); rectSize.start(); } public void draw(Canvas canvas) { if (drawingRect != null) canvas.drawBitmap(bitmap, null, drawingRect, null); } } public class CircleColorExpand implements CircledDrawable { private Paint paint = new Paint(Color.BLACK); RectF targetSize; RectF drawingRect; long startDelay; long animDuration; public CircleColorExpand(RectF targetSize, long startDelay, long animDuration, int paintColor) { this.targetSize = targetSize; this.startDelay = startDelay; this.animDuration = animDuration; paint.setColor(paintColor); paint.setAntiAlias(true); paint.setDither(true); } public void startAnim() { RectF startRect = new RectF(targetSize.centerX(), targetSize.centerY(), targetSize.centerX(), targetSize.centerY()); ValueAnimator rectSize = ValueAnimator.ofObject(new RectFEvaluator(), startRect, targetSize); rectSize.setDuration(animDuration); rectSize.setInterpolator(new QuintOut()); rectSize.setStartDelay(startDelay); rectSize.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { drawingRect = (RectF) animation.getAnimatedValue(); invalidate(); } }); rectSize.start(); } public void draw(Canvas canvas) { if (drawingRect != null) canvas.drawOval(drawingRect, paint); } } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/ColorAnimator.java ================================================ package no.agens.depth.lib; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.view.View; public class ColorAnimator { public static ObjectAnimator ofColor(Object target, String propertyName, int from, int to) { return ObjectAnimator.ofObject(target, propertyName, new ColorEvaluator(), from, to); } public static ObjectAnimator ofColor(Object target, String propertyName, int to) { return ObjectAnimator.ofObject(target, propertyName, new ColorEvaluator(), to); } public static ObjectAnimator ofBackgroundColor(View target, int from, int to) { return ObjectAnimator.ofObject(new ViewBackgroundWrapper(target), "backgroundColor", new ColorEvaluator(), from, to); } public static ObjectAnimator ofBackgroundColor(View target, int to) { return ObjectAnimator.ofObject(new ViewBackgroundWrapper(target), "backgroundColor", new ColorEvaluator(), to); } private static class ColorEvaluator implements TypeEvaluator { @Override public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startA, startR, startG, startB; int aDelta = (int) ((Color.alpha(endValue) - (startA = Color.alpha(startValue))) * fraction); int rDelta = (int) ((Color.red(endValue) - (startR = Color.red(startValue))) * fraction); int gDelta = (int) ((Color.green(endValue) - (startG = Color.green(startValue))) * fraction); int bDelta = (int) ((Color.blue(endValue) - (startB = Color.blue(startValue))) * fraction); return Color.argb(startA + aDelta, startR + rDelta, startG + gDelta, startB + bDelta); } } /** * Helper class which allows retrieval of a {@link View}'s background as a color. */ public static class ViewBackgroundWrapper { private final View mView; public ViewBackgroundWrapper(View v) { mView = v; } public int getBackgroundColor() { try { return ((ColorDrawable) mView.getBackground()).getColor(); } catch (ClassCastException cce) { // The background isn't a ColorDrawable (could be BitmapDrawable etc.) - throw a more descriptive error throw new IllegalStateException( String.format("Attempt to read View background color when background isn't a ColorDrawable (is %s instead)", mView.getBackground().getClass().getSimpleName())); } } public void setBackgroundColor(int color) { mView.setBackgroundColor(color); } } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/CustomShadow.java ================================================ package no.agens.depth.lib; 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; /** * A rounded rectangle drawable which also includes a shadow around. */ public class CustomShadow extends Drawable { final static double COS_45 = Math.cos(Math.toRadians(45)); final static float SHADOW_MULTIPLIER = 1.5f; final int mInsetShadow; // extra shadow to avoid gaps between card and shadow /* * This helper is set by CardView implementations. *

* Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface * to draw efficient rounded rectangles before 17. * */ static RoundRectHelper sRoundRectHelper; final Paint mPaint; 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 CustomShadow( Resources resources, int backgroundColor, float radius, float shadowSize, float maxShadowSize ) { mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color); mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color); mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setColor(backgroundColor); mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mCornerShadowPaint.setStyle(Paint.Style.FILL); mCornerRadius = (int) (radius + .5f); mCardBounds = new RectF(); mEdgeShadowPaint = new Paint(mCornerShadowPaint); mEdgeShadowPaint.setAntiAlias(false); setShadowSize(shadowSize, maxShadowSize); CustomShadow.sRoundRectHelper = new CustomShadow.RoundRectHelper() { @Override public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint) { canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint); } }; } /** * Casts the value to an even integer. */ private int toEven(float value) { int i = (int) (value + .5f); if (i % 2 == 1) { return i - 1; } return i; } public void setAddPaddingForCorners(boolean addPaddingForCorners) { mAddPaddingForCorners = addPaddingForCorners; invalidateSelf(); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(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"); } shadowSize = toEven(shadowSize); maxShadowSize = toEven(maxShadowSize); if (shadowSize > maxShadowSize) { shadowSize = maxShadowSize; if (!mPrintedShadowClipWarning) { mPrintedShadowClipWarning = true; } } if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) { return; } mRawShadowSize = shadowSize; mRawMaxShadowSize = maxShadowSize; mShadowSize = (int) (shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f); 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); padding.set(0, vOffset, 0, 0); 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) { mPaint.setColorFilter(cf); mCornerShadowPaint.setColorFilter(cf); mEdgeShadowPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } void setCornerRadius(float radius) { radius = (int) (radius + .5f); if (mCornerRadius == radius) { return; } mCornerRadius = radius; mDirty = true; invalidateSelf(); } @Override public void draw(Canvas canvas) { if (mDirty) { buildComponents(getBounds()); mDirty = false; } canvas.translate(0, -mRawShadowSize / 2); drawShadow(canvas); canvas.translate(0, +mRawShadowSize / 2); sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint); } private void drawShadow(Canvas canvas) { final float edgeShadowTop = -mCornerRadius - mShadowSize; final float insetVertical = mCornerRadius + mInsetShadow + mRawShadowSize / 2; final float insetHorizontal = -mInsetShadow; // LT top int saved = canvas.save(); canvas.translate(mCardBounds.left + insetHorizontal, mCardBounds.top + insetVertical); canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); canvas.drawRect(0, edgeShadowTop, mCardBounds.width() - 2 * insetHorizontal, -mCornerRadius + mShadowSize, mEdgeShadowPaint); canvas.restoreToCount(saved); // RT right saved = canvas.save(); canvas.translate(mCardBounds.right - insetHorizontal, mCardBounds.top + insetVertical); canvas.rotate(90f); canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 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)); mEdgeShadowPaint.setAntiAlias(false); } 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 = mRawMaxShadowSize * SHADOW_MULTIPLIER; mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset, bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset); buildShadowCorners(); } float getCornerRadius() { return mCornerRadius; } void getMaxShadowAndCornerPadding(Rect into) { getPadding(into); } void setShadowSize(float size) { setShadowSize(size, mRawMaxShadowSize); } void setMaxShadowSize(float size) { setShadowSize(mRawShadowSize, size); } float getShadowSize() { return mRawShadowSize; } float getMaxShadowSize() { return mRawMaxShadowSize; } 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; } public void setColor(int color) { mPaint.setColor(color); invalidateSelf(); } interface RoundRectHelper { void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/DepthLayout.java ================================================ package no.agens.depth.lib; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; import android.widget.RelativeLayout; public class DepthLayout extends RelativeLayout { public static final int DEFAULT_EDGE_COLOR = Color.WHITE; public static final int DEFAULT_THICKNESS = 2; Paint edgePaint = new Paint(); public DepthLayout(Context context) { super(context); initView(null); } @Override public boolean hasOverlappingRendering() { return false; } public Paint getEdgePaint() { return edgePaint; } public void setEdgePaint(Paint edgePaint) { this.edgePaint = edgePaint; } private void initView(AttributeSet attrs) { edgePaint.setColor(DEFAULT_EDGE_COLOR); edgePaint.setAntiAlias(true); TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.DepthView); if (attrs != null) { try { edgePaint.setColor(arr.getInt(R.styleable.DepthView_edge_color, DEFAULT_EDGE_COLOR)); setIsCircle(arr.getBoolean(R.styleable.DepthView_is_circle, false)); depth = arr.getDimension(R.styleable.DepthView_depth, DEFAULT_THICKNESS * getResources().getDisplayMetrics().density); customShadowElevation = arr.getDimension(R.styleable.DepthView_custom_elevation, 0); } finally { arr.recycle(); } } else { edgePaint.setColor(DEFAULT_EDGE_COLOR); depth = DEFAULT_THICKNESS * getResources().getDisplayMetrics().density; } setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { } }); } public DepthLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(attrs); } public DepthLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(attrs); } private float depth; public float getDepth() { return depth; } public void setDepth(float depth) { this.depth = depth; ((View) getParent()).invalidate(); } public boolean isCircle() { return isCircle; } public void setIsCircle(boolean isCircle) { this.isCircle = isCircle; } private boolean isCircle = false; float[] prevSrc = new float[8]; public boolean calculateBounds() { float[] src = new float[8]; float[] dst = new float[]{0, 0, getWidth(), 0, 0, getHeight(), getWidth(), getHeight()}; Matrix matrix = getMatrix(); matrix.mapPoints(src, dst); topLeft.x = src[0] + getLeft(); topLeft.y = src[1] + getTop(); topRight.x = src[2] + getLeft(); topRight.y = src[3] + getTop(); bottomLeft.x = src[4] + getLeft(); bottomLeft.y = src[5] + getTop(); bottomRight.x = src[6] + getLeft(); bottomRight.y = src[7] + getTop(); boolean returnValue = hasMatrixChanged(src); prevSrc = src; float percentFrom90X = (getRotationX()) / 90f; float percentFrom90Y = (-getRotationY()) / 90f; matrix.postTranslate(percentFrom90Y * getDepth(), percentFrom90X * getDepth()); src = new float[8]; dst = new float[]{0, 0, getWidth(), 0, 0, getHeight(), getWidth(), getHeight()}; matrix.mapPoints(src, dst); topLeftBack.x = src[0] + getLeft(); topLeftBack.y = src[1] + getTop(); topRightBack.x = src[2] + getLeft(); topRightBack.y = src[3] + getTop(); bottomLeftBack.x = src[4] + getLeft(); bottomLeftBack.y = src[5] + getTop(); bottomRightBack.x = src[6] + getLeft(); bottomRightBack.y = src[7] + getTop(); customShadow.calculateBounds(this); return returnValue; } boolean hasMatrixChanged(float[] newSrc) { for (int i = 0; i < 8; i++) { if (newSrc[i] != prevSrc[i]) return true; } return false; } public PointF getTopLeft() { return topLeft; } public PointF getTopRight() { return topRight; } public PointF getBottomLeft() { return bottomLeft; } public PointF getBottomRight() { return bottomRight; } public PointF getTopLeftBack() { return topLeftBack; } public PointF getTopRightBack() { return topRightBack; } public PointF getBottomLeftBack() { return bottomLeftBack; } public PointF getBottomRightBack() { return bottomRightBack; } final PointF topLeft = new PointF(0, 0); PointF topRight = new PointF(0, 0); PointF bottomLeft = new PointF(0, 0); PointF bottomRight = new PointF(0, 0); PointF topLeftBack = new PointF(0, 0); PointF topRightBack = new PointF(0, 0); PointF bottomLeftBack = new PointF(0, 0); PointF bottomRightBack = new PointF(0, 0); private CustomShadow customShadow = new CustomShadow(); public CustomShadow getCustomShadow() { return customShadow; } public void setCustomShadowElevation(float customShadowElevation) { this.customShadowElevation = customShadowElevation; ((View) getParent()).invalidate(); } public float getCustomShadowElevation() { return customShadowElevation; } float customShadowElevation; class CustomShadow { public static final float DEFAULT_SHADOW_PADDING = 10f; PointF topLeftBack = new PointF(0, 0); PointF topRightBack = new PointF(0, 0); PointF bottomLeftBack = new PointF(0, 0); PointF bottomRightBack = new PointF(0, 0); int padding; public void calculateBounds(DepthLayout target) { float[] src = new float[8]; float density = getResources().getDisplayMetrics().density; float offsetY = customShadowElevation; float offsetX = customShadowElevation / 5; padding = (int) (customShadowElevation / 4f + DEFAULT_SHADOW_PADDING * density); float[] dst = new float[]{-padding, -padding, target.getWidth() + padding, -padding, -padding, target.getHeight() + padding, target.getWidth() + padding, target.getHeight() + padding}; Matrix matrix = getMatrix(); matrix.mapPoints(src, dst); topLeftBack.x = src[0] + target.getLeft() + offsetX; topLeftBack.y = src[1] + target.getTop() + offsetY; topRightBack.x = src[2] + target.getLeft() + offsetX; topRightBack.y = src[3] + target.getTop() + offsetY; bottomLeftBack.x = src[4] + target.getLeft() + offsetX; bottomLeftBack.y = src[5] + target.getTop() + offsetY; bottomRightBack.x = src[6] + target.getLeft() + offsetX; bottomRightBack.y = src[7] + target.getTop() + offsetY; } Matrix matrix = new Matrix(); public void drawShadow(Canvas canvas, DepthLayout dl, Drawable shadow) { shadow.setBounds(-padding, -padding, dl.getWidth() + padding, dl.getHeight() + padding); float[] src = new float[]{0, 0, dl.getWidth(), 0, dl.getWidth(), dl.getHeight(), 0, dl.getHeight()}; float[] dst = new float[]{topLeftBack.x, topLeftBack.y, topRightBack.x, topRightBack.y, bottomRightBack.x, bottomRightBack.y, bottomLeftBack.x, bottomLeftBack.y}; int count = canvas.save(); matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.concat(matrix); shadow.draw(canvas); canvas.restoreToCount(count); } } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/DepthRendrer.java ================================================ package no.agens.depth.lib; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewTreeObserver; import android.widget.RelativeLayout; public class DepthRendrer extends RelativeLayout { private final Paint shadowPaint = new Paint(); private NinePatchDrawable softShadow; private Drawable roundSoftShadow; private Path edgePath = new Path(); public DepthRendrer(Context context) { super(context); setup(); } public DepthRendrer(Context context, AttributeSet attrs) { super(context, attrs); setup(); } public DepthRendrer(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setup(); } public DepthRendrer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setup(); } public float getTopEdgeLength(DepthLayout dl) { return getDistance(dl.getTopLeftBack(), dl.getTopRightBack()); } float getDistance(PointF p1, PointF p2) { return (float) Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); } public float getShadowAlpha() { return shadowAlpha; } public void setShadowAlpha(float shadowAlpha) { this.shadowAlpha = Math.min(1f, Math.max(0, shadowAlpha)); } private float shadowAlpha = 0.3f; void setup() { getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child instanceof DepthLayout) { boolean hasChangedBounds = ((DepthLayout) child).calculateBounds(); if (hasChangedBounds) invalidate(); } } return true; } }); shadowPaint.setColor(Color.BLACK); shadowPaint.setAntiAlias(true); softShadow = (NinePatchDrawable) getResources().getDrawable(R.drawable.shadow, null); roundSoftShadow = getResources().getDrawable(R.drawable.round_soft_shadow, null); } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { if (child instanceof DepthLayout && !isInEditMode()) { DepthLayout dl = (DepthLayout) child; float[] src = new float[]{0, 0, dl.getWidth(), 0, dl.getWidth(), dl.getHeight(), 0, dl.getHeight()}; if (dl.isCircle()) { dl.getCustomShadow().drawShadow(canvas, dl, roundSoftShadow); if (Math.abs(dl.getRotationX()) > 1 || Math.abs(dl.getRotationY()) > 1) drawCornerBaseShape(dl, canvas, src); } else { dl.getCustomShadow().drawShadow(canvas, dl, softShadow); if (dl.getRotationX() != 0 || dl.getRotationY() != 0) { if (getLongestHorizontalEdge(dl) > getLongestVerticalEdge(dl)) drawVerticalFirst(dl, canvas, src); else drawHorizontalFist(dl, canvas, src); } } } return super.drawChild(canvas, child, drawingTime); } private void drawCornerBaseShape(DepthLayout dl, Canvas canvas, float[] src) { float[] dst = new float[]{dl.getTopLeftBack().x, dl.getTopLeftBack().y, dl.getTopRightBack().x, dl.getTopRightBack().y, dl.getBottomRightBack().x, dl.getBottomRightBack().y, dl.getBottomLeftBack().x, dl.getBottomLeftBack().y}; int count = canvas.save(); matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.concat(matrix); edgePath.reset(); edgePath.addRoundRect(0, 0, dl.getWidth(), dl.getHeight(), dl.getWidth() / 2f, dl.getHeight() / 2f, Path.Direction.CCW); canvas.drawPath(edgePath, dl.getEdgePaint()); shadowPaint.setAlpha((int) (shadowAlpha * 0.5f * 255)); canvas.drawPath(edgePath, shadowPaint); canvas.restoreToCount(count); } private void drawHorizontalFist(DepthLayout dl, Canvas canvas, float[] src) { if (getLeftEdgeLength(dl) <= getRightEdgeLength(dl)) { drawLeftEdge(dl, canvas, src); } else { drawRightEdge(dl, canvas, src); } drawTopEdge(dl, canvas, src); drawBottomEdge(dl, canvas, src); if (getLeftEdgeLength(dl) >= getRightEdgeLength(dl)) { drawLeftEdge(dl, canvas, src); } else { drawRightEdge(dl, canvas, src); } } private void drawVerticalFirst(DepthLayout dl, Canvas canvas, float[] src) { if (getTopEdgeLength(dl) <= getBottomEdgeLength(dl)) { drawTopEdge(dl, canvas, src); } else { drawBottomEdge(dl, canvas, src); } drawLeftEdge(dl, canvas, src); drawRightEdge(dl, canvas, src); if (getTopEdgeLength(dl) >= getBottomEdgeLength(dl)) { drawTopEdge(dl, canvas, src); } else { drawBottomEdge(dl, canvas, src); } } float getLongestHorizontalEdge(DepthLayout dl) { float topEdgeLength = getTopEdgeLength(dl); float bottomEdgeLength = getBottomEdgeLength(dl); if (topEdgeLength > bottomEdgeLength) { return topEdgeLength; } else { return bottomEdgeLength; } } float getLongestVerticalEdge(DepthLayout dl) { float leftEdgeLength = getLeftEdgeLength(dl); float rightEdgeLength = getRightEdgeLength(dl); if (leftEdgeLength > rightEdgeLength) { return leftEdgeLength; } else { return rightEdgeLength; } } private float getRightEdgeLength(DepthLayout dl) { return getDistance(dl.getTopRightBack(), dl.getBottomRightBack()); } private float getLeftEdgeLength(DepthLayout dl) { return getDistance(dl.getTopLeftBack(), dl.getBottomLeftBack()); } private float getBottomEdgeLength(DepthLayout dl) { return getDistance(dl.getBottomLeftBack(), dl.getBottomRightBack()); } void drawShadow(PointF point1, PointF point2, float correctionValue, Canvas canvas, DepthLayout dl) { float angle = Math.abs(Math.abs(getAngle(point1, point2)) + correctionValue); float alpha = angle / 180f; shadowPaint.setAlpha((int) (alpha * 255f * shadowAlpha)); canvas.drawRect(0, 0, dl.getWidth(), dl.getHeight(), shadowPaint); } private void drawRectancle(DepthLayout dl, Canvas canvas) { canvas.drawRect(0, 0, dl.getWidth(), dl.getHeight(), dl.getEdgePaint()); } public float getAngle(PointF point1, PointF point2) { return (float) Math.toDegrees(Math.atan2(point1.y - point2.y, point1.x - point2.x)); } private void drawLeftEdge(DepthLayout dl, Canvas canvas, float[] src) { float[] dst = new float[]{dl.getTopLeft().x, dl.getTopLeft().y, dl.getTopLeftBack().x, dl.getTopLeftBack().y, dl.getBottomLeftBack().x, dl.getBottomLeftBack().y, dl.getBottomLeft().x, dl.getBottomLeft().y}; int count = canvas.save(); matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.concat(matrix); drawRectancle(dl, canvas); drawShadow(dl.getTopLeft(), dl.getBottomLeft(), 0, canvas, dl); canvas.restoreToCount(count); } private void drawRightEdge(DepthLayout dl, Canvas canvas, float[] src) { float[] dst = new float[]{dl.getTopRight().x, dl.getTopRight().y, dl.getTopRightBack().x, dl.getTopRightBack().y, dl.getBottomRightBack().x, dl.getBottomRightBack().y, dl.getBottomRight().x, dl.getBottomRight().y}; int count = canvas.save(); matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.concat(matrix); drawRectancle(dl, canvas); drawShadow(dl.getTopRight(), dl.getBottomRight(), -180f, canvas, dl); canvas.restoreToCount(count); } android.graphics.Matrix matrix = new android.graphics.Matrix(); private void drawTopEdge(DepthLayout dl, Canvas canvas, float[] src) { float[] dst = new float[]{dl.getTopLeft().x, dl.getTopLeft().y, dl.getTopRight().x, dl.getTopRight().y, dl.getTopRightBack().x, dl.getTopRightBack().y, dl.getTopLeftBack().x, dl.getTopLeftBack().y}; int count = canvas.save(); matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.concat(matrix); drawRectancle(dl, canvas); drawShadow(dl.getTopLeft(), dl.getTopRight(), -180f, canvas, dl); canvas.restoreToCount(count); } private void drawBottomEdge(DepthLayout dl, Canvas canvas, float[] src) { float[] dst = new float[]{dl.getBottomLeft().x, dl.getBottomLeft().y, dl.getBottomRight().x, dl.getBottomRight().y, dl.getBottomRightBack().x, dl.getBottomRightBack().y, dl.getBottomLeftBack().x, dl.getBottomLeftBack().y}; int count = canvas.save(); matrix.setPolyToPoly(src, 0, dst, 0, dst.length >> 1); canvas.concat(matrix); drawRectancle(dl, canvas); drawShadow(dl.getBottomLeft(), dl.getBottomRight(), 0, canvas, dl); canvas.restoreToCount(count); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/MaterialMenuDrawable.java ================================================ package no.agens.depth.lib; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.util.Property; import android.util.TypedValue; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import static android.graphics.Paint.Style; public class MaterialMenuDrawable extends Drawable implements Animatable { public enum IconState { BURGER, ARROW, X, CHECK } public enum AnimationState { BURGER_ARROW, BURGER_X, ARROW_X, ARROW_CHECK, BURGER_CHECK, X_CHECK; public IconState getFirstState() { switch (this) { case BURGER_ARROW: return IconState.BURGER; case BURGER_X: return IconState.BURGER; case ARROW_X: return IconState.ARROW; case ARROW_CHECK: return IconState.ARROW; case BURGER_CHECK: return IconState.BURGER; case X_CHECK: return IconState.X; default: return null; } } public IconState getSecondState() { switch (this) { case BURGER_ARROW: return IconState.ARROW; case BURGER_X: return IconState.X; case ARROW_X: return IconState.X; case ARROW_CHECK: return IconState.CHECK; case BURGER_CHECK: return IconState.CHECK; case X_CHECK: return IconState.CHECK; default: return null; } } } public enum Stroke { /** * 3 dip */ REGULAR(3), /** * 2 dip */ THIN(2), /** * 1 dip */ EXTRA_THIN(1); private final int strokeWidth; Stroke(int strokeWidth) { this.strokeWidth = strokeWidth; } protected static Stroke valueOf(int strokeWidth) { switch (strokeWidth) { case 3: return REGULAR; case 2: return THIN; case 1: return EXTRA_THIN; default: return THIN; } } } public static final int DEFAULT_COLOR = Color.WHITE; public static final int DEFAULT_SCALE = 1; public static final int DEFAULT_TRANSFORM_DURATION = 800; public static final boolean DEFAULT_VISIBLE = true; private static final int BASE_DRAWABLE_WIDTH = 40; private static final int BASE_DRAWABLE_HEIGHT = 40; private static final int BASE_ICON_WIDTH = 20; private static final int BASE_CIRCLE_RADIUS = 18; private static final float ARROW_MID_LINE_ANGLE = 180; private static final float ARROW_TOP_LINE_ANGLE = 135; private static final float ARROW_BOT_LINE_ANGLE = 225; private static final float X_TOP_LINE_ANGLE = 44; private static final float X_BOT_LINE_ANGLE = -44; private static final float X_ROTATION_ANGLE = 90; private static final float CHECK_MIDDLE_ANGLE = 135; private static final float CHECK_BOTTOM_ANGLE = -90; private static final float TRANSFORMATION_START = 0; private static final float TRANSFORMATION_MID = 1.0f; private static final float TRANSFORMATION_END = 2.0f; private static final int DEFAULT_CIRCLE_ALPHA = 200; private final float diph; private final float dip1; private final float dip2; private final float dip3; private final float dip4; private final float dip8; private final int width; private final int height; private final float strokeWidth; private final float iconWidth; private final float topPadding; private final float sidePadding; private final float circleRadius; private final Stroke stroke; private final Object lock = new Object(); private final Paint iconPaint = new Paint(); private final Paint circlePaint = new Paint(); private float transformationValue = 0f; private boolean transformationRunning = false; private IconState currentIconState = IconState.BURGER; private AnimationState animationState = AnimationState.BURGER_ARROW; private IconState animatingIconState; private boolean visible; private boolean rtlEnabled; private ObjectAnimator transformation; private Animator.AnimatorListener animatorListener; private MaterialMenuState materialMenuState; public MaterialMenuDrawable(Context context, int color, Stroke stroke) { this(context, color, stroke, DEFAULT_SCALE, DEFAULT_TRANSFORM_DURATION); } public MaterialMenuDrawable(Context context, int color, Stroke stroke, int transformDuration) { this(context, color, stroke, DEFAULT_SCALE, transformDuration); } public MaterialMenuDrawable(Context context, int color, Stroke stroke, int scale, int transformDuration) { Resources resources = context.getResources(); // convert each separately due to various densities this.dip1 = dpToPx(resources, 1) * scale; this.dip2 = dpToPx(resources, 2) * scale; this.dip3 = dpToPx(resources, 3) * scale; this.dip4 = dpToPx(resources, 4) * scale; this.dip8 = dpToPx(resources, 8) * scale; this.diph = dip1 / 2; this.stroke = stroke; this.visible = DEFAULT_VISIBLE; this.width = (int) (dpToPx(resources, BASE_DRAWABLE_WIDTH) * scale); this.height = (int) (dpToPx(resources, BASE_DRAWABLE_HEIGHT) * scale); this.iconWidth = dpToPx(resources, BASE_ICON_WIDTH) * scale; this.circleRadius = dpToPx(resources, BASE_CIRCLE_RADIUS) * scale; this.strokeWidth = dpToPx(resources, stroke.strokeWidth) * scale; this.sidePadding = (width - iconWidth) / 2; this.topPadding = (height - 5 * dip3) / 2; initPaint(color); initAnimations(transformDuration); materialMenuState = new MaterialMenuState(); } private MaterialMenuDrawable(int color, Stroke stroke, long transformDuration, int width, int height, float iconWidth, float circleRadius, float strokeWidth, float dip1 ) { this.dip1 = dip1; this.dip2 = dip1 * 2; this.dip3 = dip1 * 3; this.dip4 = dip1 * 4; this.dip8 = dip1 * 8; this.diph = dip1 / 2; this.stroke = stroke; this.width = width; this.height = height; this.iconWidth = iconWidth; this.circleRadius = circleRadius; this.strokeWidth = strokeWidth; this.sidePadding = (width - iconWidth) / 2; this.topPadding = (height - 5 * dip3) / 2; initPaint(color); initAnimations((int) transformDuration); materialMenuState = new MaterialMenuState(); } private void initPaint(int color) { iconPaint.setAntiAlias(true); iconPaint.setStyle(Style.STROKE); iconPaint.setStrokeWidth(strokeWidth); iconPaint.setColor(color); circlePaint.setAntiAlias(true); circlePaint.setStyle(Style.FILL); circlePaint.setColor(color); circlePaint.setAlpha(DEFAULT_CIRCLE_ALPHA); setBounds(0, 0, width, height); } /* * Drawing */ @Override public void draw(Canvas canvas) { if (!visible) return; final float ratio = transformationValue <= 1 ? transformationValue : 2 - transformationValue; if (rtlEnabled) { canvas.save(); canvas.scale(-1, 1, 0, 0); canvas.translate(-getIntrinsicWidth(), 0); } drawTopLine(canvas, ratio); drawMiddleLine(canvas, ratio); drawBottomLine(canvas, ratio); if (rtlEnabled) { canvas.restore(); } } private void drawMiddleLine(Canvas canvas, float ratio) { canvas.restore(); canvas.save(); float rotation = 0; float pivotX = width / 2; float pivotY = width / 2; float startX = sidePadding; float startY = topPadding + dip3 / 2 * 5; float stopX = width - sidePadding; float stopY = topPadding + dip3 / 2 * 5; int alpha = 255; switch (animationState) { case BURGER_ARROW: // rotate by 180 if (isMorphingForward()) { rotation = ratio * ARROW_MID_LINE_ANGLE; } else { rotation = ARROW_MID_LINE_ANGLE + (1 - ratio) * ARROW_MID_LINE_ANGLE; } // shorten one end stopX -= ratio * resolveStrokeModifier(ratio) / 2; break; case BURGER_X: // fade out alpha = (int) ((1 - ratio) * 255); break; case ARROW_X: // fade out and shorten one end alpha = (int) ((1 - ratio) * 255); startX += (1 - ratio) * dip2; break; case ARROW_CHECK: if (isMorphingForward()) { // rotate until required angle rotation = ratio * CHECK_MIDDLE_ANGLE; } else { // rotate back to starting angle rotation = CHECK_MIDDLE_ANGLE - CHECK_MIDDLE_ANGLE * (1 - ratio); } // shorten one end and lengthen the other startX += dip3 / 2 + dip4 - (1 - ratio) * dip2; stopX += ratio * dip1; pivotX = width / 2 + dip3 + diph; break; case BURGER_CHECK: // rotate until required angle rotation = ratio * CHECK_MIDDLE_ANGLE; // lengthen both ends startX += ratio * (dip4 + dip3 / 2); stopX += ratio * dip1; pivotX = width / 2 + dip3 + diph; break; case X_CHECK: // fade in alpha = (int) (ratio * 255); // rotation to check angle rotation = ratio * CHECK_MIDDLE_ANGLE; // lengthen both ends startX += ratio * (dip4 + dip3 / 2); stopX += ratio * dip1; pivotX = width / 2 + dip3 + diph; break; } iconPaint.setAlpha(alpha); canvas.rotate(rotation, pivotX, pivotY); canvas.drawLine(startX, startY, stopX, stopY, iconPaint); iconPaint.setAlpha(255); } private void drawTopLine(Canvas canvas, float ratio) { canvas.save(); float rotation = 0, pivotX = 0, pivotY = 0; float rotation2 = 0; // pivot at center of line float pivotX2 = width / 2 + dip3 / 2; float pivotY2 = topPadding + dip2; float startX = sidePadding; float startY = topPadding + dip2; float stopX = width - sidePadding; float stopY = topPadding + dip2; int alpha = 255; switch (animationState) { case BURGER_ARROW: if (isMorphingForward()) { // rotate until required angle rotation = ratio * ARROW_BOT_LINE_ANGLE; } else { // rotate back to start doing a 360 rotation = ARROW_BOT_LINE_ANGLE + (1 - ratio) * ARROW_TOP_LINE_ANGLE; } // rotate by middle pivotX = width / 2; pivotY = height / 2; // shorten both ends stopX -= resolveStrokeModifier(ratio); startX += dip3 * ratio; break; case BURGER_X: // rotate until required angles rotation = X_TOP_LINE_ANGLE * ratio; rotation2 = X_ROTATION_ANGLE * ratio; // pivot at left corner of line pivotX = sidePadding + dip4; pivotY = topPadding + dip3; // shorten one end startX += dip3 * ratio; break; case ARROW_X: // rotate from ARROW angle to x angle rotation = ARROW_BOT_LINE_ANGLE + (X_TOP_LINE_ANGLE - ARROW_BOT_LINE_ANGLE) * ratio; rotation2 = X_ROTATION_ANGLE * ratio; // move pivot from ARROW pivot to x pivot pivotX = width / 2 + (sidePadding + dip4 - width / 2) * ratio; pivotY = height / 2 + (topPadding + dip3 - height / 2) * ratio; // lengthen both ends stopX -= resolveStrokeModifier(ratio); startX += dip3; break; case ARROW_CHECK: // fade out alpha = (int) ((1 - ratio) * 255); // retain starting arrow configuration rotation = ARROW_BOT_LINE_ANGLE; pivotX = width / 2; pivotY = height / 2; // shorted both ends stopX -= resolveStrokeModifier(1); startX += dip3; break; case BURGER_CHECK: // fade out alpha = (int) ((1 - ratio) * 255); break; case X_CHECK: // retain x configuration rotation = X_TOP_LINE_ANGLE; rotation2 = X_ROTATION_ANGLE; pivotX = sidePadding + dip4; pivotY = topPadding + dip3; stopX += dip3 - dip3 * (1 - ratio); startX += dip3; // fade out alpha = (int) ((1 - ratio) * 255); break; } iconPaint.setAlpha(alpha); canvas.rotate(rotation, pivotX, pivotY); canvas.rotate(rotation2, pivotX2, pivotY2); canvas.drawLine(startX, startY, stopX, stopY, iconPaint); iconPaint.setAlpha(255); } private void drawBottomLine(Canvas canvas, float ratio) { canvas.restore(); canvas.save(); float rotation = 0, pivotX = 0, pivotY = 0; float rotation2 = 0; // pivot at center of line float pivotX2 = width / 2 + dip3 / 2; float pivotY2 = height - topPadding - dip2; float startX = sidePadding; float startY = height - topPadding - dip2; float stopX = width - sidePadding; float stopY = height - topPadding - dip2; switch (animationState) { case BURGER_ARROW: if (isMorphingForward()) { // rotate to required angle rotation = ARROW_TOP_LINE_ANGLE * ratio; } else { // rotate back to start doing a 360 rotation = ARROW_TOP_LINE_ANGLE + (1 - ratio) * ARROW_BOT_LINE_ANGLE; } // pivot center of canvas pivotX = width / 2; pivotY = height / 2; // shorten both ends stopX = width - sidePadding - resolveStrokeModifier(ratio); startX = sidePadding + dip3 * ratio; break; case BURGER_X: if (isMorphingForward()) { // rotate around rotation2 = -X_ROTATION_ANGLE * ratio; } else { // rotate directly rotation2 = X_ROTATION_ANGLE * ratio; } // rotate to required angle rotation = X_BOT_LINE_ANGLE * ratio; // pivot left corner of line pivotX = sidePadding + dip4; pivotY = height - topPadding - dip3; // shorten one end startX += dip3 * ratio; break; case ARROW_X: // rotate from ARROW angle to x angle rotation = ARROW_TOP_LINE_ANGLE + (360 + X_BOT_LINE_ANGLE - ARROW_TOP_LINE_ANGLE) * ratio; rotation2 = -X_ROTATION_ANGLE * ratio; // move pivot from ARROW pivot to x pivot pivotX = width / 2 + (sidePadding + dip4 - width / 2) * ratio; pivotY = height / 2 + (height / 2 - topPadding - dip3) * ratio; // lengthen both ends stopX -= resolveStrokeModifier(ratio); startX += dip3; break; case ARROW_CHECK: // rotate from ARROW angle to CHECK angle rotation = ARROW_TOP_LINE_ANGLE + ratio * CHECK_BOTTOM_ANGLE; // move pivot from ARROW pivot to CHECK pivot pivotX = width / 2 + dip3 * ratio; pivotY = height / 2 - dip3 * ratio; // length stays same as ARROW stopX -= resolveStrokeModifier(1); startX += dip3 + (dip4 + dip1) * ratio; break; case BURGER_CHECK: // rotate from ARROW angle to CHECK angle rotation = ratio * (CHECK_BOTTOM_ANGLE + ARROW_TOP_LINE_ANGLE); // move pivot from BURGER pivot to CHECK pivot pivotX = width / 2 + dip3 * ratio; pivotY = height / 2 - dip3 * ratio; // length stays same as BURGER startX += dip8 * ratio; stopX -= resolveStrokeModifier(ratio); break; case X_CHECK: // rotate from x to CHECK angles rotation2 = -X_ROTATION_ANGLE * (1 - ratio); rotation = X_BOT_LINE_ANGLE + (CHECK_BOTTOM_ANGLE + ARROW_TOP_LINE_ANGLE - X_BOT_LINE_ANGLE) * ratio; // move pivot from x to CHECK pivotX = sidePadding + dip4 + (width / 2 + dip3 - sidePadding - dip4) * ratio; pivotY = height - topPadding - dip3 + (topPadding + height / 2 - height) * ratio; // shorten both ends startX += dip8 - (dip4 + dip1) * (1 - ratio); stopX -= resolveStrokeModifier(1 - ratio); break; } canvas.rotate(rotation, pivotX, pivotY); canvas.rotate(rotation2, pivotX2, pivotY2); canvas.drawLine(startX, startY, stopX, stopY, iconPaint); } private boolean isMorphingForward() { return transformationValue <= TRANSFORMATION_MID; } private float resolveStrokeModifier(float ratio) { switch (stroke) { case REGULAR: if (animationState == AnimationState.ARROW_X || animationState == AnimationState.X_CHECK) { return dip3 - (dip3 * ratio); } return ratio * dip3; case THIN: if (animationState == AnimationState.ARROW_X || animationState == AnimationState.X_CHECK) { return dip3 + diph - (dip3 + diph) * ratio; } return ratio * (dip3 + diph); case EXTRA_THIN: if (animationState == AnimationState.ARROW_X || animationState == AnimationState.X_CHECK) { return dip4 - ((dip3 + dip1) * ratio); } return ratio * dip4; } return 0; } @Override public void setAlpha(int alpha) { iconPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { iconPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSPARENT; } /* * Accessor methods */ public void setColor(int color) { iconPaint.setColor(color); circlePaint.setColor(color); invalidateSelf(); } public void setTransformationDuration(int duration) { transformation.setDuration(duration); } public void setInterpolator(Interpolator interpolator) { transformation.setInterpolator(interpolator); } public void setAnimationListener(Animator.AnimatorListener listener) { if (animatorListener != null) { transformation.removeListener(animatorListener); } if (listener != null) { transformation.addListener(listener); } animatorListener = listener; } public void setIconState(IconState iconState) { synchronized (lock) { if (transformationRunning) { transformation.cancel(); transformationRunning = false; } if (currentIconState == iconState) return; switch (iconState) { case BURGER: animationState = AnimationState.BURGER_ARROW; transformationValue = TRANSFORMATION_START; break; case ARROW: animationState = AnimationState.BURGER_ARROW; transformationValue = TRANSFORMATION_MID; break; case X: animationState = AnimationState.BURGER_X; transformationValue = TRANSFORMATION_MID; break; case CHECK: animationState = AnimationState.BURGER_CHECK; transformationValue = TRANSFORMATION_MID; } currentIconState = iconState; invalidateSelf(); } } public void animateIconState(IconState state) { synchronized (lock) { if (transformationRunning) { transformation.end(); } animatingIconState = state; start(); } } public IconState setTransformationOffset(AnimationState animationState, float offset) { if (offset < TRANSFORMATION_START || offset > TRANSFORMATION_END) { throw new IllegalArgumentException( String.format("Value must be between %s and %s", TRANSFORMATION_START, TRANSFORMATION_END) ); } this.animationState = animationState; final boolean isFirstIcon = offset < TRANSFORMATION_MID || offset == TRANSFORMATION_END; currentIconState = isFirstIcon ? animationState.getFirstState() : animationState.getSecondState(); animatingIconState = isFirstIcon ? animationState.getSecondState() : animationState.getFirstState(); setTransformationValue(offset); return currentIconState; } public void setVisible(boolean visible) { this.visible = visible; invalidateSelf(); } public void setRTLEnabled(boolean rtlEnabled) { this.rtlEnabled = rtlEnabled; invalidateSelf(); } public IconState getIconState() { return currentIconState; } public boolean isDrawableVisible(){ return visible; } /* * Animations */ private final Property transformationProperty = new Property(Float.class, "transformation") { @Override public Float get(MaterialMenuDrawable object) { return object.getTransformationValue(); } @Override public void set(MaterialMenuDrawable object, Float value) { object.setTransformationValue(value); } }; public Float getTransformationValue() { return transformationValue; } public void setTransformationValue(Float value) { this.transformationValue = value; invalidateSelf(); } private void initAnimations(int transformDuration) { transformation = ObjectAnimator.ofFloat(this, transformationProperty, 0); transformation.setInterpolator(new DecelerateInterpolator(3)); transformation.setDuration(transformDuration); transformation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { transformationRunning = false; setIconState(animatingIconState); } }); } private boolean resolveTransformation() { boolean isCurrentBurger = currentIconState == IconState.BURGER; boolean isCurrentArrow = currentIconState == IconState.ARROW; boolean isCurrentX = currentIconState == IconState.X; boolean isCurrentCheck = currentIconState == IconState.CHECK; boolean isAnimatingBurger = animatingIconState == IconState.BURGER; boolean isAnimatingArrow = animatingIconState == IconState.ARROW; boolean isAnimatingX = animatingIconState == IconState.X; boolean isAnimatingCheck = animatingIconState == IconState.CHECK; if ((isCurrentBurger && isAnimatingArrow) || (isCurrentArrow && isAnimatingBurger)) { animationState = AnimationState.BURGER_ARROW; return isCurrentBurger; } if ((isCurrentArrow && isAnimatingX) || (isCurrentX && isAnimatingArrow)) { animationState = AnimationState.ARROW_X; return isCurrentArrow; } if ((isCurrentBurger && isAnimatingX) || (isCurrentX && isAnimatingBurger)) { animationState = AnimationState.BURGER_X; return isCurrentBurger; } if ((isCurrentArrow && isAnimatingCheck) || (isCurrentCheck && isAnimatingArrow)) { animationState = AnimationState.ARROW_CHECK; return isCurrentArrow; } if ((isCurrentBurger && isAnimatingCheck) || (isCurrentCheck && isAnimatingBurger)) { animationState = AnimationState.BURGER_CHECK; return isCurrentBurger; } if ((isCurrentX && isAnimatingCheck) || (isCurrentCheck && isAnimatingX)) { animationState = AnimationState.X_CHECK; return isCurrentX; } throw new IllegalStateException( String.format("Animating from %s to %s is not supported", currentIconState, animatingIconState) ); } @Override public void start() { if (transformationRunning) return; if (animatingIconState != null && animatingIconState != currentIconState) { transformationRunning = true; final boolean direction = resolveTransformation(); transformation.setFloatValues( direction ? TRANSFORMATION_START : TRANSFORMATION_MID, direction ? TRANSFORMATION_MID : TRANSFORMATION_END ); transformation.start(); } invalidateSelf(); } @Override public void stop() { if (isRunning() && transformation.isRunning()) { transformation.end(); } else { transformationRunning = false; invalidateSelf(); } } @Override public boolean isRunning() { return transformationRunning; } @Override public int getIntrinsicWidth() { return width; } @Override public int getIntrinsicHeight() { return height; } @Override public ConstantState getConstantState() { materialMenuState.changingConfigurations = getChangingConfigurations(); return materialMenuState; } @Override public Drawable mutate() { materialMenuState = new MaterialMenuState(); return this; } private final class MaterialMenuState extends ConstantState { private int changingConfigurations; private MaterialMenuState() { } @Override public Drawable newDrawable() { MaterialMenuDrawable drawable = new MaterialMenuDrawable( circlePaint.getColor(), stroke, transformation.getDuration(), width, height, iconWidth, circleRadius, strokeWidth, dip1 ); drawable.setIconState(animatingIconState != null ? animatingIconState : currentIconState); drawable.setVisible(visible); drawable.setRTLEnabled(rtlEnabled); return drawable; } @Override public int getChangingConfigurations() { return changingConfigurations; } } static float dpToPx(Resources resources, float dp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics()); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/MathHelper.java ================================================ package no.agens.depth.lib; import java.util.Random; public class MathHelper { public static final Random rand = new Random(); public static float randomRange(float min, float max) { return rand.nextInt(((int) max - (int) min) + 1) + (int) min; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/RectEvaluator.java ================================================ package no.agens.depth.lib; import android.animation.TypeEvaluator; import android.graphics.Rect; public class RectEvaluator implements TypeEvaluator { @Override public Rect evaluate(float fraction, Rect startValue, Rect endValue) { return new Rect(startValue.left + (int) ((endValue.left - startValue.left) * fraction), startValue.top + (int) ((endValue.top - startValue.top) * fraction), startValue.right + (int) ((endValue.right - startValue.right) * fraction), startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction)); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/RectFEvaluator.java ================================================ package no.agens.depth.lib; import android.animation.TypeEvaluator; import android.graphics.RectF; public class RectFEvaluator implements TypeEvaluator { @Override public RectF evaluate(float fraction, RectF startValue, RectF endValue) { return new RectF(startValue.left + (int) ((endValue.left - startValue.left) * fraction), startValue.top + (int) ((endValue.top - startValue.top) * fraction), startValue.right + (int) ((endValue.right - startValue.right) * fraction), startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction)); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/RippleHelper.java ================================================ package no.agens.depth.lib; import android.content.res.ColorStateList; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.RippleDrawable; public class RippleHelper { public static RippleDrawable getPressedColorRippleDrawable(int normalColor, int pressedColor) { return new RippleDrawable(getPressedColorSelector(normalColor, pressedColor), getColorDrawableFromColor(normalColor), null); } public static ColorStateList getPressedColorSelector(int normalColor, int pressedColor) { return new ColorStateList( new int[][] { new int[]{android.R.attr.state_pressed}, new int[]{android.R.attr.state_focused}, new int[]{android.R.attr.state_activated}, new int[]{} }, new int[] { pressedColor, pressedColor, pressedColor, normalColor } ); } public static ColorDrawable getColorDrawableFromColor(int color) { return new ColorDrawable(color); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/headers/AuraDrawable.java ================================================ package no.agens.depth.lib.headers; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import no.agens.depth.lib.MathHelper; public class AuraDrawable extends Renderable { private final Drawable drawable; private long lastFlicker; public AuraDrawable(Drawable drawable, Rect position) { super(null, 0, 0); drawable.setBounds(position); this.drawable = drawable; lastFlicker = System.currentTimeMillis(); } @Override public void draw(Canvas canvas) { drawable.draw(canvas); } public void update(float deltaTime, float wind) { if (lastFlicker + 50 < System.currentTimeMillis()) { drawable.setAlpha((int) (255 * (30f + (float) MathHelper.rand.nextInt(25)) / 100f)); lastFlicker = System.currentTimeMillis(); } } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/headers/NoiseEffect.java ================================================ package no.agens.depth.lib.headers; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Shader; import android.graphics.Xfermode; import no.agens.depth.lib.MathHelper; public class NoiseEffect extends Renderable { private final Paint paint = new Paint(); BitmapShader shader; Matrix matrix; float scale; public NoiseEffect(Bitmap bitmap, int grainFPS, float scale) { super(bitmap, 0, 0); shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); matrix = new Matrix(); shader.setLocalMatrix(matrix); paint.setShader(shader); paint.setAlpha(144); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); lastGrainOffset = System.currentTimeMillis(); this.grainFPS = grainFPS; this.scale=scale; } @Override public void draw(Canvas canvas) { canvas.drawPaint(paint); } long lastGrainOffset; private int grainFPS; @Override public void update(float deltaTime, float wind) { if (lastGrainOffset + grainFPS < System.currentTimeMillis()) { matrix.reset(); matrix.setScale(scale, scale); matrix.postTranslate(MathHelper.randomRange(-bitmap.getWidth() * 10f, bitmap.getWidth() * 10f), MathHelper.randomRange(-bitmap.getHeight() * 10f, bitmap.getHeight() * 10f)); shader.setLocalMatrix(matrix); lastGrainOffset = System.currentTimeMillis(); } } private static final Xfermode[] sModes = { new PorterDuffXfermode(PorterDuff.Mode.CLEAR), new PorterDuffXfermode(PorterDuff.Mode.SRC), new PorterDuffXfermode(PorterDuff.Mode.DST), new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER), new PorterDuffXfermode(PorterDuff.Mode.DST_OVER), new PorterDuffXfermode(PorterDuff.Mode.SRC_IN), new PorterDuffXfermode(PorterDuff.Mode.DST_IN), new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT), new PorterDuffXfermode(PorterDuff.Mode.DST_OUT), new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP), new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP), new PorterDuffXfermode(PorterDuff.Mode.XOR), new PorterDuffXfermode(PorterDuff.Mode.DARKEN), new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN), new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY), new PorterDuffXfermode(PorterDuff.Mode.SCREEN) }; public void setNoiseIntensity(float noiseIntensity) { paint.setAlpha((int) (255f * noiseIntensity)); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/headers/Particle.java ================================================ package no.agens.depth.lib.headers; public class Particle extends Renderable { float randomSpeedX; float randomSpeedY; public Particle(float x, float y,float randomSpeedX, float randomSpeedY) { super(null, x, y ); this.randomSpeedX = randomSpeedX; this.randomSpeedY = randomSpeedY; lastRandomizeChange = System.currentTimeMillis(); } long lastRandomizeChange; public void setRandomSpeed( float randomSpeedX, float randomSpeedY){ this.randomSpeedX = randomSpeedX; this.randomSpeedY = randomSpeedY; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/headers/ParticleSystem.java ================================================ package no.agens.depth.lib.headers; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import java.util.ArrayList; import java.util.List; import no.agens.depth.lib.MathHelper; public class ParticleSystem extends Renderable { public static final int DURATION = 10000; final Paint particlePaint = new Paint(); long lastEmit; private int emitInterWall; private float gravityY; private float randomXPlacement; private float minYCoord; private float randomMovementY; int startColor; int endColor; private int randomMovementChangeInterval=2000; public void setParticleSize(int particleSize) { this.particleSize = particleSize; } int particleSize = 20; public void setRandomMovementX(float randomMovement) { this.randomMovementX = randomMovement; } float randomMovementX = 10; public ParticleSystem(float x, float y, int emitInterWall, float gravityY, float randomXPlacement) { super(null, x, y); lastEmit = System.currentTimeMillis(); particlePaint.setColor(Color.RED); this.emitInterWall = emitInterWall; this.gravityY = gravityY; this.randomXPlacement = randomXPlacement; } public void setColors(int startColor, int endColor) { this.startColor = startColor; this.endColor = endColor; color = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor, Color.TRANSPARENT).setDuration(DURATION); } List particles = new ArrayList<>(); @Override public void draw(Canvas canvas) { for (int i = 0; i < particles.size(); i++) { Particle p = particles.get(i); setParticlePaintColor(p); canvas.drawRect(p.x, p.y, p.x + particleSize, p.y + particleSize, particlePaint); } } @Override public void update(float deltaTime, float wind) { long currentTimeMillis = System.currentTimeMillis(); if (lastEmit + emitInterWall < currentTimeMillis) { addParticle(); } for (int i = 0; i < particles.size(); i++) { Particle particle = particles.get(i); particle.y += gravityY * deltaTime; particle.y += particle.randomSpeedY * deltaTime; particle.x += particle.randomSpeedX * deltaTime; particle.x += wind * deltaTime; if (particle.lastRandomizeChange + randomMovementChangeInterval < currentTimeMillis) { particle.lastRandomizeChange = System.currentTimeMillis(); particle.setRandomSpeed(MathHelper.randomRange(-randomMovementX,randomMovementX), MathHelper.randomRange(-randomMovementY,randomMovementY)); } if (particle.y < minYCoord) { particles.remove(i); i--; } } } private void addParticle() { particles.add(new Particle(x + MathHelper.randomRange(-randomXPlacement,randomXPlacement), y, MathHelper.randomRange(-randomMovementX,randomMovementX), MathHelper.randomRange(-randomMovementY, randomMovementY))); lastEmit = System.currentTimeMillis(); } public void setMinYCoord(float minYCoord) { this.minYCoord = minYCoord; } public void setRandomMovementY(float randomMovementY) { this.randomMovementY = randomMovementY; } ValueAnimator color; public void setParticlePaintColor(Particle particle) { float currentY = y - particle.y; float maxMoveDistance = y - minYCoord; float travelDistanceInPercentOfMax = currentY / maxMoveDistance; color.setCurrentPlayTime((long) (DURATION * travelDistanceInPercentOfMax)); particlePaint.setColor((Integer) color.getAnimatedValue()); } public void setRandomMovementChangeInterval(int randomMovementChangeInterval) { this.randomMovementChangeInterval = randomMovementChangeInterval; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/headers/PathBitmapMesh.java ================================================ package no.agens.depth.lib.headers; import android.animation.ValueAnimator; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.view.animation.LinearInterpolator; public class PathBitmapMesh { public final float[] drawingVerts; public final float[] staticVerts; private static final int HORIZONTAL_SLICES = 6; private static final int VERTICAL_SLICES = 1; int horizontalSlices, verticalSlices; public Bitmap bitmap; public PathBitmapMesh(int horizontalSlices, int verticalSlices, Bitmap bitmap, int animDuration) { int totalSlicesCount = (HORIZONTAL_SLICES + 1) * (VERTICAL_SLICES + 1); drawingVerts = new float[totalSlicesCount * 2]; staticVerts = new float[totalSlicesCount * 2]; this.horizontalSlices = horizontalSlices; this.verticalSlices = verticalSlices; this.bitmap = bitmap; createVerts(); startWaveAnim(bitmap, horizontalSlices, animDuration); } private void startWaveAnim(Bitmap bitmap, float waves, int animDuration) { pathOffset = ValueAnimator.ofFloat(0, ((bitmap.getWidth() / waves) * 2f) / bitmap.getWidth()).setDuration(animDuration); pathOffset.setRepeatCount(ValueAnimator.INFINITE); pathOffset.setRepeatMode(ValueAnimator.RESTART); pathOffset.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { pathOffsetPercent = (float) animation.getAnimatedValue(); } }); pathOffset.setInterpolator(new LinearInterpolator()); pathOffset.start(); } public ValueAnimator pathOffset; public void destroy() { pathOffset.cancel(); if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); bitmap = null; } } public void setAlpha(int alpha) { paint.setAlpha(alpha); } private void createVerts() { float xDimension = (float) bitmap.getWidth(); float yDimension = (float) bitmap.getHeight(); int index = 0; for (int y = 0; y <= VERTICAL_SLICES; y++) { float fy = yDimension * y / VERTICAL_SLICES; for (int x = 0; x <= HORIZONTAL_SLICES; x++) { float fx = xDimension * x / HORIZONTAL_SLICES; setXY(drawingVerts, index, fx, fy); setXY(staticVerts, index, fx, fy); index += 1; } } } Paint paint = new Paint(); public void setXY(float[] array, int index, float x, float y) { array[index * 2 + 0] = x; array[index * 2 + 1] = y; } public void matchVertsToPath(Path path, float bottomCoord, float extraOffset) { PathMeasure pm = new PathMeasure(path, false); for (int i = 0; i < staticVerts.length / 2; i++) { float yIndexValue = staticVerts[i * 2 + 1]; float xIndexValue = staticVerts[i * 2]; float percentOffsetX = (0.000001f + xIndexValue) / bitmap.getWidth(); float percentOffsetX2 = (0.000001f + xIndexValue) / (bitmap.getWidth() + extraOffset); percentOffsetX2 += pathOffsetPercent; pm.getPosTan(pm.getLength() * (1f - percentOffsetX), coords, null); pm.getPosTan(pm.getLength() * (1f - percentOffsetX2), coords2, null); if (yIndexValue == 0) { setXY(drawingVerts, i, coords[0], coords2[1]); } else { setXY(drawingVerts, i, coords[0], bottomCoord); } } } public Bitmap getBitmap() { return bitmap; } public float[] coords = new float[2]; public float[] coords2 = new float[2]; public float pathOffsetPercent = 1; public void draw(Canvas canvas) { canvas.drawBitmapMesh(bitmap, HORIZONTAL_SLICES, VERTICAL_SLICES, drawingVerts, 0, null, 0, paint); } public void pause() { pathOffset.pause(); } public void resume() { pathOffset.resume(); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/headers/Renderable.java ================================================ package no.agens.depth.lib.headers; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RectF; /** * Created by danielzeller on 01.10.14. */ public class Renderable { public float x; public void setY(float y) { this.y = y; } public float y; public float translationY; public float translationX; public Bitmap bitmap; public float scaleX = 1f; public float scaleY = 1f; public Renderable(Bitmap bitmap, float x, float y) { this.x = x; this.y = y; this.bitmap = bitmap; } public void draw(Canvas canvas) { canvas.save(); canvas.drawBitmap(bitmap, x + translationX / 2, y + translationY, null); canvas.restore(); } public void drawStretched(Canvas canvas, float parentWidth) { canvas.save(); canvas.drawBitmap(bitmap, null, new RectF(x + translationX / 2, y + translationY, x + translationX / 2 + parentWidth, y + translationY + bitmap.getHeight()), null); canvas.restore(); } public void setTranslationY(Float translationY) { this.translationY = translationY; } public float getTranslationY() { return translationY; } public void setTranslationY(float translationY) { this.translationY = translationY; } public float getTranslationX() { return translationX; } public void setTranslationX(float translationX) { this.translationX = translationX; } public void setScale(float scale, float scale1) { } public void update(float deltaTime, float wind) { } public void destroy() { if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); bitmap = null; } } public void pause() { } public void resume() { } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/FrameRateCounter.java ================================================ package no.agens.depth.lib.tween; import android.os.SystemClock; /** * Created by danielzeller on 26.03.14. */ public class FrameRateCounter { private static long mLastTime; public static float timeStep() { final long time = SystemClock.uptimeMillis(); final long timeDelta = time - mLastTime; float timeDeltaSeconds = mLastTime > 0.0f ? timeDelta / 1000.0f : 0.0f; mLastTime = time; return Math.min(0.021f, timeDeltaSeconds); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/TRectFEvaluator.java ================================================ package no.agens.depth.lib.tween; import android.animation.TypeEvaluator; import android.graphics.RectF; /** * Created by danielzeller on 19.08.14. */ public class TRectFEvaluator implements TypeEvaluator { /** * This function returns the result of linearly interpolating the start and * end Rect values, with fraction representing the proportion * between the start and end values. The calculation is a simple parametric * calculation on each of the separate components in the Rect objects * (left, top, right, and bottom). * * @param fraction The fraction from the starting to the ending values * @param startValue The start Rect * @param endValue The end Rect * @return A linear interpolation between the start and end values, given the * fraction parameter. */ @Override public RectF evaluate(float fraction, RectF startValue, RectF endValue) { return new RectF(startValue.left + (int) ((endValue.left - startValue.left) * fraction), startValue.top + (int) ((endValue.top - startValue.top) * fraction), startValue.right + (int) ((endValue.right - startValue.right) * fraction), startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction)); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/BackIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class BackIn implements TimeInterpolator { @Override public float getInterpolation(float t) { float s = param_s; return t*t*((s+1)*t - s); } private float param_s = 1.70158f; public BackIn amount(float s) { param_s = s; return this; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/BackInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class BackInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { float s = param_s; if ((t*=2) < 1) return 0.5f*(t*t*(((s*=(1.525f))+1)*t - s)); return 0.5f*((t-=2)*t*(((s*=(1.525f))+1)*t + s) + 2); } private float param_s = 1.70158f; public BackInOut amount(float s) { param_s = s; return this; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/BackOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class BackOut implements TimeInterpolator { @Override public float getInterpolation(float t) { float s = param_s; return (t-=1)*t*((s+1)*t + s) + 1; } private float param_s = 1.70158f; public BackOut amount(float s) { param_s = s; return this; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/CircIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 14.04.15. */ class CircIn implements TimeInterpolator { @Override public float getInterpolation(float t) { return (float)Math.sqrt(1f - t*t); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/CircInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 14.04.15. */ public class CircInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { if ((t*=2) < 1) return -0.5f * ((float)Math.sqrt(1 - t*t) - 1); return 0.5f* ((float)Math.sqrt(1 - (t-=2)*t) + 1); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/CircOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 14.04.15. */ class CircOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return ((float)Math.sqrt(1f - (t-1f)*t) + 1f); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/ElasticIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class ElasticIn implements TimeInterpolator { @Override public float getInterpolation(float t) { float a = param_a; float p = param_p; if (t==0) return 0; if (t==1) return 1; if (!setP) p=.3f; float s; if (!setA || a < 1) { a=1; s=p/4f; } else s = p/(2f*(float)Math.PI) * (float)Math.asin(1/a); return -(a*(float)Math.pow(2,10*(t-=1)) * (float)Math.sin( (t-s)*(2*Math.PI)/p )); } protected float param_a; protected float param_p; protected boolean setA = false; protected boolean setP = false; public ElasticIn a(float a) { param_a = a; this.setA = true; return this; } public ElasticIn p(float p) { param_p = p; this.setP = true; return this; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/ElasticInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class ElasticInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { float a = param_a; float p = param_p; if (t==0) return 0; if ((t*=2)==2) return 1; if (!setP) p=.3f*1.5f; float s; if (!setA || a < 1) { a=1; s=p/4; } else s = p/(2*(float)Math.PI) * (float)Math.asin(1/a); if (t < 1) return -.5f*(a*(float)Math.pow(2,10*(t-=1)) * (float)Math.sin( (t-s)*(2*(float)Math.PI)/p )); return a*(float)Math.pow(2,-10*(t-=1)) * (float)Math.sin( (t-s)*(2*(float)Math.PI)/p )*.5f + 1; } protected float param_a; protected float param_p; protected boolean setA = false; protected boolean setP = false; public ElasticInOut a(float a) { param_a = a; this.setA = true; return this; } public ElasticInOut p(float p) { param_p = p; this.setP = true; return this; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/ElasticOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class ElasticOut implements TimeInterpolator { protected float param_a; protected float param_p; protected boolean setA = false; protected boolean setP = false; @Override public float getInterpolation(float t) { float a = param_a; float p = param_p; if (t==0) return 0; if (t==1) return 1; if (!setP) p=.3f; float s; if (!setA || a < 1) { a=1; s=p/4; } else s = p/(2*(float)Math.PI) * (float)Math.asin(1/a); return a*(float)Math.pow(2,-10*t) * (float)Math.sin( (t-s)*(2*Math.PI)/p ) + 1; } public ElasticOut a(float a) { param_a = a; this.setA = true; return this; } public ElasticOut p(float p) { param_p = p; this.setP = true; return this; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/ExpoIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class ExpoIn implements TimeInterpolator { @Override public float getInterpolation(float t) { return (t==0) ? 0 : (float) Math.pow(2, 10 * (t - 1)); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/ExpoInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class ExpoInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { if (t==0) return 0; if (t==1) return 1; if ((t*=2) < 1) return 0.5f * (float) Math.pow(2, 10 * (t - 1)); return 0.5f * (-(float)Math.pow(2, -10 * --t) + 2); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/ExpoOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class ExpoOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return (t==1) ? 1 : -(float) Math.pow(2, -10 * t) + 1; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuadIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuadIn implements TimeInterpolator { @Override public float getInterpolation(float t) { return t*t; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuadInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuadInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { if ((t*=2) < 1) return 0.5f*t*t; return -0.5f * ((--t)*(t-2) - 1); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuadOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuadOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return -t*(t-2); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuartIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuartIn implements TimeInterpolator { @Override public float getInterpolation(float t) { return t*t*t*t; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuartInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuartInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { if ((t*=2) < 1) return 0.5f*t*t*t*t; return -0.5f * ((t-=2)*t*t*t - 2); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuartOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuartOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return -((t-=1)*t*t*t - 1); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuintIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 30.05.14. */ public class QuintIn implements TimeInterpolator { @Override public float getInterpolation(float t) { return t*t*t*t*t; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuintInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 29.05.14. */ public class QuintInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { if ((t*=2) < 1) return 0.5f*t*t*t*t*t; return 0.5f*((t-=2)*t*t*t*t + 2); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/QuintOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class QuintOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return (t-=1)*t*t*t*t + 1; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/SineIn.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class SineIn implements TimeInterpolator { @Override public float getInterpolation(float t) { return (float) -Math.cos(t * (Math.PI/2)) + 1; } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/SineInOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class SineInOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return -0.5f * ((float) Math.cos(Math.PI*t) - 1); } } ================================================ FILE: lib/src/main/java/no/agens/depth/lib/tween/interpolators/SineOut.java ================================================ package no.agens.depth.lib.tween.interpolators; import android.animation.TimeInterpolator; /** * Created by danielzeller on 09.04.15. */ public class SineOut implements TimeInterpolator { @Override public float getInterpolation(float t) { return (float) Math.sin(t * (Math.PI/2)); } } ================================================ FILE: lib/src/main/res/values/dept_view.xml ================================================ ================================================ FILE: lib/src/main/res/values/values.xml ================================================ #03000000 #47000000 1dp ================================================ FILE: lib/src/test/java/no/agens/depth/ExampleUnitTest.java ================================================ package no.agens.depth; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: settings.gradle ================================================ include ':app', ':lib'