Android6.0源码分析之View(一)

目前对于view还处于学习阶段,本来打算学习结束之后再写一篇进行总结,但是发现自己自制力太差,学习效率太低,所以在此,边学边写博客,不仅督促自己完成对view的学习,而且还可以看看大家对于view有什么想知道的,顺便来看看自己需要研究些什么。


请尊重技术原创,转载请注明出处 ,本文出自

fang_fang_story

接下来就是对view的学习。对于view我的学习思路是先要对view相关的知识进行一个整体的系统的了解,然后在对view中各个知识模块进行详细的研究,如果有正在研究view并且遇到问题的可以留言,大家一起讨论讨论。想要对view先有个整体的了解,我建议大家参考view.java中的类的注释,注释中交代了view所涉及到的所有知识。


Chapter One ,View概念介绍

View是基本的用户界面组件,一个view拥有屏幕上的一块儿矩形区域,可以进行绘制以及事件events的处理。View类是各种控件widget类的基类,控件widget是那些用户交互UI组件,比如,button,textview,imageview等等。View的子类ViewGroup是界面布局layout的基类,layout是指linearlayout,relativelayout等等,这些layout是一些不可见但占有space的invisible的布局容器,可以包括layout或者widget,并且可以声明他们自己的layout属性。(笔者注:invisible是说控件存在屏幕上已经布局了该view但是不可见此时控件可以有事件处理逻辑,gone是指控件不存在屏幕上现在不存在该view);


Chapter Two,Using View(view如何使用)


一个Window窗口中所有的view都在同一个tree即树形结构中。往一个树形结构中添加view有两种方法

1,可以通过代码添加(笔者注:调用viewgroup的addview方法 ,如下代码),

RelativeLayout mRl = (RelativeLayout) findViewById(R.id.rl);
TextView tv = new TextView(this);
tv.setText("这是在代码中加载的view");
tv.setTextColor(Color.RED);
mRl.addView(tv);

2,也可以在view树的layout文件中添加。

一旦你创建了view树,你可能想做以下一些操作。
1>,设置属性,properties,不同的view子类会有一些不同的属性,在编译期就确定的属性可以在xml文件中规定,如果在运行时期才能确定的属性则在代码中规定
2>,设置焦点,focus,framework层会处理焦点的移动来响应用户的操作,如果想要强制view获取焦点可以调用requestfocus方法
3>,监听事件,listener,view允许客户端设置监听器,当所监听的事件发生时会进行notify。
example setOnFocusChangeListener 监听view的焦点的获取和丢失。其他view的子类也会提供一些特殊的监听器,比如button有点击监听

4>,设置可见性,visibility,可以设置view的隐藏和显示(笔者注:对于可见性的参数有三个选择,visible,gone,invisible。对于visible,是view对用户可见,占据一定的space。对于gone,是对用户不可见,且不占据space。但对于invisible,虽然对于用户不可见,但仍旧占有space。)


framework负责measuring laying out and drawing views,(测量,布局,以及绘制)(笔者注:也就是说,对于加载view,先是测量一下view的大小,然后布局一下view的xy坐标,即在画布的位置,然后就在某个位置绘制已经能measure的view。很符合逻辑,就像比如想要画一个圆,先要确定圆心位置和圆半径,然后才能画圆)。除非你是自定义的viewgroup,否则你自己不能调用这些方法进行相关的操作

Chapter Three,View的生命周期--LifeCycle

要想自定义view,你可以覆写一些framework层使用view时调用的一些标准方法,不需要覆写所有的,事实上,可以覆写onDraw,如下表所示

CategoryMethodsDescription
CreationConstructors(构造器方法)当view在代码中被创建时会调用该方法,填充xml文件时也会调用该方法
 onFinishInflate()当view以及他所有的子view都被填充时会调用该方法
LayoutonMeasure(int ,int )决定view与子view所要求的大小时调用
 onLayout(boolean,int,int,int,int)当view给子view分配size和position,大小和位置的时候会调用
 onSizeChanged(int,int,int,int)当view的size发生改变时会调用
DrawingonDraw当view需要提供content内容时调用
Event Processing(事件处理)onKeyDown(int,KeyEvent)当有物理按键被按下时会调用
 onKeyUp(int,KeyEvent)当物理按键抬起时会调用
 onTrackBallEvent(MotionEvent)???Called when a trackball motion event occurs(笔者注:何时触发,view轨迹移动)
 onTouchEvent(MotionEvent)当有触摸事件发生时会触发
