动脑学院笔记


==========================Material Design=====================================

一、关于Material Design
从Android5.0开始引入的,是一种全新的设计语言(翻译为“原材料设计”),其实是谷歌提倡的一种设计风格、理念、原则。
拟物设计和扁平化设计一种结合体验。还吸取了最新一些科技理念。
层次感:View Z轴

1.对于美工:遵循MD的界面设计、图标合集。
2.对于产品经理:遵循MD界面设计、页面的跳转及动画效果、交互设计。
3.对于开发人员:参与原型设计、辅助美工原型设计的素材准备。
        开发实现MD的设计----界面、动画、转场动画等等。


二、MD的使用及开发
    谷歌开放以及收集了一些最新的开源的项目(很多是自己开发的),汇集到最新的support兼容支持包以及最新的5.X API里面。
    (preference:设置页面,可以通过配置文件达到界面设计的效果。)
    1)android-support-v4:最低兼容到Android 1.6系统,里面有类似ViewPager等控件。
    2)android-support-v7:appcompat、CardView、gridlayout、mediarouter、
    palette、preference、recyclerView(最低兼容到3.0)
    最低兼容到Android 2.1的系统,这个工程可以让开发人员统一开发标准,在任何的系统版本下保证兼容性。
    (比如:Theme,value,布局,新的控件,新的动画特效实现)
    所以现在ADT、AndrodStudio一般都会直接创建项目的时候就直接帮你新建或者引入了一个叫做appcompat的项目。
    (这里可能会碰到很多问题:1.自动导入的appcompat-v7项目自身就是报错的,什么原因?build的版本太低了,要么是SDK很新但是兼容包没有更新。
                    (还有的有其他的原因:1.没有将依赖的项目作为library,而且也没有将自己的项目加入该依赖项目。
                      2.multiple dex files。。。。appcompat/res/com.android.v7.R$anim 有文件冲突--一般是代表jar包冲突。
                      如何解决?删掉重复的jar )
                  2.appcompat-v7好不容易没报错,但是项目报错,一看控制台:报appcompat里面的某个res/values/theme/xxx属性不存在 等等问题。
                  什么原因?因为你引入的是很新的appcompat-v7项目,它要求必须很高的版本编译,然而Eclipse很蛋疼,在引入该项目的主项目编译的时候也必须要达到这个很高的版本---直接使用最高版本编译)


现在一般做开发都是最低兼容到4.0。
SDK升级:API升级、兼容包的升级、工具升级。
版本:1.compileSDK 编译版本;
    2.minSDK 兼容到最低版本是多少
    3.targetSDK;

SDK更新的历史上几个特别重要的版本:14(4.0)、19(4.4)、21(5.0)

关于Eclipse项目如何导入到AndroidStudio。百度。
1.直接导入没问题;
2.有问题,导出项目的时候应该选择gradle模式导出, 再导入到as。(这种情况下都有可能还是报错,可能是gradle版本太低了 需要升级!)
3.直接在as里面建个项目,然后把所有的资源和代码拷贝过去 就欧了!!



--------------------------1.MaterialDesign控制项目全局样式-------------------------------
为什么要用appcompat项目,因为里面是谷歌精心准备的---解决android碎片化开发蛋疼的问题,让我们app编译出来在
各种高低版本之间、不同的厂商生产的ROM之间显示出来的效果UI控件等有一较一致的体验。


1.引入appcompat-v7项目(包括了android-support-v7-appcompat.jar和资源文件)
2.写自己的全局样式:
    
   

   
   
    colorPrimary:主色,
    colorPrimaryDark:主色--深色,一般可以用于状态栏颜色、底部导航栏
    colorAccent:(代表各个控件的基调颜色--CheckBox、RadioButton、ProgressBar等等)
    "android:textColor":当前所有的文本颜色

----------------------------MaterialDesign兼容性控件的使用------------------------------------
尤其是在appcompat-V7里面有很多为兼容而生的控件
这样就可以做到高低版本和不同的ROM之间体验一致!还可以配合appcompat的主题使用达到体验一致性
1.android.support.v7.app.AlertDialog

2.进度条样式设置
 style="@style/Widget.AppCompat.ProgressBar.Horizontal"

3.SwipeRefreshLayout下拉刷新

4.PopupWindow
    ListPopupWindow
    PopupMenu

5.android.support.v7.widget.LinearLayoutCompat
给包裹在里面的所有子控件添加间隔线
                    android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:divider="@drawable/abc_list_divider_mtrl_alpha"
            app:showDividers="beginning|middle"
            android:orientation="vertical" >


-----------------------------V7 RecyclerView-----------------------------------
特点:
1.谷歌在高级版本提出一个新的替代ListView、GridView的控件。
2.高度解耦。
3.自带了性能优化。ViewHolder。
软件:低耦合高内聚。

RecyclerView没有条目点击事件,需要自己写。


作业:
1.封装一个简单的BaseRecyclerAdapter。
2.解决position可能错位的问题。
3.设置条目长按事件



