Add Shadow demos to ApiDemos
bug:15724818
Change-Id: I22e3faaee9bb1d46f9c9f16cac38069071e1fc0b
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 095d7ee..7d681cd 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -2685,6 +2685,24 @@
</intent-filter>
</activity>
+ <activity android:name=".graphics.ShadowCardStack"
+ android:label="Graphics/Shadow Card Stack"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".graphics.ShadowCardDrag"
+ android:label="Graphics/Shadow Card Drag"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".graphics.WindowSurface"
android:label="Graphics/Surface Window">
<intent-filter>
diff --git a/samples/ApiDemos/res/drawable/round_rect.xml b/samples/ApiDemos/res/drawable/round_rect.xml
new file mode 100644
index 0000000..6577d14
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/round_rect.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#ffffffff" />
+ <corners android:radius="10dp" />
+</shape>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/shadow_card_drag.xml b/samples/ApiDemos/res/layout/shadow_card_drag.xml
new file mode 100644
index 0000000..c3bc474
--- /dev/null
+++ b/samples/ApiDemos/res/layout/shadow_card_drag.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/card_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal" >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <CheckBox
+ android:id="@+id/tilt_check"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:text="@string/enable_tilt"/>
+ <CheckBox
+ android:id="@+id/shading_check"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:text="@string/enable_shading"/>
+ <Button
+ android:id="@+id/shape_select"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/select_shape"/>
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/card"
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ android:background="@drawable/round_rect"
+ android:clipToPadding="false"
+ android:gravity="center"
+ android:padding="20dp"
+ android:text="@string/draggable_card"
+ android:textSize="20sp"
+ android:elevation="2dp"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/shadow_card_stack.xml b/samples/ApiDemos/res/layout/shadow_card_stack.xml
new file mode 100644
index 0000000..11d60f7
--- /dev/null
+++ b/samples/ApiDemos/res/layout/shadow_card_stack.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/card_parent"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:gravity="center">
+ <TextView
+ android:layout_width="250dp"
+ android:layout_height="150dp"
+ android:background="@drawable/round_rect"
+ android:padding="20dp"
+ android:textSize="20sp" />
+ <TextView
+ android:layout_width="250dp"
+ android:layout_height="150dp"
+ android:background="@drawable/round_rect"
+ android:padding="20dp"
+ android:textSize="20sp" />
+ <TextView
+ android:layout_width="250dp"
+ android:layout_height="150dp"
+ android:background="@drawable/round_rect"
+ android:padding="20dp"
+ android:textSize="20sp" />
+ <TextView
+ android:layout_width="250dp"
+ android:layout_height="150dp"
+ android:background="@drawable/round_rect"
+ android:padding="20dp"
+ android:textSize="20sp" />
+ <TextView
+ android:layout_width="250dp"
+ android:layout_height="150dp"
+ android:background="@drawable/round_rect"
+ android:padding="20dp"
+ android:textSize="20sp" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 46ef1a2..6a2df2e 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -904,6 +904,11 @@
<string name="density_title">Density: Unknown Screen</string>
<string name="camera_alert">Device has only one camera!</string>
<string name="switch_cam">Switch Camera</string>
+ <string name="draggable_card">Draggable Card</string>
+
+ <string name="enable_tilt">Enable Tilt</string>
+ <string name="enable_shading">Enable Shading</string>
+ <string name="select_shape">Select Shape</string>
<!-- ============================ -->
<!-- media examples strings -->
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardDrag.java b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardDrag.java
new file mode 100644
index 0000000..facc009
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardDrag.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.shapes.RectShape;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.example.android.apis.R;
+
+import java.util.ArrayList;
+
+public class ShadowCardDrag extends Activity {
+ private static final float MAX_Z_DP = 10;
+ private static final float MOMENTUM_SCALE = 10;
+ private static final int MAX_ANGLE = 10;
+
+ private class CardDragState {
+ long lastEventTime;
+ float lastX;
+ float lastY;
+
+ float momentumX;
+ float momentumY;
+
+ public void onDown(long eventTime, float x, float y) {
+ lastEventTime = eventTime;
+ lastX = x;
+ lastY = y;
+
+ momentumX = 0;
+ momentumY = 0;
+ }
+
+ public void onMove(long eventTime, float x, float y) {
+ final long deltaT = eventTime - lastEventTime;
+
+ if (deltaT != 0) {
+ float newMomentumX = (x - lastX) / (mDensity * deltaT);
+ float newMomentumY = (y - lastY) / (mDensity * deltaT);
+
+ momentumX = 0.9f * momentumX + 0.1f * (newMomentumX * MOMENTUM_SCALE);
+ momentumY = 0.9f * momentumY + 0.1f * (newMomentumY * MOMENTUM_SCALE);
+
+ momentumX = Math.max(Math.min((momentumX), MAX_ANGLE), -MAX_ANGLE);
+ momentumY = Math.max(Math.min((momentumY), MAX_ANGLE), -MAX_ANGLE);
+
+ //noinspection SuspiciousNameCombination
+ mCard.setRotationX(-momentumY);
+ //noinspection SuspiciousNameCombination
+ mCard.setRotationY(momentumX);
+
+ if (mShadingEnabled) {
+ float alphaDarkening = (momentumX * momentumX + momentumY * momentumY) / (90 * 90);
+ alphaDarkening /= 2;
+
+ int alphaByte = 0xff - ((int)(alphaDarkening * 255) & 0xff);
+ int color = Color.rgb(alphaByte, alphaByte, alphaByte);
+ mCardBackground.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
+ }
+ }
+
+ lastX = x;
+ lastY = y;
+ lastEventTime = eventTime;
+ }
+
+ public void onUp() {
+ ObjectAnimator flattenX = ObjectAnimator.ofFloat(mCard, "rotationX", 0);
+ flattenX.setDuration(100);
+ flattenX.setInterpolator(new AccelerateInterpolator());
+ flattenX.start();
+
+ ObjectAnimator flattenY = ObjectAnimator.ofFloat(mCard, "rotationY", 0);
+ flattenY.setDuration(100);
+ flattenY.setInterpolator(new AccelerateInterpolator());
+ flattenY.start();
+ mCardBackground.setColorFilter(null);
+ }
+ }
+
+ /**
+ * Simple shape example that generates a shadow casting outline.
+ */
+ private static class TriangleShape extends Shape {
+ private final Path mPath = new Path();
+
+ @Override
+ protected void onResize(float width, float height) {
+ mPath.reset();
+ mPath.moveTo(0, 0);
+ mPath.lineTo(width, 0);
+ mPath.lineTo(width / 2, height);
+ mPath.lineTo(0, 0);
+ mPath.close();
+ }
+
+ @Override
+ public void draw(Canvas canvas, Paint paint) {
+ canvas.drawPath(mPath, paint);
+ }
+
+ @Override
+ public boolean getOutline(Outline outline) {
+ outline.setConvexPath(mPath);
+ return true;
+ }
+ }
+
+ private final ShapeDrawable mCardBackground = new ShapeDrawable();
+ private final ArrayList<Shape> mShapes = new ArrayList<Shape>();
+ private float mDensity;
+ private View mCard;
+
+ private final CardDragState mDragState = new CardDragState();
+ private boolean mTiltEnabled;
+ private boolean mShadingEnabled;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.shadow_card_drag);
+
+ mDensity = getResources().getDisplayMetrics().density;
+ mShapes.add(new RectShape());
+ mShapes.add(new OvalShape());
+ float r = 10 * mDensity;
+ float radii[] = new float[] {r, r, r, r, r, r, r, r};
+ mShapes.add(new RoundRectShape(radii, null, null));
+ mShapes.add(new TriangleShape());
+
+ mCardBackground.getPaint().setColor(Color.WHITE);
+ mCardBackground.setShape(mShapes.get(0));
+ final View cardParent = findViewById(R.id.card_parent);
+ mCard = findViewById(R.id.card);
+ mCard.setBackground(mCardBackground);
+
+ final CheckBox tiltCheck = (CheckBox) findViewById(R.id.tilt_check);
+ tiltCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mTiltEnabled = isChecked;
+ if (!mTiltEnabled) {
+ mDragState.onUp();
+ }
+ }
+ });
+
+ final CheckBox shadingCheck = (CheckBox) findViewById(R.id.shading_check);
+ shadingCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mShadingEnabled = isChecked;
+ if (!mShadingEnabled) {
+ mCardBackground.setColorFilter(null);
+ }
+ }
+ });
+
+ final Button shapeButton = (Button) findViewById(R.id.shape_select);
+ shapeButton.setOnClickListener(new View.OnClickListener() {
+ int index = 0;
+ @Override
+ public void onClick(View v) {
+ index = (index + 1) % mShapes.size();
+ mCardBackground.setShape(mShapes.get(index));
+ }
+ });
+
+ /**
+ * Enable any touch on the parent to drag the card. Note that this doesn't do a proper hit
+ * test, so any drag (including off of the card) will work.
+ *
+ * This enables the user to see the effect more clearly for the purpose of this demo.
+ */
+ cardParent.setOnTouchListener(new View.OnTouchListener() {
+ float downX;
+ float downY;
+ long downTime;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ downX = event.getX() - mCard.getTranslationX();
+ downY = event.getY() - mCard.getTranslationY();
+ downTime = event.getDownTime();
+ ObjectAnimator upAnim = ObjectAnimator.ofFloat(mCard, "translationZ",
+ MAX_Z_DP * mDensity);
+ upAnim.setDuration(100);
+ upAnim.setInterpolator(new DecelerateInterpolator());
+ upAnim.start();
+ if (mTiltEnabled) {
+ mDragState.onDown(event.getDownTime(), event.getX(), event.getY());
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mCard.setTranslationX(event.getX() - downX);
+ mCard.setTranslationY(event.getY() - downY);
+ if (mTiltEnabled) {
+ mDragState.onMove(event.getEventTime(), event.getX(), event.getY());
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ ObjectAnimator downAnim = ObjectAnimator.ofFloat(mCard, "translationZ", 0);
+ downAnim.setDuration(100);
+ downAnim.setInterpolator(new AccelerateInterpolator());
+ downAnim.start();
+ if (mTiltEnabled) {
+ mDragState.onUp();
+ }
+ break;
+ }
+ return true;
+ }
+ });
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardStack.java b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardStack.java
new file mode 100644
index 0000000..5a34251
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/ShadowCardStack.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.apis.R;
+
+import java.util.ArrayList;
+
+public class ShadowCardStack extends Activity {
+
+ private static final float X_SHIFT_DP = 1000;
+ private static final float Y_SHIFT_DP = 50;
+ private static final float Z_LIFT_DP = 8;
+ private static final float ROTATE_DEGREES = 15;
+
+ public AnimatorSet createSet(ArrayList<Animator> items, long startDelay) {
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(items);
+ set.setStartDelay(startDelay);
+ return set;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.shadow_card_stack);
+
+ float density = getResources().getDisplayMetrics().density;
+
+ final ViewGroup cardParent = (ViewGroup) findViewById(R.id.card_parent);
+
+ final float X = X_SHIFT_DP * density;
+ final float Y = Y_SHIFT_DP * density;
+ final float Z = Z_LIFT_DP * density;
+
+ ArrayList<Animator> towardAnimators = new ArrayList<Animator>();
+ ArrayList<Animator> expandAnimators = new ArrayList<Animator>();
+ ArrayList<Animator> moveAwayAnimators = new ArrayList<Animator>();
+ ArrayList<Animator> moveBackAnimators = new ArrayList<Animator>();
+ ArrayList<Animator> awayAnimators = new ArrayList<Animator>();
+ ArrayList<Animator> collapseAnimators = new ArrayList<Animator>();
+
+ final int max = cardParent.getChildCount();
+ for (int i = 0; i < max; i++) {
+ TextView card = (TextView) cardParent.getChildAt(i);
+ card.setText("Card number " + i);
+
+ float targetY = (i - (max-1) / 2.0f) * Y;
+ Animator expand = ObjectAnimator.ofFloat(card, "translationY", targetY);
+ expandAnimators.add(expand);
+
+ Animator toward = ObjectAnimator.ofFloat(card, "translationZ", i * Z);
+ toward.setStartDelay(200 * ((max) - i));
+ towardAnimators.add(toward);
+
+ card.setPivotX(X_SHIFT_DP);
+ Animator rotateAway = ObjectAnimator.ofFloat(card, "rotationY",
+ i == 0 ? 0 : ROTATE_DEGREES);
+ rotateAway.setStartDelay(200 * ((max) - i));
+ rotateAway.setDuration(100);
+ moveAwayAnimators.add(rotateAway);
+ Animator slideAway = ObjectAnimator.ofFloat(card, "translationX",
+ i == 0 ? 0 : X);
+ slideAway.setStartDelay(200 * ((max) - i));
+ slideAway.setDuration(100);
+ moveAwayAnimators.add(slideAway);
+
+ Animator rotateBack = ObjectAnimator.ofFloat(card, "rotationY", 0);
+ rotateBack.setStartDelay(200 * i);
+ moveBackAnimators.add(rotateBack);
+ Animator slideBack = ObjectAnimator.ofFloat(card, "translationX", 0);
+ slideBack.setStartDelay(200 * i);
+ moveBackAnimators.add(slideBack);
+
+ Animator away = ObjectAnimator.ofFloat(card, "translationZ", 0);
+ away.setStartDelay(200 * i);
+ awayAnimators.add(away);
+
+ Animator collapse = ObjectAnimator.ofFloat(card, "translationY", 0);
+ collapseAnimators.add(collapse);
+ }
+
+ AnimatorSet totalSet = new AnimatorSet();
+ totalSet.playSequentially(
+ createSet(expandAnimators, 250),
+ createSet(towardAnimators, 0),
+
+ createSet(moveAwayAnimators, 250),
+ createSet(moveBackAnimators, 0),
+
+ createSet(awayAnimators, 250),
+ createSet(collapseAnimators, 0));
+ totalSet.start();
+ totalSet.addListener(new RepeatListener(totalSet));
+ }
+
+ public static class RepeatListener implements Animator.AnimatorListener {
+ final Animator mRepeatAnimator;
+ public RepeatListener(Animator repeatAnimator) {
+ mRepeatAnimator = repeatAnimator;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (animation == mRepeatAnimator) {
+ mRepeatAnimator.start();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ }
+}