FocusonFocusChanged(boolean,int,android.graphics.Rect)当view的焦点发生改变时(失去或者获取焦点)会调用该方法
 onWindowFocusChanged(boolean)当包含这个view的window获取或者失去焦点时(activity的生命周期有关)
AttachingonAttachedToWindow()当与window绑定时会调用该方法
 onDetachedFromWindow()当与window解除绑定时会调用该方法
 onWindowVisibilityChanged(int)当所绑定的window的可见性发生改变时会调用






















各方法详细阐述:
1>,构造方法,view的构造方法有四个
第一,最简单的(其实还有构造器,但是不是public的,只是测试),只有一个context参数,View(Context),这个构造方法适合在代码中创建view中使用(笔者注:如果在自定义view时只覆写了该方法,在xml文件中添加view就会报inflateException.)
这里的context上下文参数使用的是正在运行的,用来获取theme,resource等等
第二,View(Context,AttributSet),当在xml使用view时会调用该方法,所以如果你想要在xml中使用自定义的view,必须覆写该构造方法。当所有子类被添加成功后会调用onFinishInflate 方法;AttributSet参数是view在xml文件中添加的属性标签(笔者注:在xml文件中使用veiw时所添加的各种标签属性都是通过传入的attrs来被解析的,如果在自定义view时构造方法中传入的attrs为null,则在xml文件中规定的各种属性均不会被解析。example,如下图,在xml中添加了我自定义的button,但是因为在自定义button时我的构造方法中传入的为null,则导致button的xml中定义的属性没有被解析)


第三,View(Context,AttributSet,int),当在xml中使用view时会调用该方法,第三个参数是defStyleAttr,这个参数的值与当前activity的theme主题有关,是引用的当前activity的一个主题属性item,默认的为0
第四,View(Context,AttributSet,int,int),xml使用时调用,又多了第四个参数:defStyleRes,当且仅当第三个参数为0或者第三个参数没有在主题属性中定义时该参数才会起作用。是一个style的id。
笔者总结:通过这么分析,总结一下view的构造方法以及构造参数。
view的public构造方法共4个,涉及到的构造参数共四个
  • Context context:上下文对象,用于获取theme,resource资源等等。(获取当前activity的theme用this,获取整个应用可以用getApplicationContext)
  • AttributSet attrs:属性集合,有了这个参数,才可以解析在xml中添加的view的各种属性。(width,height...etc)
  • int defStyleAttr:主题属性(item)比如buttonstyle等等,R.attrs....,是在context所获取的theme中寻找view所对应的一个item属性,用来规定view的样式资源style
  • int defStyleRes:样式资源style的id,可以使用自己自定义的style来给view,自定义的style可以通过context获取到resource资源获取,但是这个只有在主题中没有定义样式资源这个属性或者样式资源这个属性为0时才会起作用
添加view的方法
  • 可以在代码中动态添加方法(在运行时期添加),借助viewgroup特有的addView(View)方法添加view,添加view可借助只含有Context构造参数的构造方法
  • 可以在xml中静态添加(在编译期添加),在xml中添加view要注意两个问题,一个是一定要覆写带有AttributSet参数的构造方法,二是这个参数一定不能为null,因为这个参数会传给framework层让其去解析xml文件中对应的属性

在笔者刚开始自定义view时问题很多,比如在xml文件中使用时会报出填充异常等等,很多都是因为对view的构造方法不熟悉导致的。只有了解了基础,才能进行举一反三,毕竟关于view的这些都是依靠这些基础而来的,不论再多变,不变的是基础!!!

2>,onFinishInflate()
该方法是针对用layout文件填充view来说的,当view以及它的子view全都被添加之后会调用该方法,在填充布局的最后一个阶段别调用。即使自定义view覆写了该方法,一定要记得调用父类的方法,以保证framework层可以调用到。(笔者注:但是对于这个方法view是究竟怎么被调用的,没有找到Java中的,也许涉及到view的加载过程)。在该方法中可以对所填充的layout文件中的控件进行初始化。

对于onMeasure,onLayout,onDraw以及事件的处理,需要仔细分析。对于这些方法的分析先留着以后进行,接下来继续对view的介绍