作业:
1.看源码LinearLayoutCompat ,分析实现的原理:是如何做到给里面的所有的child之间添加间隔线的?
    
    View的绘制会经过三个方法:onMearsue(测量自身和里面的所有子控件),onLayout(摆放里面所有的子控件),onDraw(绘制)
    猜想:1)mearsuredWidth,mearsuredHeight会变大(加上间隔线);2)摆放子控件位置会有一定的体现(childView: left/top/right/bottom);
        3)onDraw绘制的时候也会有提现(childView: left/top/right/bottom);



2.解决本次课的瀑布流效果的bug
报错:NullPointException----LayoutParams为空

View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, null)
最终都会调用该方法:
inflate(resource, root, root != null);
inflate(resource, null, false);

经过修改:
MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, viewGroup));
还是报错:
java.lang.IllegalStateException:
The specified child already has a parent. You must call removeView() on the child's parent first.
默认还是调用的:
inflate(resource, root, root != null);
inflate(resource, root, true);
看源码就知道:多做了一个事情就是
 if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

由于RecyclerView/ListView会自动将child添加到它里面去

为何Fragment的onCreateView又可以呢?


最终结局办法:
首先,root不为null, boolean(attachToRoot)必须为false。
inflate(resource, root, false);

------------------------RecyclerView设置分割线---------------------------
1.RecyclerView没有默认的分割线,需要自己绘制。
RecyclerView.ItemDecoration
    1)线性的分割线
    2)网格的分割线

1).可以通过修改Theme.Appcompa主题样式里面的android:listSelector或者 android:listDivider属性
    达到改变间隔线的大小和颜色哦!(自己尝试下)
           
    2).写一个条目装饰类,继承
    class MyItemDecoration extends RecyclerView.ItemDecoration{
    }

绘制分发。
    绘制REcyclerView的时候会分发Canvas到ItemDecoration里面。

作业:
    1。现在DividerGridViewItemDecoration还有不完善的吗?
    效果上面
    2。看ItemDecoration的绘制分发的过程源码粗略分析


------------------------RecyclerView 如何添加头部和底部---------------------------
ListView.addHeadView();
ListView.addFooterView();

RecyclerView没有这样的方法,需要自己解决
所以我们通过看ListView的源码学习如何解决这个问题!!

ListView.addHeaderView(){
     if (mAdapter != null) {
            if (!(mAdapter instanceof HeaderViewListAdapter)) {
                mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
            }
};
ListView.setAdapter(){
    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
}

ListView.setAdapter(new SimpleAdapter(xxx))

使用了中间代理模式解决的。模仿!!

注意:刷新问题
                //注意:如果添加了头部的话,这样刷新是没有效果的
//                adapter.notifyDataSetChanged();
                //需要拿到真正的adapter(因为我们自定义REcyclerView里面是包装了一层新的adapter)
//                recyclerView.getAdapter().notifyDataSetChanged();
                //这里还要注意position的问题,因为header也是属于条目的一部分,所以position都要往下移1位才行
                recyclerView.getAdapter().notifyItemInserted(1);



作业:
    1.思考,如何实现更多条目的装饰效果,比如:联系人条目的左侧添加首字母装饰。

-------------------------------RecyclerView交互动画---------------------------------------------------------
ItemTouchHelper
作业:1.解决课上的bug:滑动删除的时候,然后再滑动页面,结果有的条目不出现了 有空白的地方。
    原因:ListView和RecyclerView都会有复用条目itemView。这样就会导致上面的问题。
    解决:很多。比如在clearView回调方法里面去恢复这些条目的状态
    @Override
    public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
        // 恢复
        viewHolder.itemView.setBackgroundColor(Color.WHITE);
        
//        viewHolder.itemView.setAlpha(1);//1~0
//        viewHolder.itemView.setScaleX(1);//1~0
//        viewHolder.itemView.setScaleY(1);//1~0
        super.clearView(recyclerView, viewHolder);
    }


2.类似QQ的条目侧滑一半的效果
提示:
    方法一:判断
    //判断是否超出或者达到width/2,就让其setTranslationX位一般的位置
        if(Math.abs(dX)<=viewHolder.itemView.getWidth()/2){
            viewHolder.itemView.setTranslationX(-0.5f*viewHolder.itemView.getWidth());
        }else{
            viewHolder.itemView.setTranslationX(dX);
        }
    方法二:ItemView就是一个ViewPager,上面的view可以朝反方向设置TranslationX


-------------------------------------MateriaDesign侧滑----------------------------------------------
以前是有民间的效果:SliddingMenu

侧滑两种效果:1.盖在整个页面上面;2.在Toolbar下面。
在MD提出来以后,谷歌就收录并改变了很多开源项目,放到API及support包里面。
1.DrawerLayout 抽屉容器
    来自support-v4包里面的。(android.support.v4.widget)
    相当于一个自定义容器 extends ViewGroup ,可以看出是一个有侧滑效果的帧布局
    两个部分:1)内容布局;2)侧滑出来的菜单布局


    

