架构:

  • 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 控件架构与自定义控件详解的更多相关文章

  1. 《Android群英传》读书笔记 (2) 第三章 控件架构与自定义控件详解 + 第四章 ListView使用技巧 + 第五章 Scroll分析

    第三章 Android控件架构与自定义控件详解 1.Android控件架构下图是UI界面架构图,每个Activity都有一个Window对象,通常是由PhoneWindow类来实现的.PhoneWin ...

  2. 第二章 控件架构与自定义控件详解 + ListView使用技巧 + Scroll分析

    1.Android控件架构下图是UI界面架构图,每个Activity都有一个Window对象,通常是由PhoneWindow类来实现的.PhoneWindow将DecorView作为整个应用窗口的根V ...

  3. Android群英传笔记——第三章:Android控件架构与自定义控件讲解

    Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...

  4. Android 控件架构及View、ViewGroup的测量

    附录:示例代码地址 控件在Android开发的过程中是必不可少的,无论是我们在使用系统控件还是自定义的控件.下面我们将讲解一下Android的控件架构,以及如何实现自定义控件. 1.Android控件 ...

  5. Android 控件架构

    如果说Android上的app是一个有血有肉的人的话,那么人靠衣装马靠鞍,那么控件就是把app装扮的漂漂亮亮的“衣服”.那么安卓的控件到底是如何架构,又是如何渲染的了. 无论是什么控件,在Androi ...

  6. VB6.0中WinSock控件属性和方法详解

    原文链接:http://liweibird.blog.51cto.com/631764/653134 WinSock控件能够通过UDP协议(用户数据报协议)或TCP协议(数据传输协议)连接到远程的机器 ...

  7. Appium+python自动化(二十五)- 那些让人抓耳挠腮、揪头发和掉头发的事 - 获取控件ID(超详解)

    简介 在前边的第二十二篇文章里,已经分享了通过获取控件的坐标点来获取点击事件的所需要的点击位置,那么还有没有其他方法来获取控件点击事件所需要的点击位置呢?答案是:Yes!因为在不同的大小屏幕的手机上获 ...

  8. ASP.NET验证控件应用实例与详解。

    ASP.NET公有六种验证控件,分别如下:  控件名      功能描叙 1RequiredFieldValidator(必须字段验证)  用于检查是否有输入值 2CompareValidator(比 ...

  9. jquery网页日历显示控件calendar3.1使用详解

    关于日历插件,我做了好多次尝试,一直致力于开发一款简单易用的日历控件.我的想法是争取在引用这个控件后,用一行js代码就能做出一个日历,若在加点参数,就能自定义外观和功能丰富多彩的日历.Calendar ...

随机推荐

  1. 37、iamgeview 图层叠加

    1 Drawable d1 = new BitmapDrawable(circleBitmap); Drawable d2 = login.this.getResources().getDrawabl ...

  2. 微信小程序简单的数据表格及查询功能

    简介: 此项目是一个前后端分离的小demo, 开发工具:idea+微信小程序开发工具 前端:界面布局样式和js的跳转 后端:依靠SpringBoot的业务逻辑层 项目的码云地址: https://gi ...

  3. SQL 语句执行后同步返回结果条数

    PgSQL SELECT COUNT(*) OVER() AS res_count FROM table WHERE ... MySQL mysql> SELECT SQL_CALC_FOUND ...

  4. maven学习(十三)——eclipse整合maven插件

    一.安装Maven插件 下载下来的maven插件如下图所示:,插件存放的路径是:E:/MavenProject/Maven2EclipsePlugin

  5. idea调试查看mybitis发送的语句(引用)

    MyBatis 的配置文件 Configuration 中有相关属性,设属性即可,不用再放一个 log4j.properties 文件. <configuration> <setti ...

  6. 【bzoj4825】[Hnoi2017]单旋 线段树+STL-set

    题目描述 H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天 ...

  7. Codeforces 954E Water Taps

    题目大意 有 $n$($1\le n\le 200000$)个变量 $x_1, x_2, \dots, x_n$,满足 \begin{equation} 0\le x_i \le a_i \label ...

  8. 【Begin】

    迫于无奈,我想提高写博速度.我要尽量压缩每道题的题解思路.最终我选择背叛大米兔,但是我支持它.因为它的每一篇博客耗时巨大,精贵的竞赛集训时间经不起它花:可同时精致的博客会带给来浏览的Oier们更多东西 ...

  9. modulus CRT

    (吐槽)额..CRT本来就是modulus的么.. CRT是可以每次加一个条件的(当然要保证coprime) 那么我们考虑 x=a (mod p1) x=b (mod p2) 这样的话我们知道 x=a ...

  10. mybatis 关联查询时,从表只返回第一条记录解决办法

    如果两表联查,主表和明细表的主键都是id的话,明细表的多条只能查询出来第一条. 造成以上情况可能的原因: 1.级联查询的时候,主表和从表有一样的字段名的时候,在mysql上命令查询是没问题的.但在my ...