Android 控件架构与自定义控件详解
架构:
- PhoneWindow 将一个 DecorView 设置为整个应用窗口的根 View,这里面所有 View 的监听事件,都通过 WindowManagerService 来接收。DecorView 分为 TitleView 和 ContentView,ContentView 是一个 ID 为 content 的 FrameLayout
- 在
onCreate()
方法中调用setContentView()
方法后,ActivityManagerService 会回调onResume()
方法,此时系统才会把整个 DecorView 添加到 PhoneWindow 中,并让其显示出来,从而完成最终的界面绘制。
View 的测量:
测量 View 的类:MeasureSpec 类,它是一个32位的 int 值,高两位为测量模式,低30位为测量大小,使用位运算提高并优化效率。
重写 onMeasure()
后,最终要做的是把测量后的宽高值作为参数设置给 setMeasureDimension()
方法。
[代码]java代码:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec){ setMeasureDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } //可作为模板代码! private int measureWidth( int measureSpec){ int result = 0 ; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY){ //精确值模式,指定具体数值 result = specSize; } else { result = 200 ; //先设置一个默认大小 //最大值模式,layout_width 或 layout_height 为 wrap_content 时,控件大小随控件的内容变化而变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。 if (specMode == MeasureSpec.AT_MOST){ result = Math.min(result, specSize); //取出我们指定的大小和 specSize 中最小的一个来作为最后的测量值 } //MeasureSpec.UNSPECIFIED 不指定其大小,View 想多大就多大 } return result; } |
即,如果不重写 onMeasure()
方法,系统则会不知道该默认多大尺寸,就会默认填充整个父布局,所以,重写 onMeasure()
方法的目的,就是为了能够给 View 一个 wrap_content 属性下的默认大小。
View 的绘制
onDraw()
中的参数,就是 Canvas 对象,使用该对象进行绘图,而在其他地方,则需要 new 出该对象:
[代码]java代码:
1
|
Canvas canvas = new Canvas(bitmap); |
传进去的 bitmap 是与这个 bitmap 创建的 Canvas 画布紧密联系的,这个过程称为装载画布。该 bitmap 用来存储所有绘制在 Canvas 上的像素信息。所有的 Canvas.drawXXX 方法都发生在这个 bitmap 上。
[代码]java代码:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@Override protected void onDraw(Canvas canvas){ //... //在 onDraw 方法中绘制两个 bitmap canvas.drawBitmap(bitmap1, 0 , 0 , null ); canvas.drawBitmap(bitmap2, 0 , 0 , null ); //... } private void otherMethod(){ //将 bitmap2 装载到另一个 Canvas 对象中 Canvas mCanvas = new Canvas(bitmap2); //其他地方使用 Canvas 对象的绘图方法在装载 bitmap2 的 Canvas 对象上进行绘图 mCanvas.drawXXX } |
通过 mCanvas 将绘制效果作用在了 bitmap2 上,再刷新 View 的时候,就会发现通过 onDraw()
方法画出来的 bitmap2 已经改变,因为 bitmap2 承载了在 mCanvas 上所进行的绘图操作。我们没有将图形直接绘制在 onDraw()
方法制定的那块画布上,而是通过改变 bitmap,让 View 重绘,从而显示改变之后的 bitmap。
ViewGroup 的测量
当 ViewGroup 的大小为 wrap_content 时,ViewGroup 需要对子 View 进行遍历,以便获得所有子 View 大小从而决定自己的大小,即调用子 View 的 Measure 方法来获得每一个子 View 的测量结果。
子 View 测量完毕后,ViewGroup 执行 Layout 过程时,同样是遍历调用子 View 的 Layout 方法,并指定其具体显示的位置,从而来决定其布局位置。
自定义 View
[代码]java代码:
1
2
3
4
5
6
|
@Override protected void onDraw(Canvas canvas){ //在回调父类方法前,对 TextView 来说是在绘制文本内容之前,实现逻辑 super .onDraw(canvas); //之后,绘制文本之后 } |
在 attrs.xml 中通过使用<declar-styleable>
标签声明使用了自定义属性,使用如下代码获得在布局文件中自定义的那些属性
[代码]java代码:
1
2
3
4
|
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar); mLeftColor = ta.getColor(R.styleable.TopBar_leftTextColor, 0 ); //完成资源回收,避免重新创建的时候的错误 ta.recycle(); |
自定义 ViewGroup
重写 onMeasure()
来对子 View 进行测量,重写 onLayout()
确定子 View 位置,重写onTouchEvent()
增加响应事件。
实例需求:自定义 ViewGroup 实现类似 ScrollView 上下滑动,同时增加粘性效果。即,当一个子 View 向上滑动大于一定距离后,松开将自动上滑,显示下一个子 View,否则回到原始位置。
步骤一:先实现类似 ScrollView 功能:
[代码]java代码:
01
02
03
04
05
06
07
08
09
10
|
@override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec){ super .onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); //遍历通知子 View 对其自身进行测量 for ( int i = 0 ;i < count; ++i){ View childView = getChildAt(i); measureChild(childView, widthMeasureSpec, heightMeasureSpec); } } |
步骤二:再对子 View 进行放置位置设定,让每个子 View 都显示完整的一屏。所以,本例中ViewGroup 的高度就是子 View 的个数乘以屏幕高度,然后遍历设定每个子 View 放置的位置
[代码]java代码:
1
2
3
4
5
6
7
8
9
|
@Override protected void onLayout( boolean changed, int l, int t, int r, int b){ int childCount = getChildCount(); //设置 ViewGroup 高度 MarginLayoutParams mlp = (MarginLayoutParams)getLayoutParams(); mlp.height = mScreenHeight.childCount; setLayoutParams(mlp); //修改子 View 的 top 和 bottom 属性,使它们依次排列 for ( int i= 0 ; i<childcount; i++){= "" view= "" child= "getChildAt(t);" if (child.getvisibility()= "" != "View.GONE){" child.layout(l,= "" i*mscreenheight,= "" r,= "" (i+ 1 )*mscreenheight);= "" }= "" }<= "" pre= "" ></childcount;> |
步骤三:
[代码]java代码:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
@Override public boolean onTouchEvent(MotionEvent event){ int y = ( int )event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mLastY = y; mStart = getScrollY(); //记录按下位置 break ; case MotionEvent.ACTION_MOVE: if (!mScroller.isFinished()){ mScroller.abortAnimation(); } int dy = mLastY - y; if (getScrollY() < 0 ){ dy = 0 ; } if (getScrollY() >getHeight - mScreenHeight){ dy = 0 ; } scrollBy( 0 , dy); //随手指滚动 dy mLastY = y; break ; case MotionEvent.ACTION_UP: mEnd = getScrollY(); int dScrollY = mEnd - mStart; if (dScrollY > 0 ){ //上滑 if (dScrollY < mScreenHeight / 3 ){ //小于一定距离, 滚回去 mScroller.startScroll( 0 ,getScrollY(), 0 , -dScrollY); } else { //大于,则滚动完剩余的距离 mScroller.startScroll( 0 ,getScrollY(), 0 , mScreenHeight-dScrollY); } } else { //同理 if (-dScrollY < mScreenHeight / 3 ){ mScroller.startScroll( 0 , getScrollY(), 0 , -dScrollY); } else { mScroller.startScroll( 0 , getScrollY(), 0 , -mScreenHeight - dScrollY); } } break ; } postInvalidata(); return true ; } /** *Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary. This will typically be done if the child is animating a scroll using a Scroller object. **/ @Override public void computeScroll(){ super .computeScroll(); if (mScroller.computeScrollOffset()){ scrollTo( 0 , mScroller.getCurrY()); postInvalidate(); } } |
事件拦截机制
点击 View 的 log:
[代码]java代码:
1
2
3
4
5
6
7
8
|
ViewGroupA dispatchTouchEvent ViewGroupA onInterceptTouchEvent ViewGroupB dispatchTouchEvent ViewGroupB onInterceptTouchEvent View dispatchTouchEvent View onTouchEvent //last event, will back to parent ViewGroupB onTouchEvent ViewGroupA onTouchEvent |
所以事件传递顺序是:先执行 dispatchTouchEvent()
然后是 onInterceptTouchEvent()
。返回值:True,拦截,不继续;False,不拦截,继续流程。初始返回是 false。
事件处理顺序是:onTouchEvent()
。返回值:True,处理了,不审核;False,给上级处理。初始返回是 false。
即:
分发、拦截:如果某个 ViewGroup 直接使用
dispatchTouchEvent()
返回了 true ,则分发拦截结束,不再向其子 View 传递,则,直接执行该 ViewGroup 的onTouchEvent()
,然后继续向上处理对应 ViewGroup 的onTouchEvent()
。处理:如果某个 View 直接在
onTouchEvent()
中返回了 true。则上级不再执行onTouchEvent()
。所有的处理在此结束。
Android 控件架构与自定义控件详解的更多相关文章
- 《Android群英传》读书笔记 (2) 第三章 控件架构与自定义控件详解 + 第四章 ListView使用技巧 + 第五章 Scroll分析
第三章 Android控件架构与自定义控件详解 1.Android控件架构下图是UI界面架构图,每个Activity都有一个Window对象,通常是由PhoneWindow类来实现的.PhoneWin ...
- 第二章 控件架构与自定义控件详解 + ListView使用技巧 + Scroll分析
1.Android控件架构下图是UI界面架构图,每个Activity都有一个Window对象,通常是由PhoneWindow类来实现的.PhoneWindow将DecorView作为整个应用窗口的根V ...
- Android群英传笔记——第三章:Android控件架构与自定义控件讲解
Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...
- Android 控件架构及View、ViewGroup的测量
附录:示例代码地址 控件在Android开发的过程中是必不可少的,无论是我们在使用系统控件还是自定义的控件.下面我们将讲解一下Android的控件架构,以及如何实现自定义控件. 1.Android控件 ...
- Android 控件架构
如果说Android上的app是一个有血有肉的人的话,那么人靠衣装马靠鞍,那么控件就是把app装扮的漂漂亮亮的“衣服”.那么安卓的控件到底是如何架构,又是如何渲染的了. 无论是什么控件,在Androi ...
- VB6.0中WinSock控件属性和方法详解
原文链接:http://liweibird.blog.51cto.com/631764/653134 WinSock控件能够通过UDP协议(用户数据报协议)或TCP协议(数据传输协议)连接到远程的机器 ...
- Appium+python自动化(二十五)- 那些让人抓耳挠腮、揪头发和掉头发的事 - 获取控件ID(超详解)
简介 在前边的第二十二篇文章里,已经分享了通过获取控件的坐标点来获取点击事件的所需要的点击位置,那么还有没有其他方法来获取控件点击事件所需要的点击位置呢?答案是:Yes!因为在不同的大小屏幕的手机上获 ...
- ASP.NET验证控件应用实例与详解。
ASP.NET公有六种验证控件,分别如下: 控件名 功能描叙 1RequiredFieldValidator(必须字段验证) 用于检查是否有输入值 2CompareValidator(比 ...
- jquery网页日历显示控件calendar3.1使用详解
关于日历插件,我做了好多次尝试,一直致力于开发一款简单易用的日历控件.我的想法是争取在引用这个控件后,用一行js代码就能做出一个日历,若在加点参数,就能自定义外观和功能丰富多彩的日历.Calendar ...
随机推荐
- 使用 Spirit 类在 XNA 中创建游戏中的基本单位精灵(十三)
平方已经开发了一些 Windows Phone 上的一些游戏,算不上什么技术大牛.在这里分享一下经验,仅为了和各位朋友交流经验.平方会逐步将自己编写的类上传到托管项目中,没有什么好名字,就叫 WPXN ...
- 菜鸟之路——机器学习之非线性回归个人理解及python实现
关键词: 梯度下降:就是让数据顺着梯度最大的方向,也就是函数导数最大的放下下降,使其快速的接近结果. Cost函数等公式太长,不在这打了.网上多得是. 这个非线性回归说白了就是缩小版的神经网络. py ...
- html之表单标签
表单标签的属性: 用于向服务器传输数据 表单能够包含input元素,比如文本字段,复选框,单选框,提交按钮等等 表单还可以包含textarea(简介之类的),select(下拉),fieldset和l ...
- 感谢beyond,感谢家驹
- 通过Url网络编程实现下载
import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputS ...
- win7删除一个空白文件夹总是显示:“找不到该项目,该项目不在E盘中,请确认该项目的位置,重试”的解决办法
把下面的代码复制粘贴到一新建的txt记事本文档中,并另存为del.bat文件(或者你喜欢的名字),注意扩展名为批处理文件bat: DEL /F /A /Q \\?\%1 RD /S ...
- [NOI2008][bzoj1061] 志愿者招募 [费用流+巧妙的建图]
题面 传送门 思路 引入:网络流? 看到这道题,第一想法是用一个dp来完成决策 但是,显然这道题的数据并不允许我们进行dp,尤其是有10000种志愿者的情况下 那么我们就要想别的办法来解决: 贪心?这 ...
- Linux中的小括号和大括号,${}/$()/()/{}/${var:-string}/${var:=string}/${var:+string}/${var:?string}/${var%pattern}/${var#pattern}/${var%%pattern}/${var##pattern}
简单记录一下大小括号在Linux中的用处. 1.${var},这是Linux中变量的原形.所以$var,别忘记了你的本来面目. # a= # echo $a # echo ${a} # echo ${ ...
- 拖动层 拖动div 封装js 貌似不兼容FF,郁闷
原文发布时间为:2009-12-02 -- 来源于本人的百度文章 [由搬家工具导入] <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tran ...
- c language compile process.