2.NavigationView
    是谷歌在侧滑的MaterialDesign的一种规范,所以提出了一个新的控件,用来规范侧滑的基本样式。
DrawerLayout+ NavigationView结合使用

注意:使用NavigationView,需要依赖项目:Design项目。
    同时还需要依赖recyclerView项目和CardView项目!!!!

作业:
1.实现侧滑效果,点击菜单实现fragment切换。
2.如何关联菜单按钮,实现打开和关闭侧滑菜单。

---------------------------------------SnackBar----------------------------------------------
Snackbar:的提出实际上是界于Toast和Dialog的中间产物。
Toast: 用户无法交互;
Dialog:用户可以交互,但是体验会打折扣,会阻断用户的连贯性操作;
Snackbar既可以做到轻量级的用户提醒效果,又可以有交互的功能(必须是一种非必须的操作)。

自定义吐司:

源码分析:
do{
    //不断地查找parent,实际上就是找到最外层的FrameLayout
    view.getParent();
}while()
.......
rootView.addView(view);//添加到最外层的布局容器里面

-------------------------------------TextInputLayout------------------------------------------
是强大的带提示的MD风格的Edittext
看源码:TextInputLayout extends android.widget.LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        app:hintAnimationEnabled="true"
        >
                    android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:hint="请输入用户名" />


app:hintAnimationEnabled="true" : 是否获得焦点的时候hint提示问题会动画地移动上去。
app:errorEnabled="true" 是都打开错误提示。
要使用错误提示还得自己定义规则。
 app:counterTextAppearance="" 可以自己修改计数的文字样式。
app:counterOverflowTextAppearance="" 超出字数范围后显示的警告的文字样式


源码分析

-------------------------------------MaterialDesign_Toolbar-------------------------------------
导航---顶部导航
以前:谷歌干脆规范了顶部导航---ActionBar(3.0API,也有兼容包)
后来:ActionBar开发起来很蛋疼(1.用来比较费劲;2.扩展性太差 太死板)
    大多数人都会使用一个民间的ActonBar,叫SherlockActionBar。
    谷歌就重新定义了一个Toolbar。现在又有个MaterialDesign的APPBar

作用:导航控件---显示标题、导航back、快捷操作、菜单等。而且Toolbar不一定要放在顶部,也可以放底部。

android.support.v7.widget.Toolbar

使用:
    1.引入support-v7包
    2.修改主题:   
    2)通过设置样式属性解决
        @color/system_bottom_nav_color
    3)通过代码设置
        //5.0+可以直接用API来修改状态栏的颜色。
        getWindow().setStatusBarColor(getResources().getColor(R.color.material_blue_grey_800));
        (注意:要在setContentView方法之前设置)
2. 4.4 API
(低于4.4API,不可以做到)
用到一些特殊手段!----4.4(KitKat)新出的API,可以设置状态栏为透明的。
    1.在属性样式里面解决(不推荐使用,因为兼容不好)
        true
    2.再代码里面解决
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        setContentView(R.layout.activity_main);
    出现副作用:
        APP的内容顶到最上面去了,即状态栏会遮挡一部分界面。很坑
    解决办法(有几种):
        1)给Toolbar设置android:fitsSystemWindows="true"
        该属性的作用:设置布局时,是否考虑当前系统窗口的布局,如果为true就会调整整个系统窗口
                布局(包括状态栏的view)以适应你的布局。

        但是:又出现了一个bug,当里面有ScrollView并且ScrollView里面有Edittext的时候,就会出现软键盘一弹起就会把toolbar拉下来,很难看
        这种办法有什么价值呢?如果里面没有ScrollView就可以用。------本人发现并没用这个bug
        
        2)推荐
            灵感:发现给布局最外层容器设置android:fitsSystemWindows="true" 可以达到状态栏透明,并且露出底色---android:windowBackground颜色。
                不会出现toolbar被状态栏遮挡的情况。
            巧妙地解决:步骤:
                1.在最外层容器设置android:fitsSystemWindows="true"
                  不要给Toolbar设置android:fitsSystemWindows="true"
                2.直接将最外层容器(也可以修改-android:windowBackground颜色)设置成状态栏想要的颜色
                3.下面剩下的布局再包裹一层正常的背景颜色。

        3)修改Toolbar的高度
            1.不要给Toolbar设置android:fitsSystemWindows="true"
            2.
            需要知道状态栏的高度是多少?去源码里面找找
               
                24dp
               
                48dp
                反射手机运行的类:android.R.dimen.status_bar_height.

            3.修改Toolbar的PaddingTop(因为纯粹增加toolbar的高度会遮挡toobar里面的一些内容)
                toolbar.setPadding(
                    toolbar.getPaddingLeft(),
                    toolbar.getPaddingTop()+getStatusBarHeight(this),
                    toolbar.getPaddingRight(),
                    toolbar.getPaddingBottom());

            4.或者可以设置toolbar的高度(不推荐)

