《Android群英传》读书笔记(12)第十二章:Android 5.X新特性详解

来源:互联网 发布:cf领枪软件下载 编辑:程序博客网 时间:2024/05/13 03:30

由于第十章是介绍的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——柔和的亮色
使用Palette首先需要引入com.android.support:palette的依赖,最新的Palette类使用的是Builder模式来进行创建,书上所讲的generateAsync()等方法已经弃用了,不得不说Google的更新速度真是让人受不了。下面是Builder构造器的用法:
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());为状态栏设置颜色。

三、视图与阴影
Material Design中还为View加入了Z轴的概念,除了之前的X轴和Y轴,现在也可以改变View的垂直于手机屏幕的高度属性了,反应出来的效果就是View的阴影的大小以及多个View之间的重叠关系。
在Material Design中View的Z轴由两部分组成,elevation和translationZ。elevation是静态的成员,可以通过在xml布局中通过android:elevation="xx"来设置,translationZ可以在代码中使用来实现动画效果,他们两个纸盒就是View总的Z轴高度。即Z=elevation+translationZ。

四、Tinting和Clipping
1、Tint为着色,即在原来的bitmap上使用特定的颜色进行遮罩,可以在xml布局中通过backgroundTint或tint来进行配置;tint配合tintMode来使用,ttintMode有以下几种类型:
  • add
  • multiply
  • screen
  • src_in
  • src_atop
  • src_over
 可以试着在布局中用用看每个类型是什么效果。
2、Clipping可以让我们改变一个视图的外形。要使用Clipping,首先要使用ViewOutlineProvider来修改Outline,然后再通过view.setOutlineProvider(outline)将outline作用给view,达到裁剪的目的。

下面来看看具体的实现代码:

<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——改变目标图片的大小和缩放比例
下面来看看如何使用动画从MainActivity进入SecondActivity
如果使用进入或退出这两种动画,在MainActivity中只需要对startActivity()替换成下面这个就行:
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传入即可。
然后在SecondActivity中首先调用requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS);开启Activity的转场动画,或者在style中配置
<item name="android:windowContentTransitions">true</item>也可以,建议使用后面这种方法,因为在AppcompatActivity中调用requestWindowFeature()会报错,而且AndroidStudio创建的工程默认就是继承这个Activity的。设置好这一项后只需要在SecondActivity的setContentView()之前,注意是之前,调用下面几行代码就可以了:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            getWindow().setEnterTransition(new Fade());        }
当然也可以调用setExitTransition()来设置退出的动画。
对于共享元素动画是什么,可以参考书中引用的一张图:

共享元素即Activity1和Activity2都有的元素,也就是图中的Android机器人,使用共享元素的动画在从Activity1跳转到Activity2时共享元素会通过动画的方式直接显示在Activity2上,并不会消失再出现。
使用共享元素前需要在共享的view的xml布局中加入下面这个属性:android:transitionName="XXX",而且两个activity的布局中同样的元素的这个属性值要相同。然后在第一个Activity中startActivity时将共享元素的view对象和transitionName属性的字符串传进去就行了:
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动画效果
1.Ripple水波纹效果
Android5.0中大量使用了ripple水波纹的按钮点击效果,最简单的使用方法是在xml中进行以下设置:
<!-- 波纹有边界-->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属性使用即可。
2.CircularReveal效果
这个效果的具体表现为一个View以圆的形式展开、揭示出来。通过ViewAnimationUtils.createCircularReceal()方法就可以创建一个RevealAnimator动画。代码如下:
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
Android5.0中提供了视图状态改变来设置一个视图的状态切换动画。使用StateListAnimator来实现,通常会使用Selector来进行设置,以前使用Selector通常是通过修改背景来达到反馈的效果,现在在Android5.0中可以使用动画来作为视图改变的效果。
在XML中定义一个StateListAnimator并添加到Selector中去,如下面所示:
<?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"属性即可。
在代码中可以通过调用AnimationInflater.loadStateListAnimator()方法,并且通过View.setStateListAnimator()方法分配动画到视图上。
  • animated-selector
animated-selector同样是一个状态改变的动画效果Selector,它是通过类似帧动画来达到点击时平滑的切换效果。Material Design中很多的控件设计都是通过这种方式实现的,例如check_box的动画效果。
要实现这样的效果首先我们需要一组状态切换图:


然后在drawable文件夹中定义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
Toolbar与Actionbar最大的区别就是Toolbar更加自由、可控,要使用Toolbar必须先引入appcompat-v7支持,并设置主题为NoActionBar或者其子主题,在style.xml中可以这么配置:
<?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:
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
Notification可以通过RemoteViews来设置自定义的视图,同时在5.0中还增加了可以展开显示详细信息的Notification,同样是通过RemoteViews来实现。下面来具体看看怎么做。
首先通过RemoteViews来创建一个自定义的通知视图:
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的等级
Android5.0中将Notification分成了三个等级,以便对不同的通知信息进行过滤:
VISIBILITY_PRIVATE——表示只有当没有锁屏的时候会显示
VISIBILITY_PUBLIC——表示在任何情况下都会显示
VISIBILITY_SECRET——表示在pin、password等安全锁和没有锁屏的情况下才能够显示。
设置Notification的显示等级同样是通过builder来完成的:
        builder.setVisibility(Notification.VISIBILITY_PRIVATE);
0 0