Home | History | Annotate | Download | only in graphics
      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 
     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 
     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 
     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
    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
    130         public void draw(Canvas canvas, Paint paint) {
    131             canvas.drawPath(mPath, paint);
    132         }
    133 
    134         @Override
    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
    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