第三方的沉浸式解决方案:SystemTint。


作业:尝试自己封装;(判断版本,baseActivity)
思考:如何让虚拟导航也做出沉浸式效果。
    提示:5.0+ 可以用API解决  @color/system_bottom_nav_color

--------------------------------如何让虚拟导航NavigationBar也做出沉浸式效果-----------------------------------

1. 5.x 底部虚拟导航沉浸效果
    1)属性解决
        navigationBarColor
    2)代码
        getWindow().setNavigationBarColor()
    

2. 4.4
用到一些特殊手段!----4.4(KitKat)新出的API,可以设置虚拟导航栏为透明的。
    步骤:
    1)在布局底部添加一个高度为0.1dp的view
    2)动态设置底部View的高度为虚拟导航栏的高度
    View nav = findViewById(R.id.nav);
    LayoutParams p = nav.getLayoutParams();
    p.height += getNavigationBarHeight(this);
    nav.setLayoutParams(p);

3.做兼容性判断
    1)SDK版本不一样
    两个区间:1. 大于5.0;2.=<4.4sdk<5.0

    2)有的没有虚拟导航栏
        判断是否有虚拟导航栏(源码里面有方法可以得到是否有虚拟导航,反射得到)

    3)有的有虚拟导航,但是还可以开关
        判断是否虚拟导航栏打开了

一步解决2)3)两个问题: NavigationBarHeight=整个屏幕的高度 - 内容部分view的高度 判断是否>0

作业:用另外一种方式做到低版本底部导航沉浸式的兼容。提示SnackBar。DecorView


-----------------------------------MaterialDesign_CardView----------------------------------------
兼容开发,引用v7目录下面的CardView项目
CardView: android-support-v7-cardView.jar
1.特性
    1) 边框圆角效果
        5.x 图片和布局都可以很好的呈现圆角效果,图片也变圆角了
        4.x 图不能变成圆角,如果要做成5.x一样的效果:通过加载图片的时候自己去处理成圆角
    2)阴影效果

    3)5.x上有Ripple水波纹效果(低版本需要自己做自定义的)
        android:foreground="?attr/selectableItemBackground"
        android:clickable="true"
    4)5.x实现按下的互动效果---下沉,松开弹起---Z轴位移效果 (低版本也需要自己自定义做)
        
    5)可以设置内容的内边距
     app:contentPadding="5dp"

同一套布局的兼容性开发:(5.x上面不需要设置app:contentPadding="5dp",而4.x上面不需要设置)
layout
layout-v21

细节:
    5.x上面,边距阴影比较小,需要手动添加边距16dp
    4.x上面,边距太大, 手动修改边距0dp(原因:兼容包里面设置阴影效果自动设置了margin来处理16dp)


    app:cardCornerRadius="10dp" 圆角(半径值越大圆角就越明显)
    app:cardElevation="10dp" 阴影效果 (值越大阴影效果越明显)
    >
    


-----------------------------------MaterialDesign_FloatingActionButton----------------------------------------

FloatingActionButton :悬浮动作按钮
特性:1.阴影效果--景深(反馈动作:按下去阴影加深elevation)
    2.水波纹效果

    //3.其他效果--自己做

兼容性注意:
    需要写两个layout/layout-v21
    layout-v21:添加layout_margin="16dp"
    layout: 添加layout_margin="0dp"

    app:backgroundTint="?attr/colorPrimary"背景着色
        app:elevation="10dp"阴影深度
        android:layout_margin="0dp"
        app:fabSize="mini"大小:normal,mini


作业:分析源码CardView和FloatingActionButton


-------------------------------CoordinatorLayout----------------------------------------

1.实现滑动RecyclerView,Fab显示和隐藏。

思路:1.监听RecyclerView的滑动
    2.fab执行显示和隐藏的动画

方法二:Behavior
RecyclerView+FloatingActionButton+CoordinatorLayout

CoordinatorLayout: 继承自ViewGroup。
    通过协调并调度里面的子控件或者布局来实现触摸(一般是指滑动)产生一些相关的动画效果。
    可以通过设置view的Behavior来实现触摸的动画调度。





作业:平行空间的欢迎页面效果实现。(提示:viewpager)

思路:
    1.布局:RelativeLayout+ViewPager
    2.动画:背景的两个动画,
        1)一进来就执行一个平移动画;---属性动画 view.setTranslationX();
        2)平移完后,执行翻转动画;
        3)背景颜色渐变(绿色-->蓝色)

    监听viewpager的滑动。--->滑动的百分比。

    3.如何做到手机壳里面的view滑动的时候不会出现在手机壳外面。
    办法很多:1)自定义Drawable(滑动的时候不断地将两张图片剪切并且拼接成一个Drawable。)
          2)HorizontalScrollView + 两张拼在一起的图片

    HorizontalScrollView 和手机壳的宽高一致。如何做到?相对于屏幕的宽高决定自己的宽高。
    用百分比布局。官方谷歌的百分比布局:只能相对于屏幕的宽度和高度
    layout_width=50%w
    layout_height=50%h
    这样会造成图片变形。所以只能自定义百分比布局(可以在谷歌的版本上直接修改少量代码。)
    layout_width=50%w
    layout_height=70%w

