由于第十章是介绍的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类可以提取的色调种类有以下几个:
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());为状态栏设置颜色。
下面来看看具体的实现代码:
<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移除或插入的动画。
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类型:
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()来设置退出的动画。
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()就可以直接执行动画。
<!-- 波纹有边界--> 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); }下面是这个方法几个传入参数的含义:
Animator animator = ViewAnimationUtils.createCircularReveal(v, v.getWidth() / 2, v.getHeight() / 2, 0, v.getWidth()); animator.setDuration(2000); animator.start();3.View state changes Animation
<?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 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[]{};
if (mChecked) { mChecked = false; ((FloatingActionButton) v).setImageState(STATE_UNCHECKED, true); } else { mChecked = true; ((FloatingActionButton)v).setImageState(STATE_CHECKED,true); }
<?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) 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.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);
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则不支持。
n.contentView = remoteView; n.bigContentView = expandedView;
builder.setVisibility(Notification.VISIBILITY_PRIVATE);