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