-------------------------------CoordinatorLayout----------------------------------------
1.CoordinatorLayout
    监听滑动控件的滑动通过Behavior反馈到其他子控件并执行一些动画。
    注意:滑动控件指的是:RecyclerView/NestedScrollView/ViewPager,意味着ListView、ScrollView不行。
谷歌有个bug在最新版的Design包里面解决了:snackbar弹出的时候会遮挡住fab.
解决:用CoordinatorLayout将其包裹在里面就可以解决了。
源码:

2.AppBarLayout
AppBarLayout extends android.widget.LinearLayout
    app:layout_scrollFlags="scroll"
    flag包括:
        scroll: 里面所有的子控件想要当滑出屏幕的时候view都必须设置这个flag,
            没有设置flag的view将被固定在屏幕顶部。
        enterAlways:('quick return' pattern)
        enterAlwaysCollapsed:当你的视图设置了minHeight属性的时候,那么视图只能以最小高度进入,
                    只有当滚动视图到达顶部时才扩大到完整高度。
        exitUntilCollapsed:滚动退出屏幕,最后折叠在顶端。
        snap:

    1)NestedScrollView
    android.support.v4.widget.NestedScrollView; 在v4包里面,是ScrollView的升级版
    2)ViewPager+TabLayout+Fragment + AppBarLayout
    


3.CollapsingToolbarLayout
    可以实现Toolbar折叠效果.

    注意:1.AppBarLayout设置固定高度,并且要实现折叠效果必须比toolbar的高度要高。
         2.CollapsingToolbarLayout最好设置成match_parent

    app:layout_collapseMode="parallax"
        parallax:视差模式,在折叠的时候会有折叠视差效果。
            一般搭配layout_collapseParallaxMultiplier=“0.5”视差的明显程度
             be between 0.0 and 1.0.
        none:没有任何效果,往上滑动的时候toolbar会首先被固定并推出去。
        pin:固定模式,在折叠的时候最后固定在顶端。


              android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|enterAlways"
            app1:collapsedTitleTextAppearance=""
            app1:expandedTitleMargin="5dp"
        app1:contentScrim="@color/colorPrimary_pink"//内容部分的沉浸式效果:toolbar和imageview有一个渐变过渡的效果
            app1:statusBarScrim="@color/colorPrimary_pink"//和状态栏的沉浸式效果:指定颜色。
            app1:title="动脑学院"
            >

4.Behavior(CoordinatorLayout.Behavior/FloatingActionButton.Behavior)

-------------------------------md 动画----------------------------------------
1.Touch Feedback(触摸反馈)
    例子:水波纹效果
    水波纹效果是5.0+自带的。
    
            android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:text="@string/hello_world" />
            android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:background="?attr/selectableItemBackground"
        android:text="@string/hello_world" />
            android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:background="?attr/selectableItemBackgroundBorderless"//没有边界,圆形
        android:text="@string/hello_world" />

    可以修改背景颜色和水波纹的颜色:
     @color/colorPrimary_pink//高亮颜色
        @color/material_blue_grey_800//默认的颜色

    最好使用AppcompatActivity
    注意:如果是Button设置背景,原来的背景会被替换了。其他的控件可以设置背景。

2.Reveal Effect(揭露效果)
    例子:Activity的揭露出现的效果。
    ViewAnimationUtil工具类

    //圆形水波纹揭露效果
    ViewAnimationUtils.createCircularReveal(
            view, //作用在哪个View上面
            centerX, centerY, //扩散的中心点
            startRadius, //开始扩散初始半径
            endRadius)//扩散结束半径

3.Activity transition(Activity转场动画效果)
 概念:两个Activity进行跳转的时候,转场动画。

    ActivityOptions类。只支持API21以上的版本。
    版本判断会比较麻烦,谷歌很贴心 设计了一个兼容类:ActivityOptionsCompat(v4包中)
    但是此类在低版本上面并没有转场动画效果,只是解决了我们手动去判断版本的问题而已。
    
    转场动画可以分为两大类:共享元素转换和普通的转换
    
    使用转换动画前提:需要给两个Activity都设置如下,让其允许使用转场动画。
    //方法一:
    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
    //方法二:
    修改主题:true
    ----本人貌似不设置也行

    1)共享元素转换
        概念:可以把两个Activity当中的相同的元素关联起来做连贯的变换动画。
        前提:(1)给两个Activity当中的共享元素view都设置同一个名字---android:transitionName
                                 android:id="@+id/iv1"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:transitionName="iv_meinv3"
                android:src="@drawable/meinv3" />

            
        
        按返回键的时候自动实现了返回的共享元素转场动画,原因看源码:
        public void onBackPressed() {
            finishAfterTransition();
        }
        public void finishAfterTransition() {
            if (!mActivityTransitionState.startExitBackTransition(this)) {
                finish();
            }
        }

        //单个元素共享
        ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this, iv1, "iv_meinv3");
        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent, optionsCompat.toBundle());
        //多个共享元素
        ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                .makeSceneTransitionAnimation(this, Pair.create((View)iv1, "iv1"),Pair.create((View)bt, "bt"));
        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent, optionsCompat.toBundle());


    2)普通的转换动画
        (只有API 21才有下面自带效果)
        三种系统带的:滑动效果(Slide)、展开效果Explode、渐变显示隐藏效果Fade
            



