由于第十章是介绍的Bmob云服务器,内容不多而且都很简单,就直接跳过了。下面来看看十一章关于Material Design的内容。
一、Material Design主题
使用兼容包里的Material Design主题
如果像书中说的那样直接使用Material Design的主题有一个缺点,就是只能运行在Android5.+的设备上,而Android 5.0以下的设备还需要重新写其他的主题。这样就比较麻烦了,但是也不要紧,Google还提供了了兼容的主题包来兼容旧版本的设备,就不用那么麻烦了。现在使用AndroidStudio直接创建的工程就默认使用的是support-v7包中的兼容主题,大致有以下几个:
Theme.AppCompat
Theme.AppCompat.Light
Theme.AppCompat.Light.DarkActionBar
Theme.AppCompat.Light.NoActionBar
在values/styles.xml文件里直接继承以上几个主题就可以使应用的界面具有Material Design的效果。而不用重新创建values-v21/style.xml文件。下面是一个Material Design的示例主题:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Actionbar的顏色. -->
<item name="colorPrimary">@color/colorPrimary</item>
<!-- 状态栏的颜色,在Material Design中状态栏比Actionbar要稍微深一些-->
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<!-- colorAccent指控件的颜色,如Switch控件和CheckBox控件-->
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowContentTransitions">true</item>
</style>
需要注意的是三个item中的属性不需要加android:命名空间。这样使用的就是support-v7包中的属性,在Android5.0以下也会生效。
二、Palette类
Palette类可以用来从Bitmap上提取特定色调的,修改当前主题的色调上来达到一致的显示效果。Palette类可以提取的色调种类有以下几个:
- Vibrant——充满活力的
- VibrantDark——充满活力的暗色
- VibrantLight——充满活力的亮色
- Muted——柔和的
- MutedDark——柔和的暗色
- MutedLight——柔和的亮色
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.images);
new Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
Palette.Swatch darkvibrant = palette.getDarkVibrantSwatch();
Palette.Swatch vibrant= palette.getVibrantSwatch();
Palette.Swatch lightvibrant= palette.getLightVibrantSwatch();
Palette.Swatch muted= palette.getMutedSwatch();
Palette.Swatch darkmuted= palette.getDarkMutedSwatch();
Palette.Swatch lightmuted= palette.getLightMutedSwatch();
if (vibrant != null && getSupportActionBar() != null) {
getSupportActionBar().setBackgroundDrawable(
new ColorDrawable(lightmuted.getRgb()));
Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(lightmuted.getRgb());
}
}
}
});
有一需要注意的是,在不同的bitmap位图上,不是所有的Swatch类型对象都可以得到,有时也会返回null,因此一定要判断是否为null,否则程序就会出现异常。或者使用palette.getXXXColor(int defaultColor);方法。这个方法传入一个默认的颜色,如果获取不到Swatch对象,就会返回传入的默认颜色,省去了判null的步骤,下面是这个方法的代码:
public int getLightMutedColor(@ColorInt int defaultColor) {
Swatch swatch = getLightMutedSwatch();
return swatch != null ? swatch.getRgb() : defaultColor;
}
可以看到内部也是调用了getXXXSwatch()方法,如果为null就返回defaultColor。然后使用getSupportActionBar.setBackgroundDrawable(new ColorDrawable(swatch.getRgb()));为Actionbar/Toolbar设置颜色,用getWindow().setStatusBarColor(swatch.getRgb());为状态栏设置颜色。
- add
- multiply
- screen
- src_in
- src_atop
- src_over
下面来看看具体的实现代码:
<span style="white-space:pre"> </span>View v1 = findViewById(R.id.tv_1);
View v2 = findViewById(R.id.tv_2);
v1.setClipToOutline(true);
v2.setClipToOutline(true);
ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), 10);
}
};
ViewOutlineProvider viewOutlineProvider1 = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), view.getHeight() / 2);
}
};
v1.setOutlineProvider(viewOutlineProvider);
v2.setOutlineProvider(viewOutlineProvider1);
在为视图调用setOutlineProvider之前务必要调用setClipToOutline并设置为true,否则是看不到裁剪后的效果的。
五、RecyclerView
Android 5.0最重要的更新之一就是RecyclerView了,它将使用了很久的ListView进行了升级,新的RecyclerView使用起来更加方便和高效,并且灵活性也是很高,配合LayoutManager可以实现各种不同的列表布局,同样使用RecyclerView也需要引入依赖包:com.android.support:recyclerview。
布局的配置RecyclerView基本与ListView相同。RecyclerView的优点是在Adapter的配置上,以前我们写ListView的Adapter基本都会实现一个视图缓存类ViewHolder,而RecyclerView默认就集成了这样一个类,只需要几行代码就能完成相应的配置工作。下面是一个RecyclerView.Adapter的代码示例:
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
List<String> mDatas;
public MyRecyclerAdapter(List<String> datas){
mDatas = datas;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//在这里初始化ViewHolder类对象,将item布局解析出来并传入viewholder中进行初始化
View item = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_item, parent, false);
return new MyViewHolder(item);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
//在这里对viewholder中的控件进行绑定
holder.tv.setText(getItem(position));
}
public String getItem(int position){
return mDatas.get(position);
}
@Override
public int getItemCount() {
return mDatas.size();
}
public interface OnItemClickListener{
void onItemClick(View v,int position);
}
private OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener listener){
mListener = listener;
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tv;
//在viewholder的构造方法中对item布局进行控件绑定,RecyclerView没有提供控件的点击监听,需要自己实用接口实现
public MyViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
tv.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClick(v,getLayoutPosition());
}
}
}
}
另外需要注意的是RecyclerView并没有提供item的点击事件监听,因此要自己来为item布局添加点击监听,并通过接口回调出去;当RecyclerView中的数据发生变化时推荐使用Adapter的notifyItemRemoved(int position)和notifyItemInserted(int position),这两个方法在数据发生变化时为RecyclerView提供一个item移除或插入的动画。
定义好Adapter之后就可以在Activity中对RecyclerView进行设置了,另外RecyclerView中还提供了了一个类LayoutManager来管理RecyclerView的布局。这也是RecyclerView比ListView灵活的地方,系统默认提供了LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager分别用来设置线性列表、表格和瀑布流,这几个LayoutManager都可以通过设置orientation属性来指定是水平列表还是垂直列表,同样可以自定义LayoutManager来实现更多的功能。下面是MainActivity中对RecyclerView进行配置的代码:
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_list);
List<String> datas = generateDatas();
mAdapter = new MyRecyclerAdapter(datas);
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
另外RecyclerView还有两个常用到的方法:RecyclerView.setItemAnimator(ItemAnimator animator)为item指定动画,RecyclerView.addItemDecoration(ItemDecoration decor)为两个item之间指定自定义的分割线。可见RecyclerView的功能是多么的强大,赶快抛弃ListView吧!
六、Activity过渡动画
在Android5.0中,Google对Activity的转场效果设计了更加丰富的动画,下面是Android5.0中提供的三种Transition类型:
- 进入(getWindow().setEnterTransition(Transition transition))——决定Activity中所有的视图怎么进入屏幕
- 退出(getWindow().setExitTransition(Transition transition))——决定Activity中所有的视图怎么退出屏幕
- 共享元素——共享元素过渡动画决定两个Activities之间的过渡,怎么共享它们的视图。
- explode(分解)——从屏幕中间进或出,移动视图
- slide(滑动)——从屏幕边缘进或出,移动视图
- fade(淡出)——改变屏幕上视图的不透明度达到添加或移除视图
- changeBounds——改变目标视图的布局边界
- changeClipBounds——裁剪目标视图边界
- changeTransform——改变目标视图的缩放比例和旋转角度
- changeImageTransform——改变目标图片的大小和缩放比例
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
bundle = ActivityOptions.makeSceneTransitionAnimation(
MainActivity.this).toBundle();
startActivity(intent, bundle);
}<span style="white-space:pre"> </span>
通过ActivityOptions中的静态方法makeSceneTransitionAnimation(Activity activity)创建一个 activityOptions对象并转换成bundle在startActivity传入即可。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setEnterTransition(new Fade());
}
当然也可以调用setExitTransition()来设置退出的动画。
共享元素即Activity1和Activity2都有的元素,也就是图中的Android机器人,使用共享元素的动画在从Activity1跳转到Activity2时共享元素会通过动画的方式直接显示在Activity2上,并不会消失再出现。
bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this,v,"card").toBundle();
startActivity(intent, bundle);
如果有多个共享元素,可以使用Pair.create(View view,String name)来创建多个pair对象,makeSceneTransitionAnimation支持可变的pair参数,如下面的代码:
bundle = ActivityOptions.makeSceneTransitionAnimation(
MainActivity.this,Pair.create(v,"card"),Pair.create(fab,"fab")).toBundle();
startActivity(intent, bundle);
使用共享元素动画不需要在SecondActivity中调用window.setEnterTransition()就可以直接执行动画。
七、Material Design动画效果
<!-- 波纹有边界-->
android:background="?android:attr/selectableItemBackground"
<!-- 波纹可以超出边界-->
android:background="?android:attr/selectableItemBackgroundBorderless"
同样也可以通过创建一个RippleDrawable文件来实现水波纹效果:
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
<!-- 未点击时的颜色-->
android:color="#FFFF80AB"
>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/colorAccent"/>
</shape>
</item>
</ripple>
然后指定给view的background属性使用即可。
public static Animator createCircularReveal(View view,
int centerX, int centerY, float startRadius, float endRadius) {
return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
}
下面是这个方法几个传入参数的含义:
- centerX——动画开始的中心点X
- centerY——动画开始的中心点Y
- startRadius——动画开始的半径
- endRadius——动画结束的半径
Animator animator = ViewAnimationUtils.createCircularReveal(v,
v.getWidth() / 2,
v.getHeight() / 2,
0,
v.getWidth());
animator.setDuration(2000);
animator.start();
3.View state changes Animation
- StateListAnimator
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectAnimator
android:duration="@android:integer/config_mediumAnimTime"
android:propertyName="rotationY"
android:valueTo="180"
android:valueType="floatType"
/>
</set>
</item>
<item android:state_pressed="false">
<set>
<objectAnimator
android:duration="@android:integer/config_mediumAnimTime"
android:propertyName="rotationY"
android:valueTo="0"
android:valueType="floatType"
/>
</set>
</item>
</selector>
然后在Xml布局中指定控件的android:stateListAnimator="@drawable/anim_change"属性即可。
- animated-selector
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/state_on"
android:state_checked="true">
<bitmap android:src="@drawable/ic_done_anim_030"/>
</item>
<item android:id="@+id/state_off"
android:state_checked="false">
<bitmap android:src="@drawable/ic_plus_anim_000"/>
</item>
</animated-selector>
定义好两个不同状态下的item后,还要使用<transition>标签来给这两种状态间设置不同的过渡图片,完整的animated-selector代码如下:
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/state_on"
android:state_checked="true">
<bitmap android:src="@drawable/ic_done_anim_030"/>
</item>
<item android:id="@+id/state_off"
android:state_checked="false">
<bitmap android:src="@drawable/ic_plus_anim_000"/>
</item>
<transition
android:fromId="@id/state_on"
android:toId="@id/state_off">
<animation-list>
<item android:duration="16">
<bitmap android:src="@drawable/ic_plus_anim_000"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_plus_anim_001"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_plus_anim_002"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_plus_anim_003"/>
</item>
......
<item android:duration="16">
<bitmap android:src="@drawable/ic_plus_anim_030"/>
</item>
</animation-list>
</transition>
<transition
android:fromId="@id/state_off"
android:toId="@id/state_on">
<animation-list>
<item android:duration="16">
<bitmap android:src="@drawable/ic_done_anim_000"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_done_anim_001"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_done_anim_002"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_done_anim_003"/>
</item>
.....
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_done_anim_029"/>
</item>
<item android:duration="16">
<bitmap android:src="@drawable/ic_done_anim_030"/>
</item>
</animation-list>
</transition>
</animated-selector>
可以看到<transition>标签中通过fromId和toId两个属性指定了图片播放的时机。有了animated-selector之后,只需要将它设置到控件的drawable上即可,同时在代码中设置不同的点击状态。通常使用如下所示的系统属性来设置点击的状态:
private static final int[] STATE_CHECKED = new int[]{android.R.attr.state_checked};
private static final int[] STATE_UNCHECKED = new int[]{};
当点击时,通过setImageState方法来改变一个背景状态图:
if (mChecked) {
mChecked = false;
((FloatingActionButton) v).setImageState(STATE_UNCHECKED, true);
} else {
mChecked = true;
((FloatingActionButton)v).setImageState(STATE_CHECKED,true);
}
4.Toolbar
<?xml version="1.0" encoding="utf-8"?>
<resource>
<style name="AppTheme" parent="Theme.AppCompatLight.NoActionBar">
<!--toolbar颜色 -->
<item name="colorPrimary">#4876FF</item>
<!--状态栏颜色 -->
<item name="colorPrimaryDark">#3A5FCD</item>
<!--窗口的背景颜色 -->
<item name="android:windowBackground">@android:color/white</item>
<!--add Search View -->
<item name="searchViewStyle">@style/MySearchView</item>
</style>
<style name="MySearchView" parent="Widget.AppCompat.SearchView"/>
</resource>
Toolbar的常用方法:
Toolbar toolbar;
toolbar = (Toolbar) findViewById(R.id.toolbar);
<span style="white-space:pre"> </span>//设置图标
toolbar.setLogo(R.mipmap.ic_launcher);
//设置主标题
toolbar.setTitle("主标题");
//设置副标题
toolbar.setSubtitle("副标题");
//将Toolbar作为Actionbar使用
setSupportActionBar(toolbar);
5.Notification
- 基本的Notification
Notification.Builder builder = new Notification.Builder(context);
下面通过设置一个PendingIntent来指定Notification点击时执行的操作:
Intent Intent = new Intent(Intent.ACTION_VIEW,Uri.parse("http://www.baidu.com");
PendingIntent pendingIntent = PendingIntent.getActivity(this,requestCode,intent,flag);
PendingIntent通过静态的方法来创建出来,getActivity可以指定打开一个Activity,类似的还有PendingIntent.getService(),PendingIntent.getBroadcast等。然后可以通过builder来给Notification设置各种属性,builder支持链式编程,最后通过builder.build来创建Notification对象。
//设置内容标题
builder.setContentTitle("This is title")
//设置内容文本
.setContentText("This is content text")
//设置contentInfo
.setContentInfo("This is content info")
//设置子文本
.setSubText("This is sub text")
//点击时是否自动取消
.setAutoCancel(true)
//设置通知的类型,如message、alarm、call、email等
.setCategory(Notification.CATEGORY_MESSAGE)
//设置大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
//设置小图标
.setSmallIcon(R.mipmap.ic_launcher)
//设置点击时的pendingIntent
.setContentIntent(pendingIntent)
//是否设置为浮动通知(5.0的新特性,可以将通知悬浮显示在其他界面上,而不打断用户当前的操作)
.setFullScreenIntent(pendingIntent,false)
//设置默认的属性,可以配置铃声和震动等
.setDefaults(Notification.DEFAULT_ALL);
//调用build方法创建Notification。
Notification n = builder.build();
//获得NotificationManager对象
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//显示通知
nm.notify(0,n);
- 创建自定义视图的Notification
RemoteViews remoteView = new RemoteViews(getPackageName(),R.layout.notification_normal);
remoteView.setTextViewText(R.id.tv_title, "RemoteView title");
remoteView.setTextViewText(R.id.tv_sub_text, "Remote View content");
remoteView.setImageViewResource(R.id.iv_icon, R.mipmap.ic_launcher);
RemoteViews expandedView = new RemoteViews(getPackageName(), R.layout.notification_expanded);
expandedView.setTextViewText(R.id.tv_expanded, "This is expanded content");
上面代码中remoteView就是正常状态下显示的通知视图,expandedView则是展开后的视图。可以通过setTextViewText()、setImageViewResource()给特定的TextView、ImageView设置具体的文字和图片信息。由此也可以看出RemoteViews并不支持所有的View类型,只支持一些简单的view,具体大家可以去看View的源文件,有注解@RemoteView的就说明这个View支持使用在RemoteView上。从下面的图片可以看到ImageView支持RemoteView,而他的父类View则不支持。
RemoteViews创建好后就可以通过notification.contentView和notification.bigContentView两个字段来设置自定义的view了。
n.contentView = remoteView;
n.bigContentView = expandedView;
- 设置Notification的等级
builder.setVisibility(Notification.VISIBILITY_PRIVATE);