1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.apis.graphics;
18 
19 import android.animation.ObjectAnimator;
20 import android.app.Activity;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Outline;
24 import android.graphics.Paint;
25 import android.graphics.Path;
26 import android.graphics.PorterDuff;
27 import android.graphics.drawable.ShapeDrawable;
28 import android.graphics.drawable.shapes.OvalShape;
29 import android.graphics.drawable.shapes.RectShape;
30 import android.graphics.drawable.shapes.RoundRectShape;
31 import android.graphics.drawable.shapes.Shape;
32 import android.os.Bundle;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.animation.AccelerateInterpolator;
36 import android.view.animation.DecelerateInterpolator;
37 import android.widget.Button;
38 import android.widget.CheckBox;
39 import android.widget.CompoundButton;
40 import com.example.android.apis.R;
41 
42 import java.util.ArrayList;
43 
44 public class ShadowCardDrag extends Activity {
45     private static final float MAX_Z_DP = 10;
46     private static final float MOMENTUM_SCALE = 10;
47     private static final int MAX_ANGLE = 10;
48 
49     private class CardDragState {
50         long lastEventTime;
51         float lastX;
52         float lastY;
53 
54         float momentumX;
55         float momentumY;
56 
onDown(long eventTime, float x, float y)57         public void onDown(long eventTime, float x, float y) {
58             lastEventTime = eventTime;
59             lastX = x;
60             lastY = y;
61 
62             momentumX = 0;
63             momentumY = 0;
64         }
65 
onMove(long eventTime, float x, float y)66         public void onMove(long eventTime, float x, float y) {
67             final long deltaT = eventTime - lastEventTime;
68 
69             if (deltaT != 0) {
70                 float newMomentumX = (x - lastX) / (mDensity * deltaT);
71                 float newMomentumY = (y - lastY) / (mDensity * deltaT);
72 
73                 momentumX = 0.9f * momentumX + 0.1f * (newMomentumX * MOMENTUM_SCALE);
74                 momentumY = 0.9f * momentumY + 0.1f * (newMomentumY * MOMENTUM_SCALE);
75 
76                 momentumX = Math.max(Math.min((momentumX), MAX_ANGLE), -MAX_ANGLE);
77                 momentumY = Math.max(Math.min((momentumY), MAX_ANGLE), -MAX_ANGLE);
78 
79                 //noinspection SuspiciousNameCombination
80                 mCard.setRotationX(-momentumY);
81                 //noinspection SuspiciousNameCombination
82                 mCard.setRotationY(momentumX);
83 
84                 if (mShadingEnabled) {
85                     float alphaDarkening = (momentumX * momentumX + momentumY * momentumY) / (90 * 90);
86                     alphaDarkening /= 2;
87 
88                     int alphaByte = 0xff - ((int)(alphaDarkening * 255) & 0xff);
89                     int color = Color.rgb(alphaByte, alphaByte, alphaByte);
90                     mCardBackground.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
91                 }
92             }
93 
94             lastX = x;
95             lastY = y;
96             lastEventTime = eventTime;
97         }
98 
onUp()99         public void onUp() {
100             ObjectAnimator flattenX = ObjectAnimator.ofFloat(mCard, "rotationX", 0);
101             flattenX.setDuration(100);
102             flattenX.setInterpolator(new AccelerateInterpolator());
103             flattenX.start();
104 
105             ObjectAnimator flattenY = ObjectAnimator.ofFloat(mCard, "rotationY", 0);
106             flattenY.setDuration(100);
107             flattenY.setInterpolator(new AccelerateInterpolator());
108             flattenY.start();
109             mCardBackground.setColorFilter(null);
110         }
111     }
112 
113     /**
114      * Simple shape example that generates a shadow casting outline.
115      */
116     private static class TriangleShape extends Shape {
117         private final Path mPath = new Path();
118 
119         @Override
onResize(float width, float height)120         protected void onResize(float width, float height) {
121             mPath.reset();
122             mPath.moveTo(0, 0);
123             mPath.lineTo(width, 0);
124             mPath.lineTo(width / 2, height);
125             mPath.lineTo(0, 0);
126             mPath.close();
127         }
128 
129         @Override
draw(Canvas canvas, Paint paint)130         public void draw(Canvas canvas, Paint paint) {
131             canvas.drawPath(mPath, paint);
132         }
133 
134         @Override
getOutline(Outline outline)135         public void getOutline(Outline outline) {
136             outline.setConvexPath(mPath);
137         }
138     }
139 
140     private final ShapeDrawable mCardBackground = new ShapeDrawable();
141     private final ArrayList<Shape> mShapes = new ArrayList<Shape>();
142     private float mDensity;
143     private View mCard;
144 
145     private final CardDragState mDragState = new CardDragState();
146     private boolean mTiltEnabled;
147     private boolean mShadingEnabled;
148 
149     @Override
onCreate(Bundle savedInstanceState)150     protected void onCreate(Bundle savedInstanceState) {
151         super.onCreate(savedInstanceState);
152         setContentView(R.layout.shadow_card_drag);
153 
154         mDensity = getResources().getDisplayMetrics().density;
155         mShapes.add(new RectShape());
156         mShapes.add(new OvalShape());
157         float r = 10 * mDensity;
158         float radii[] = new float[] {r, r, r, r, r, r, r, r};
159         mShapes.add(new RoundRectShape(radii, null, null));
160         mShapes.add(new TriangleShape());
161 
162         mCardBackground.getPaint().setColor(Color.WHITE);
163         mCardBackground.setShape(mShapes.get(0));
164         final View cardParent = findViewById(R.id.card_parent);
165         mCard = findViewById(R.id.card);
166         mCard.setBackground(mCardBackground);
167 
168         final CheckBox tiltCheck = (CheckBox) findViewById(R.id.tilt_check);
169         tiltCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
170             @Override
171             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
172                 mTiltEnabled = isChecked;
173                 if (!mTiltEnabled) {
174                     mDragState.onUp();
175                 }
176             }
177         });
178 
179         final CheckBox shadingCheck = (CheckBox) findViewById(R.id.shading_check);
180         shadingCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
181             @Override
182             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
183                 mShadingEnabled = isChecked;
184                 if (!mShadingEnabled) {
185                     mCardBackground.setColorFilter(null);
186                 }
187             }
188         });
189 
190         final Button shapeButton = (Button) findViewById(R.id.shape_select);
191         shapeButton.setOnClickListener(new View.OnClickListener() {
192             int index = 0;
193             @Override
194             public void onClick(View v) {
195                 index = (index + 1) % mShapes.size();
196                 mCardBackground.setShape(mShapes.get(index));
197             }
198         });
199 
200         /**
201          * Enable any touch on the parent to drag the card. Note that this doesn't do a proper hit
202          * test, so any drag (including off of the card) will work.
203          *
204          * This enables the user to see the effect more clearly for the purpose of this demo.
205          */
206         cardParent.setOnTouchListener(new View.OnTouchListener() {
207             float downX;
208             float downY;
209             long downTime;
210 
211             @Override
212             public boolean onTouch(View v, MotionEvent event) {
213                 switch (event.getAction()) {
214                     case MotionEvent.ACTION_DOWN:
215                         downX = event.getX() - mCard.getTranslationX();
216                         downY = event.getY() - mCard.getTranslationY();
217                         downTime = event.getDownTime();
218                         ObjectAnimator upAnim = ObjectAnimator.ofFloat(mCard, "translationZ",
219                                 MAX_Z_DP * mDensity);
220                         upAnim.setDuration(100);
221                         upAnim.setInterpolator(new DecelerateInterpolator());
222                         upAnim.start();
223                         if (mTiltEnabled) {
224                             mDragState.onDown(event.getDownTime(), event.getX(), event.getY());
225                         }
226                         break;
227                     case MotionEvent.ACTION_MOVE:
228                         mCard.setTranslationX(event.getX() - downX);
229                         mCard.setTranslationY(event.getY() - downY);
230                         if (mTiltEnabled) {
231                             mDragState.onMove(event.getEventTime(), event.getX(), event.getY());
232                         }
233                         break;
234                     case MotionEvent.ACTION_UP:
235                         ObjectAnimator downAnim = ObjectAnimator.ofFloat(mCard, "translationZ", 0);
236                         downAnim.setDuration(100);
237                         downAnim.setInterpolator(new AccelerateInterpolator());
238                         downAnim.start();
239                         if (mTiltEnabled) {
240                             mDragState.onUp();
241                         }
242                         break;
243                 }
244                 return true;
245             }
246         });
247     }
248 }
249