4.Curved motion(曲线运动)
    设计:view的平移旋转等效果结合Path、Interpolator插值器。

5.View State change (视图的状态改变)
    例子:按下一个控件会有Z轴的阴影加深效果。



作业:
    1.实现揭露效果的低版本兼容!

作业:
    1.使用RecyclerView实现共享元素转场动画。
    2.如何实现普通的转换动画滑动效果(Slide),兼容低版本实现。
        手动撸。提示:各种属性动画组合实现。

    view.setTranslationX(xxx)
    objectClass.setCurrentRadius(xxx)

-------------------------------事件传递---------------------------------------

在很多的滑动控件嵌套的情况下经常会出现滑动事件冲突等等。
在自定义控件的时候,需要处理触摸、点击、滑动等事件,需要考虑父容器的这些事件的冲突问题。

事件传递:
    1.事件传递机制---源码。
    
    2.处理事件的冲突。

事件:Activity里面、View里面、ViewGroup里面、Key按键。

一、View的事件分发。
结论:
    1.控件的Listener事件触发的顺序是先onTouch,再onClick。】
    2.控件的onTouch返回true,将会onClick事件没有了---阻止了事件的传递。
      返回false,才会传递onClick事件(才会传递up事件)


源码依据:
    View的事件分发
    1.dispatchTouchEvent();分发
    2.onTouchListener-->onTouch方法
    3.onTouchEvent()
    4.onClickListener-->onClick方法


结论:
    1.如果onTouchListener的onTouch方法返回了true,那么view里面的onTouchEvent就不会被调用了。

顺序dispatchTouchEvent-->onTouchListener---return false-->onTouchEvent
    2.如果view为disenable,则:onTouchListener里面不会执行,但是会执行onTouchEvent(event)方法
      
    3.onTouchEvent方法中的ACTION_UP分支中触发onclick事件监听
        onTouchListener-->onTouch方法返回true,消耗次事件。down,但是up事件是无法到达onClickListener.
        onTouchListener-->onTouch方法返回false,不会消耗此事件

二、ViewGroup+View的事件分发
ViewGroup-->View
    1.dispatchTouchEvent()
    2.onTouchEvent();
    3.onInterceptTouchEvent(); 拦截触摸事件。

1.先接触到事件的是父容器。
2.顺序:dispatchTouchEvent-->onInterceptTouchEvent----->onTouchListener---return false-->onTouchEvent

dispatchTouchEvent:action--0---view:MyRelativeLayout
onInterceptTouchEvent:action--0---view:MyRelativeLayout
    super.dispatchTouchEvent()
    child.dispatchTouchEvent()
dispatchTouchEvent:action--0---view:MyButton
OnTouchListener:acton--0----view:com.ricky.event.MyButton{528154dc VFED..C. ........ 0,37-205,163 #7f080001 app:id/button1}
onTouchEvent:action--0---view:MyButton

源码:2108行
     final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);//调用拦截方法判断
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
源码:2197行
     if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

源码:2520行
    dispatchTransformedTouchEvent(){
         if (child == null) {//如果ViewGroup里面没有子控件就交给自己处理(就是一个纯粹的View)
            handled = super.dispatchTouchEvent(event);
            } else {
            handled = child.dispatchTouchEvent(event);
            }
    }

作业:
    1.在ScrollView里面嵌套一个ListView,滑动事件冲突,解决。
    2.ListView全部展开。

看源码有什么技巧:
    1.带着疑问或者你推测的结论去看。
    2.画图、记录笔记
    3.遍看源码的时候不但地去回溯代码,一定需要反复看。
    4.要能及时刹住车。





一、ViewGroup的dispatchTouchEvent()方法
    1.mFirstTouchTarget = null;

    if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;//target置为空了。
        }
    }
    
    2.所有的ViewGroup都是默认不拦截的。
    (注意:所谓的拦截,是指按下去自身以及以后的后续事件move,up。拦截下来给自己onTouch使用。)
        public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
        }

        // Check for interception.
            final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }






    viewGroup.setDisallowIntercept(true/false)



---------------Paint---------------
1)Paint的基本实用方法和技巧
    (1)基本的使用
    1.1 负责图形绘制相关
    //重置
    mPaint.reset();
    mPaint.setColor(Color.RED);
    mPaint.setAlpha(255);
    //设置画笔的样式