Chapter Four ,IDS

对于view的id应该没什么好说的了,经常使用。view需要一个整形的id与其对应,在你所查找的view树范围(一个window窗口)内,ID具有唯一性。所以在view树中可以通过ID来寻找view


Chapter Five, Position

view的几何形状是个矩形(笔者注:自定义selector改变形状除外),left和top的值决定了view的location(位置),宽和高的大小决定了view的size(尺寸大小),不论是尺寸还是位置单位都是像素。笔者注:所以可以知道通过getLeft(view的X坐标)和getTop(view的Y坐标)就可以获取到view的位置(但是所获取到的getLeft和getTop都是相对于它的父view来说的)


还提供了一些类似的方法,比如,getRight/getBottom,来避免不必要的计算。
这里的getLeft说view的左边界距离他直接父类的左边界的距离,getRight说的是view的右边界距离他直接父类的左边界的距离。关系如下图所示


Chapter Six,SizePaddingMargins

Size:View的大小通过宽和高两个值来体现,事实上一个view拥有两对宽和高的值

  • the first第一对,measured width/height,测量宽高(getMeasuredHeight/width):这组宽高定义了view在它的父view中所想要的宽高
  • the Sec第二对,width/height有时也叫drawingwidth/drawingHeight,绘制的宽高(getWidth/height):定义了在layout结束后在draw绘制时view在屏幕上所显示的真是的宽高,这组值有可能与第一组值不同。

Padding : 为了测量view的尺寸,需要将Padding考虑在内,View的padding包括left、top、right、bottom。padding用来表示view的内容的偏移量,(这个不再多说,目前还没看到需要有什么注意的地方,如果以后有遇到再说)

Margins:margin说的是view与view之间的距离,所以对于单个view来说没有margin的概念,margin是针对viewgroup来说的。


Chapter Seven,EventHandlingThreading(事件处理线程)

view的事件处理周期如下:
事件触发(event trigger)---->分发给合适的view(dispatch event )--->view处理event并且通知listener(handler&notify)
如果在事件处理的过程中view的边界需要修改,view会调用invalidate方法,requestLayoiut或者invalidate方法会导致framework层会以合适的方式进行measure测量,laying out布局,drawing绘制view树

注,整个view树是一个单独的线程,你只能在UI线程中调用view的方法,如果你想在别的线程中更新UI,可以借助handler进行线程间通信

Chapter Eight,FocusHandling(焦点处理)


framework层会处理普通的焦点移动事件来响应用户的输入,包括view焦点的移动和隐藏,或者是另一个新的veiw获取到焦点。

  • isFocusable()/setFocusable(boolean) :View是否想要获取焦点
  • isFocusableInTouchMode/setFocusableInTouchMode:在触摸模式下,view是否想要获取焦点
  • requestFocus():让view获取焦点

View焦点的移动基于一定的运算规则----总是在给定方向上去寻找离他最近的下一个view。在极少数情况下,默认的焦点移动的顺序可能无法匹配开发者的预期的行为,可以在xml文件中显式的去指定下一个要获取焦点的id(nextFocusDown/left/right/up)

Chapter Nine Touch Mode (触摸模式)


当用户通过键盘上的方向键来导航用户界面时,给那些可操作的item(比如button)焦点是很有必要的以便于用户可以看到谁会处理用户输入。
如果设备有触摸的功能,但是用户通过触摸界面来进行交互的话没必要让view高亮或者获取焦点,这就是触摸模式。


对于一个可触摸的设备,一旦用户触摸了设备屏幕,就会进入到触摸模式,从这一点开始,只有isFocusInTouchMode为true的view才可以获取焦点,比如编辑框,其他的可触摸的view在被触摸时不会再获取焦点,这类view只会响应点击事件,不会去响应触摸事件


任何时候只要用户点击了按键,会立刻退出触摸模式,并且找到一个view让其获取焦点或者高亮,以便用户在不触摸的情况下可以继续对用户界面进行交互。

触摸模式的状态时activity保存的 ,通过activity调用isInTouchMode方法,可以看看当前是否处于触摸模式下。

Chapter Ten Tags(标签)

