前言
Material Design 规范指导里面特别提出了阴影的重要性和如何正确使用的方法(点击传送),那我们就更加不能忽视这一点了,本篇文章就要教大家如何设置阴影,做出一个有层次感的界面。
设置方法:
android:elevation
分别设置不同数值的elevation效果如下:
layout:
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView android:layout_width="100dp"
- android:layout_margin="2dp"
- android:layout_height="100dp"
- android:text="test"
- android:background="@android:color/white"
- android:gravity="center"
- android:elevation="1dip"
- />
- <TextView android:layout_width="100dp"
- android:layout_margin="2dp"
- android:layout_height="100dp"
- android:text="test"
- android:background="@android:color/white"
- android:gravity="center"
- android:elevation="4dip"/>
- <TextView android:layout_width="100dp"
- android:layout_margin="2dp"
- android:layout_height="100dp"
- android:text="test"
- android:background="@android:color/white"
- android:gravity="center"
- android:elevation="8dip"
- />
- </LinearLayout>
View的z值由两部分组成,elevation和translationZ.
eleavation是静态的成员,translationZ是用来做动画。
Z = elevation + translationZ
在布局中使用 android:elevation属性去定义
在代码中使用 View.setElevation 方法去定义
设置视图的translation,可以使用View.setTranslationZ方法
新的ViewPropertyAnimator.z和ViewPropertyAnimator.translationZ方法可以设置视图的elevation值
在5.0 的API Demos中 Graphics-》Shadow Card Drag 和 Shadow Card stack 这两个例子就很好的使用translationZ和eleavation

- /*
- * 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 void getOutline(Outline outline) {
- outline.setConvexPath(mPath);
- }
- }
- 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;
- }
- });
- }
- }
layout:
- <?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>
- /*
- * 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) {}
- }
- }
- <?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>