//        mPaint.setStyle(Paint.Style.FILL);//填充内容
//        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStyle(Paint.Style.STROKE);//描边
    //画笔的宽度
        mPaint.setStrokeWidth(50);
    //线帽
//        mPaint.setStrokeCap(Paint.Cap.BUTT);//没有
        mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形
//        mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形
        
//        mPaint.setStrokeJoin(Paint.Join.MITER);//锐角
//        mPaint.setStrokeJoin(Paint.Join.ROUND);//圆弧
//        mPaint.setStrokeJoin(Paint.Join.BEVEL);//直线
    //线段的连接处的样式
//        mPaint.setStrokeJoin(Paint.Join.MITER);//锐角
//        mPaint.setStrokeJoin(Paint.Join.ROUND);//圆弧
//        mPaint.setStrokeJoin(Paint.Join.BEVEL);//直线

    //防锯齿,会损失一定的性能
        mPaint.setAntiAlias(true);


    1.2 负责文字绘制相关
            //-----------------2.文字绘制--------------------
        //获得字符行间距
//        mPaint.getFontSpacing();
        //获得字符之间的间距
//        mPaint.getLetterSpacing();
//        mPaint.setLetterSpacing(letterSpacing)//设置
        //设置文本删除线
//        mPaint.setStrikeThruText(true);
        //是否设置下划线
//        mPaint.setUnderlineText(true);
        //设置文本大小
//        mPaint.setTextSize(textSize);
//        mPaint.getTextSize();
//        mPaint.setTypeface(Typeface.BOLD);//设置字体类型
//        Typeface.ITALIC
//        Typeface.create(familyName, style)//加载自定义字体
        //文字倾斜 默认0,官方推荐的-0.25f是斜体
//        mPaint.setTextSkewX(-0.25f);
        //文本对齐方式
//        mPaint.setTextAlign(Align.LEFT);
//        mPaint.setTextAlign(Align.CENTER);
//        mPaint.setTextAlign(Align.RIGHT);
        //计算制定长度的字符串(字符长度、字符个数、显示的时候真实的长度)
//        int breadText = mPaint.breakText(text, measureForwards, maxWidth, measuredWidth)
        
        mPaint.setTextSize(50);
//        float[] measuredWidth = new float[1];
//        int breakText = mPaint.breakText(str, true, 200, measuredWidth);
//        Log.i("RICKY", "breakText="+breakText+", str.length()="+str.length()+", measredWidth:"+measuredWidth[0]);
        
        // Rect bounds获取文本的矩形区域(宽高)
//        mPaint.getTextBounds(text, index, count, bounds)
//        mPaint.getTextBounds(text, start, end, bounds)
        
        //获取文本的宽度,和上面类似,但是是一个比较粗略的结果
        float measureText = mPaint.measureText(str);
        //获取文本的宽度,和上面类似,但是是比较精准的。
        float[] measuredWidth = new float[10];
        
        //measuredWidth得到每一个字符的宽度;textWidths字符数
        int textWidths = mPaint.getTextWidths(str, measuredWidth);
//        mPaint.getTextWidths(text, start, end, widths)
        Log.i("RICKY", "measureText:"+measureText+", textWidths:"+textWidths);
        
        =====================基线的问题=====================

        FontMetrics fontMetrics = mPaint.getFontMetrics();
        fontMetrics.top;
        fontMetrics.ascent;
        fontMetrics.descent;
        fontMetrics.bottom;
        所有的四个值都是以基线baseLine为基准来计算的。baseline以上的就是负的;以下的是正的。

        在做自定义控件的时候canvas.drawText(x,y) 这个y并不是text的左上角,而是以baseline为基准的。

        1)实例:指定左上角的顶点坐标 绘制文本
        公式: float baselineY = Y - fontMetrics.top;

        
        2)实例:指定中间位置,绘制文本
        公式: float baselineY = centerY + (fontMetrics.bottom-fontMetrics.top)/2 - fontMetrics.bottom
        

--------------UI绘制流程-----------------
一、从setContentView(R.layout.activity_main);入手了解UI的绘制起始过程
1.Activity.java
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);//①
        initWindowDecorActionBar();
    }

2.getWindow()拿到的是Window的实现类PhoneWindow

PhoneWindow源码:
 com.android.internal.policy

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();//②
        }
    ……
    mLayoutInflater.inflate(layoutResID, mContentParent);//⑥
    }

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();//③生成一个DecorView(继承的FrameLayout)
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//④
    }
    
    protected ViewGroup generateLayout(DecorView decor) {//⑤
    View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
    }


三、measure、layout、draw的三个执行流程
View.java类
    measure:测量,测量自己有多大,如果是ViewGroup的话会同时测量里面的子控件的大小
    layout:摆放里面的子控件bounds(left,top,right,bottom)
    draw:绘制 (直接继承了view一般都会重写onDraw)

ViewGroup.java




