设计原则
实体感隐喻
通过构建系统化的动效和空间合理化利用,并将两个理念合二为一,构成了实体隐喻。与众不同的触感是实体的基础,这一灵感来自我们对纸墨的研究,但是我们相信,随着科技的进步,应用前景将不可估量。
实体的表面和边缘提供基于真实效果的视觉体验,熟悉的触感让用户可以快速地理解和认知。实体的多样性可以让我们呈现出更多反映真实世界的设计效果,但同时又绝不会脱离客观的物理规律。
光效、表面质感、运动感这三点是解释物体运动规律、交互方式、空间关系的关键。真实的光效可以解释物体之间的交合关系、空间关系,以及单个物体的运动。
鲜明、形象、深思熟虑
新的视觉语言,在基本元素的处理上,借鉴了传统的印刷设计——排版、网格、空间、比例、配色、图像使用——这些基础的平面设计规范。在这些设计基础上下功夫,不但可以愉悦用户,而且能够构建出视觉层级、视觉意义以及视觉聚焦。精心选择色彩、图像、选择合乎比例的字体、留白,力求构建出鲜明、形象的用户界面,让用户沉浸其中。
Material Design设计语言强调根据用户行为凸显核心功能,进而为用户提供操作指引。
有意义的动画效果
动画效果(简称动效)可以有效地暗示、指引用户。动效的设计要根据用户行为而定,能够改变整体设计的触感。
动效应当在独立的场景呈现。通过动效,让物体的变化以更连续、更平滑的方式呈现给用户,让用户能够充分知晓所发生的变化。
动效应该是有意义的、合理的,动效的目的是为了吸引用户的注意力,以及维持整个系统的连续性体验。动效反馈需细腻、清爽。转场动效需高效、明晰。
Material 主题
android提供了三种Material Design风格Theme:
@android:style/Theme.Material (dark version)
@android:style/Theme.Material.Light (light version)
@android:style/Theme.Material.Light.DarkActionBar
我们还可以根据自己的需求继承主题,修改颜色实现我们自己的主题
<resources>
<!-- inherit from the material theme -->
<style name="AppTheme" parent="android:Theme.Material">
<!-- Main theme colors -->
<!-- your app branding color for the app bar -->
<item name="android:colorPrimary">@color/primary</item>
<!-- darker variant for the status bar and contextual app bars -->
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<!-- theme UI controls like checkboxes and text fields -->
<item name="android:colorAccent">@color/accent</item>
</style>
</resources>
Creating Lists and Cards
在程序中创建复杂的Material Design 样式的 List和Card,可以使用RecyclerView和CardView组件。
RecylerView类简化大数据集的显示和处理,通过提供:
- 布局管理者控制元素定位。
- 在通用的元素上操作上显示默认的动画,比如移除和增加元素。
使用RecyclerView组件,你需要指定一个Adapter和布局管理器,创建一个Adapter继承RecyclerView.Adapter类,具体的实现细节要根据数据集合视图的类型变化。
一个布局管理器定位Item视图在RecyclerView中,决定什么时候去回收它当他不再可见时。当重用(或者回收)一个视图时,布局管理器可能会请求适配器(Adapter)去替换子视图中的内容用不同的内容。通过这种方式回收重用视图,可以减少view的创建和避免更多的findViewById(),从而提高性能。
RecyclerView提供了以下内建的布局管理器:
LinearLayoutManager 显示Item 在一个水平或者垂直的滚动列表中。
GridLayoutManager 显示Item作为网格布局。
StaggeredGridLayoutManager 显示Item在交错的网格布局。
例子:
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
public class MyActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
}
...
}
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private String[] mDataset;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
// set the view's size, margins, paddings and layout parameters
...
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.mTextView.setText(mDataset[position]);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length;
}
}
CardView继承FrameLayout类,通过它可以显示信息在卡片内部,并且在不同的平台上有统一的样式。CardView组件可以有阴影和圆角。
创建有阴影的Card,使用card_view:cardElevation属性。CardView 使用真实的高度和动态阴影在Android5.0(API 21)和更高版本,较早的版本则使用传统的阴影。
使用这些属性去定制CardView的外观:
使用card_view:cardCornerRadius属性设置圆角的半径,在布局文件中。
使用CardView.setRadius方法设置圆角的半径在java代码中。
设置卡片的背景颜色,使用card_view:cardBackgroundColor属性。
下面是一个在xml布局文件中包含一个CardView的示例:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
... >
<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="200dp"
android:layout_height="200dp"
card_view:cardCornerRadius="4dp">
<TextView
android:id="@+id/info_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v7.widget.CardView>
</LinearLayout>
Defining Shadows and Clipping Views
Material Design 为用户界面元素引入了深度这个元素。深度帮助用户理解各个元素之间的重要关联和帮助用户关注他们手上的任务。
视图的高度
视图的高度(elevation),通过Z属性表现,通过他的阴影确定:Z值更高的视图投影出更大的阴影。视图只在Z=0的平面上投影处阴影;他们不会投影阴影在其他放在下面的视图上面和高于Z=0的平面。
有更高Z值的视图挡住Z值较低的视图。无论如何,Z值不会影响到View的大小。
一个View的Z值有两个组成部分,elevation(高度)和translation(平移).elevation是一个静态部分,translation 用于动画:
- Z = elevation + translationZ
在布局文件中设置evelation 使用android:elevation,在代码中使用View.setElevation()方法。
设置一个视图的平移,使用View.setTranslationZ()方法。
新的方法ViewPropertyAnimator.z()和ViewPropertyAnimator.translationZ()可以让你更容易的变动视图的高度。
自定义视图阴影和轮廓
背景是一个圆角矩形:
<!-- res/drawable/myrect.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#42000000" />
<corners android:radius="5dp" />
</shape>
当这个背景drawable作为视图的轮廓,视图投射出圆角阴影。提供一个自定义的轮廓,可以覆盖默认视图阴影的形状。
在自己的代码中自定义一个轮廓:
- 继承ViewOutlineProvider类
- 重写getOutline()方法
- 在视图中设置轮廓,使用View.setOutlineProvider()方法
你可以创建椭圆和圆角矩形轮廓使用OutLine类中的方法。视图默认的outline provider会根据视图的背景来生成轮廓。可以设置视图的outline provider为null,来阻止投射阴影。
裁剪视图
裁剪视图功能,可以让你更容易的改变视图的形状。你可以裁剪视图为了和其他的设计元素保持一致,或者改变成形状响应用户的输入。你可以裁剪一个视图的轮廓使用View.setClipToOutLine()方法,或者android:clipToOutline属性。只有矩形,圆角矩形,圆圈的轮廓支持被裁剪,可以使用Outline.canClip()方法检测是否支持被裁剪。
裁剪视图到一个drawable的形状,设置drawable作为视图的背景(让视图显示在其上),并且调用View.setClipToOutline()方法。
Working with Drawables
图片资源着色
在android 5.0(api 21)和更高版本,可以着色bitmap和.9 png 通过定义透明度遮盖。你可以着色通过使用颜色资源或者主题的属性去解析颜色资源(比如,?android:attr/colorPrimary).通常我们创建一次,然后资源自适应主题。
你可以给BitmapDrawable或NinePatchDrawable对象着色使用setTint()方法。你可以可以在布局文件中使用android:tint和android:tintMode属性设置着色颜色和着色模式。
从图片中抽取高亮颜色
support library r21和更高的版本中包括了Palette类,可以从一个图片中提取高亮颜色。这个类可以提起以下几种突出颜色:
- Vibrant 充满生机
- Vibrant dark 亮的充满生机
- Muted 柔和
- Muted dark 暗的柔和
- Muted light 亮的柔和
传递一个Bitmap对象给静态方法Palette.generate(),它会在后台线程帮你从后台线程提取颜色。如果你不能使用这个后台线程,使用Palette.generateAsync()方法,并且设置一个监听器listener.
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
public void onGenerated(Palette palette) {
// Do something with colors...
palette.getVibrantColor(Color.BLACK); //get a color in rgb value
}
});
创建矢量drawables
在android 5.0和更高版本中,可以创建矢量的drawable,在缩放的时候不会失真。你只需要定义一个矢量图片文件,相反的,使用bitmap位图则需要针对不同的分辨率创建多个文件。创建一个矢量图片,你需要说明图形的详细,在xml文件的标签下。
<!-- res/drawable/heart.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
<!-- intrinsic size of the drawable -->
android:height="256dp"
android:width="256dp"
<!-- size of the virtual canvas -->
android:viewportWidth="32"
android:viewportHeight="32">
<!-- draw a path -->
<path android:fillColor="#8fff"
android:pathData="M20.5,9.5
c-1.955,0,-3.83,1.268,-4.5,3
c-0.67,-1.732,-2.547,-3,-4.5,-3
C8.957,9.5,7,11.432,7,14
c0,3.53,3.793,6.257,9,11.5
c5.207,-5.242,9,-7.97,9,-11.5
C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>
Defining Custom Animations
自定义触摸反馈
触摸反馈在Material Design中在触摸点提供了一个即时视觉确认当用户作用在UI元素。按钮的默认触摸反馈动画是使用了新的RippleDrawable类,它会是波纹效果在不同状态间变换。
大多数情况下,我们可以使用这个功能通过在xml文件中定义背景:
?android:attr/selectableItemBackground 有界限的波纹
?android:attr/selectableItemBackgroundBorderless 可以超出视图区域的波纹(是21新添加的api)
另外,还以使用ripple元素定义RippleDrawable作为一个xml资源。
你可以给RippleDrawable对象分配一个颜色。使用主题的android:colorControlHighlight属性可以改变默认的触摸反馈颜色。
使用渐变效果
渐变动画为用户提供视觉上的持续性挡显示或者隐藏一组界面元素。ViewAnimationUtils.createCircularReveal()方法使你可以使用动画效果来显示或者隐藏一个视图。
显示一个先前隐藏的视图:
// previously invisible view
View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;
// get the final radius for the clipping circle
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
// create the animator for this view (the start radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
隐藏一个先前显示的视图:
// previously visible view
final View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;
// get the initial radius for the clipping circle
int initialRadius = myView.getWidth();
// create the animation (the final radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
}
});
// start the animation
anim.start();
自定义activity转换效果
activity间转换在Material Design程序中提供不同状态间的视觉连接通过在公用元素上动作或者转换。你可以为进入或退出的转换自定义动画,共享元素在不同的activity之间的转换效果。
android 5.0(api 21)提供以下进入和退出效果:
- explode(爆裂) - 从场景中间移动视图进入或者退出
- slide(滑动) - 视图从场景的一个边缘进入或者退出
- fade(淡入淡出)从场景添加或者移除一个视图通过改变他的透明
Android5.0(api 21)也支持共享元素过渡效果:
- changeBounds - 改变目标视图的布局边界
- changeClipBounds - 裁剪目标视图边界
- changeTransform - 改变目标视图的缩放比例和旋转角度
- changeImageTransform -改变目标图片的大小和缩放比例
例子:
首先,使用在从material theme继承的样式中,使用android:windowContentTransitions属性开启窗口内内容过渡效果。也可以在样式定义中第一进入,退出,共享元素的效果:
<style name="BaseAppTheme" parent="android:Theme.Material">
<!-- enable window content transitions -->
<item name="android:windowContentTransitions">true</item>
<!-- specify enter and exit transitions -->
<item name="android:windowEnterTransition">@transition/explode</item>
<item name="android:windowExitTransition">@transition/explode</item>
<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
@transition/change_image_transform</item>
</style>
示例中的过渡change_image_transform定义如下:
<!-- res/transition/change_image_transform.xml -->
<!-- (see also Shared Transitions below) -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeImageTransform/>
</transitionSet>
changeImageTransform元素对应ChangeImageTransform类
在代码中启用窗口内容过渡效果,使用Window.requestFeature()方法:
// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// set an exit transition
getWindow().setExitTransition(new Explode());
在代码中定义过渡效果,使用下面的方法,并传一个Transition对象:
Window.setEnterTransition()
Window.setExitTransition()
Window.setSharedElementEnterTransition()
Window.setSharedElementExitTransition()
如果你为一个activity开启过渡并且设置了一个退出过渡效果,过渡效果会在你打开其他activity的时候激活,像这样:
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
打开一个activity包含一个共享元素
- 在theme中开启窗口内容过渡效果
- 在style中指定一个共享元素过渡效果
- 在xml中定义过渡样式
- 在两个activity的样式文件中给共享元素分配一个相同的名字使用
android:transitionName
属性 - 使用
ActivityOptions.makeSceneTransitionAnimation()
方法。
// get the element that receives the click event
final View imgContainerView = findViewById(R.id.img_container);
// get the common element for the transition in this activity
final View androidRobotView = findViewById(R.id.image_small);
// define a click listener
imgContainerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(this, Activity2.class);
// create the transition animation - the images in the layouts
// of both activities are defined with android:transitionName="robot"
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(this, androidRobotView, "robot");
// start the new activity
startActivity(intent, options.toBundle());
}
});
使用曲线运动
视图状态改变动画
StateListAnimator定义动画当视图的状态改变的时候运行,下面的例子是怎么在xml中定义一个StateListAnimator动画:
<!-- animate the translationZ property of a view when pressed -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectAnimator android:propertyName="translationZ"
android:duration="@android:integer/config_shortAnimTime"
android:valueTo="2dp"
android:valueType="floatType"/>
<!-- you could have other objectAnimator elements
here for "x" and "y", or other properties -->
</set>
</item>
<item android:state_enabled="true"
android:state_pressed="false"
android:state_focused="true">
<set>
<objectAnimator android:propertyName="translationZ"
android:duration="100"
android:valueTo="0"
android:valueType="floatType"/>
</set>
</item>
</selector>
给视图附加自定义的视图状态动画,使用selector元素在xml文件中定义一个动画祥例子中这样,给视图分配动画使用android:stateListAnimator属性。在代码中使用,使用AnimationInflater.loadStateListAnimator()方法,并且使用View.setStateListAnimator()方法。
可绘矢量动画
可绘制矢量图在拉伸时不会失真。AnimatedVectorDrawable类让你可以在可绘制矢量图上面作用动画。
通常需要在三个xml文件中定义可动的矢量图:
- 一个矢量图使用元素,放在res/drawable/下。
- 一个可动的矢量图使用元素,放在res/drawable/下。
- 一个或更多个动画对象使用元素,放在res/anim/下。
可动矢量图可以使用和元素。元素定义一系列路径或者子组,元素定义可绘图的路径。
兼容性
Use the Support Library
The v7 Support Libraries r21 and above includes the following material design features:
- Material design styles for some system widgets when you apply one of the
Theme.AppCompat
themes. - Color palette theme attributes in theTheme。
- AppCompat themes.
- The RecyclerView widget to display data collections.
- The CardView widget to create cards.
- The Palette class to extract prominent colors from images.
The Theme.AppCompat themes provide material design styles for these widgets:
- EditText
- Spinner
- CheckBox
- RadioButton
- SwitchCompat
- CheckedTextView