与id不同,tags不是用来标识view的,tags本质上是与view相关联的一些额外的信息,tags通常来用存储一些与view相关的一些数据,而不是把他们放在单独的结构中。标签是个很方便的记忆的东西,我一般在adapter刷新数据中用的较多,用法其实也很简单。
首先你需要在string.xml文件中定义一个标签,格式如下

<resources>

。。。。。
    <item name="R.tag.view" type="id"/>
</resources>

然后你可以在Java中传递view的信息



可以看到有两种方式,第一是setTag(要保存的信息),第二种是setTag(key,value);如果你只需要保存一个view相关的信息,完全可以使用setTag(value)的方式,但如果要保存的view的信息有多个,那就要使用setTag(key,value)来保存,然后通过对应的tag的key来获取所保存的信息。


其他与measure,layout,draw以及触摸或者按键事件的处理过程需要详细研究


【S】【actor added 】【2016-12-29】

Chapter Eleven,Layout

Layout包含两个过程:measure测量过程和layout定位布局的过程。

通过measure(int,int)来实现测量过程,测量是对view树从上到下进行递归测量的一个过程。在递归测量的过程中每一个view把自己的测量数据交给view树。在测量过程结束后每一个view存储了自己的测量值。

layout定位布局的过程发生在layout(int,int,int,int),也是沿着view树从上到下的过程,在layout过程中每一个父view有责任根据measure过程中所计算的子view的大小去定位子view的位置。


当一个view的measure的方法返回时,该view的measuredWIdth和measuredHeight必须被设置,也就是说,如果调用了measure方法对view进行了测量,那就必须调用setMeasuredDimensionRaw方法将view包括该view的子view的measuredHeight和measuredWidth保存起来。view的measuredHeight/measuredWidth大小必须遵循父view对子view的限制。只有这样才能保证在测量过程结束后父view能够接受子view的尺寸,进行更好的布局。父view可能对子view多次调用measure方法。例如,父view去测量未指明大小的view所想要的size,又或者所有view的大小加起来过大或者过小时就会重新进行measure。

measure的过程涉及到两个类。一个是MeasureSpec(测量规范)用来让子view告诉父view他们想要怎样被测量。还有一个LayoutParams类用来描述一个view想要多大,对于view的尺寸可能是以下三种的一个值

  • an exact number:一个确定的值
  • match_parent:和父类一样大
  • wrap_parent:包裹内容.

不同的viewgroup有不同 的layoutparams的子类,例如,Absolutelayout的layoutparams的子类可以添加X和Y的值。

测量规范是父view对子view的一个测量规范,一个测量规范有三种模式

  • UNSPECIFIED:未指明的,父view不对子view的测量做任何要求,子view想要多大就多大
  • EXACTLY:准确的值,父view给子view一个确定值,不论子view想要多大,只能使用父view给子view规定的值
  • AT_MOST:至多,父view会规定一个最大值,子view的大小不能超过这个值

通过调用requestlayout()方法可以初始化layout,当一个view察觉到当前的边界已经不再适合时会调用。


Chapter Twelve,Drawing


Drawing用来遍历整个view树并且渲染无效区域的view,因为drawing是沿着view进行遍历的,这也就意味着父view优先于子view的绘制,同一级的view是按照他们view树中出现的顺序进行绘制,如果你给一个view设置了背景图片,那么在回调onDraw方法之前会优先绘制背景图片。

framework不会绘制不处于无效区域(无效区域是指还未被使用的一片区域)的view。

通过调用invaliate()方法来强制进行drawing。


Chapter Thirteen,Animation

可以调用setAnimation(animation)或者startAnimation(animation)来给view绑定一个动画。动画可以是view进行伸缩,旋转,平移和透明度渐变的动画。如果一个有子view的view添加了动画,那么这个动画会影响以该view为一个根节点沿着view树向下的所有view。当开始动画后,framework会按照动画要求对view进行重新绘制,直到动画结束。


Chapter Fourteen,嵌套类

  • BaseSavedState:基类,通过继承该类并借助onSaveInstanceState保存自己状态
  • MeasureSpec:封装了父view对子view的测量要求
  • onClickListener:当view被点击时调用
  • onCreateContextMenuListener:当view创建了上下文菜单时触发
  • onFocusChangedListener:当view的焦点发生改变时触发
  • onKeyListener:当有按键事件会触发
  • onLongClickListener:长按时触发
  • onTouchListener:触摸时触发


  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值