看View.java类的源码:
1.view的requestLayout()方法开始,递归地不断往上找父容器,最终找到DecorView
2.执行了DecorView的ViewRootImp类的performTranversal()方法 (ViewRootImp类:是PhoneWindow和DecorView的桥梁)
3.performTranversal(){

     // Ask host how big it wants to be
          performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

      performLayout(lp, desiredWindowWidth, desiredWindowHeight);

      performDraw();
}

4.
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }





------------------------ViewGroup.java总结:-----------------------
一、measure的过程
如何去合理的测量一颗View树?
如果ViewGroup和View都是直接指定的宽高,我还要测量吗?
正是因为谷歌设计的自适应尺寸机制(比如Match_parent,wrap_content),造成了宽高不确定,所以就需要进程测量measure过程。
measure过程会遍历整颗View树,然后依次测量每一个View的真实的尺寸。(树的遍历--先序遍历)

MeasureSpec:测量规格
int 32位:010111100011100
拿前面两位当做mode,后面30位当做值。
    1.mode:
        1) EXACTLY: 精确的。比如给了一个确定的值 100dp
        2)  AT_MOST: 根据父容器当前的大小,结合你指定的尺寸参考值来考虑你应该是多大尺寸,需要计算(Match_parent,wrap_content就是属于这种)
        3)  UNSPECIFIED: 最多的意思。根据当前的情况,结合你制定的尺寸参考值来考虑,在不超过父容器给你限定的只存的前提下,来测量你的一个恰好的内容尺寸。
            用的比较少,一般见于ScrollView,ListView(大小不确定,同时大小还是变的。会通过多次测量才能真正决定好宽高。)
    2.value:宽高的值。

经过大量测量以后,最终确定了自己的宽高,需要调用:setMeasuredDimension(w,h)

写自定义控件的时候,我们要去获得自己的宽高来进行一些计算,必须先经过measure,才能获得到宽高---不是getWidth(),而是getMeasuredWidth()
也就是当我们重写onMeasure的时候,我们需要在里面调用child.measure()才能获取child的宽高。

从规格当中获取mode和value:
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
反过来将mode和value合成一个规格呢:
    MeasureSpec.makeMeasureSpec(resultSize, resultMode);

ViewGroup:
    设计它的目的是什么?
    1)作为容器处理焦点问题。
    2)作为容器处理事件分发问题;
    3)控制容器添加View的流程:addView(),removeView()
    4)抽出了一些容器的公共的工具方法:measureChildren,measureChild,measureChildWidthMargins方法。


-------------------重点:-----------------------
玩自定义控件的时候,需要进行测量measure,如何做好这件事?
两种情况:
    1.继承自View的子类
        只需要重写onMeasure测量好自己的宽高就可以了。
        最终调用setMeasuredDimension()保存好自己的测量宽高。
        套路:
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int Size = MeasureSpec.getSize(widthMeasureSpec);
        int viewSize = 0;
        switch(mode){
            case MeasureSpec.EXACTLY:
                viewSize = size;//当前view的尺寸就为父容器的尺寸
                break;
            case MeasureSpec.AT_MOST:
                viewSize = Math.min(size, getContentSize());//当前view的尺寸就为内容尺寸和费容器尺寸当中的最小值。
                break;
            case MeasureSpec.UNSPECIFIED:
                viewSize = getContentSize();//内容有多大,久设置多大尺寸。
                break;
            default:
                break;
        }
        //setMeasuredDimension(width, height);
        setMeasuredDimension(size);
    
    2.继承自ViewGroup的子类:
        不但需要重写onMeasure测量自己,还要测量子控件的规格大小。
    
        可以直接使用ViewGroup的工具方法来测量里面的子控件,也可以自己来实现这一套子控件的测量(比如:RelativeLayout)
    套路:
        //1.测量自己的尺寸
        ViewGroup.onMeasure();
            //1.1 为每一个child计算测量规格信息(MeasureSpec)
            ViewGroup.getChildMeasureSpec();
            //1.2 将上面测量后的结果,传给每一个子View,子view测量自己的尺寸
            child.measure();

            //1.3 子View测量完,ViewGroup就可以拿到这个子View的测量后的尺寸了
            child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()
            //1.4ViewGroup自己就可以根据自身的情况(Padding等等),来计算自己的尺寸
            ViewGroup.calculateSelfSize();
        //2.保存自己的尺寸
        ViewGroup.setMeasuredDimension(size);

   
----------------------------------------------

二、layout的过程


三、draw的过程


作业:如何让一个ScrollView里面的ListView全部展开?
有一种解决办法就是继承ListView,重写onMeasure方法:
public void onMeasure(){
  int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
  super.onMeasure(widthMeasureSpec, expandSpec);    
}
为什么要这么做?1.设置mode为 MeasureSpec.AT_MOST?2.value为Integer.MAX_VALUE >> 2?


作业:
1.热门标签自定义控件。

ps:视频 下载:https://pan.baidu.com/s/1nvE8lS1


你可能感兴趣的:(笔记)