Android控件架构与自定义控件详解

内容概要

  • Android控件架构
  • View的测量与绘制
  • ViewGroup的测量与绘制
  • 自定义控件的三种方式
  • 事件的拦截机制

3.1Android控件架构

在这里插入图片描述
每个Activity都包含一个window对象,在Android中Window对象通常由PhoneWindow来实现。PhoneWindow将一个DecorView设置为整个应用窗口的根View。DecorView作为窗口界面的顶层视图,封装了一些窗口操作的通用方法。DecorView将要显示的具体内容呈现在了PhoneWindow上,这里的所有View的监听事件,都通过WindowMannagerService来进行接收,并通过Activity对象来回调相应的onClickListener。在显示上,它将屏幕分成两部分,一个是TitleView,另外一个是ContentView。ContentView是一个的ID为content的Framelayout。

用户通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)来设置全屏显示,视图布局就只有Content了,正因如此调用requestWindowFeature()方法一定要在调用setContentView方法之前才能生效。

在代码中,当程序在onCreate()方法中调用setContentView方法后,ActivityMannagerService会调用onResume()方法,此时系统会把整个DecorView添加到PhoneWindow中,让其显示出来,从而完成界面的绘制。

3.2View的测量

MeasureSpec类-----帮助测量View。
32位的int值,高2位位测量的模式,低30位为测量的大小,使用位运算提高并优化效率。

测量的模式

  • EXACTLY 精确模式
  • AT_MOST 最大值模式
  • UNSPECIFIED 此模式不指定大小测量模式,View想多大就多大,通常自定义View时使用

View的测量,重写onMeasure方法。View的源码onMeasure调用setMeasuredDimension(int measuredWidth, int measuredHeight)方法将测量后的宽高值设置进去。
具体代码`

package wangsheng.swpuiot.qunyingzhuan.ch3.section1;
//3.2View的测量


import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context,  @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context,  @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    private int measureWidth(int widthMeasureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
}

这是宽高都设置为Wrap_Content
在这里插入图片描述
宽高都设置为match_parent
在这里插入图片描述
宽高都设置为400px
在这里插入图片描述

3.3View的绘制

测量好一个View之后,就可以简单重写onDraw(Canvas canvas)方法,并在Canvas对象上来绘制所需要的图形。
Canvas像是一个画板,使用Paint就可以在上面作画。

创建一个Canvas对象

Canvas	canvas = new Canvas(bitmap);

首先在onDraw()方法中绘制两个bitmap

canvas.drawBitmap(bitmap1,0,0,null);
canvas.drawBitmap(bitmap2,0,0,null);

对于bitmap2将它装载到另外一个Canvas对象中

Canvas mCanvas = new Canvas(bitmap2);

在其他地方使用Canvas对象的绘图方法在装在bitmap2的Canvas对象上进行绘图

mCanvas.drawXXX

虽然使用了Canvas绘制API,但其实并没有将图形直接绘制在onDraw()方法指定的那块画布上,而是通过改变bitmap,让View重绘,从而显示改变之后的bitmap。

3.4ViewGroup的测量

ViewGroup回去管理子View。当ViewGroup的大小为wrap_content时,ViewGroup的大小取决于所有子View的大小。其他模式通过具体指定值确定大小。

ViewGroup测量时通过遍历所有子View,从而调用子View的Measure方法来获取每一个子View的测量结果,即是对View的测量。

ViewGroup执行Layout过程时,使用遍历调用子View的Layout方法,并指定其具体显示位置,从而决定其布局位置
在自定义ViewGroup时,通常会去重写onLayout()方法来控制其子View显示位置的逻辑。如果ViewGroup要支持wrap_contebt同样要重写onMeasure()方法,与View相同。

3.5ViewGroup的绘制

ViewGroup通常不需要绘制,因为它本身没有要绘制的东西,如果不是指定了ViewGroup的背景颜色,那么ViewGroup的onDraw方法都不会调用。但是ViewGroup会使用dispatchDraw()方法来绘制其子View,其过程同样是通过遍历所有子View,并调用子View的绘制方法来完成绘制工作。

3.6自定义View

onDraw()方法绘制View显示内容,如果需要wrap_content属性,还必须重写onMeasure()方法。通过自定义attrs属性可以设置新的属性配置值。

View中一些重要的回调方法

  • onFinishInflate():从XML加载组件后回调。
  • onSizeChanged():组件大小改变时回调。
  • onMeasure:回调该方法来进行测量。
  • onLayout():回调该方法来确定显示的位置。
  • onTouchEvent():监听到触摸事件的回调。

创建自定义View的时候不需要重写所有方法,重写特定条件的回调即可。

实现自定义控件的三种方法

  1. 对现有控件进行拓展
  2. 通过组合来实现新控件
  3. 重写View来实现全新的控件

对现有控件进行拓展
一个TextView控件的拓展
通过组合来实现新控件
标题栏组合控件

  1. 定义属性 自定义的属性
  2. 组合控件 把所有要组合的控件组合在ViewGroup中,可以通过定义接口,暴露接口给调用者实现点击事件
  3. 引用UI模板

3.8事件拦截机制分析

OnInterceptTouchEvent方法是我们主要关注的。dispatchTouchEvent方法虽然是事件分发的第一步,但一般情况不太会改写这个方法。

事件拦截机制三个重要方法

dispatchTouchEvent():分发事件
onInterceptTouchEvent():拦截事件
onTouchEvent():处理事件
举一个例子说明事件分发机制:

ViewGroupA:处于视图最下层
ViewGroupB:处于视图中间层
View:处于视图最上层
正常的事件分发机制流程:

ViewGroupA dispatchTouchEvent
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
View dispatchTouchEvent
View onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
若ViewGroupB的onInterceptTouchEvent()方法返回true的分发机制流程:

ViewGroupA dispatchTouchEvent
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
若View的onTouchEvent()方法返回true的分发机制流程:

ViewGroupA dispatchTouchEvent
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
View dispatchTouchEvent
View onTouchEvent
若ViewGroupB的onTouchEvent()方法返回true的分发机制流程:

ViewGroupA dispatchTouchEvent
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
View dispatchTouchEvent
View onTouchEvent
ViewGroupB onTouchEvent
简单的说dispatchTouchEvent()和onInterceptTouchEvent()是从下往上一层一层分发下去的,而onTouchEvent()是从上往下一层一层分发下去的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值