Android群英传之Android控件架构与自定义控件

1、Android控件架构

ViewGroup可以包含多个View,形成控件树。上层控件负责下层子控件的测量与绘制。findViewById是在控件树中进行深度优先搜索。ViewGroup实现了ViewParent接口,Viewparent定义了一个控件作为父控件的职责,负责子布局与父布局的交互,例如requestLayout。

UI界面架构图

每个Activity都有一个Window对象,一般由PhoneWindow实现,各种监听事件都通过WindowManagerService接收,PhoneWindow将DecorView作为整个应用窗口的根View。DecorView分为两部分:TitleView和ContentView。

为什么requestWindowFeature方法一定要在setContentView之前才能生效?
因为一般的视图树会默认基本分为两部分,上面TitleBar下面Content,在requestWindowFeature(Window.Feature_NO_TITLE)后,DecorView只剩Content。而在setContentView后,WindowManagerService会回调onResume,把整个DecorView添加到PhoneWindow,让其显示。若在setContentView之后再设置requestWindowFeature,因为DecorView已经被添加了,所以再改变也没有用了。

2、View的测量

即使我们不看Android的View源码,对于一个手机上的视图,我们也应该想到,它应该是先确定大小,再确定它的位置,最后进行绘制。

核心类:MeasureSpec
核心方法:MeasureSpec.getMode、MeasureSpec.getSize

MeasureSpec是一个32位的int值,高2位是测量模式,低30位是测量大小。
测量模式有三种
1. EXACTLY:精确值模式,属性设置为具体数值或match_parent时,使用此模式
2. AT_MOST:最大值模式,属性设置为wrap_content时,使用此模式
3. UNSPECIFIED:不指定大小测量模式,通常在绘制自定义View时才会用到

View类默认的onMeasure方法只支持EXACTLY模式,想让自定义View支持wrap_content属性,必须重写onMeasure方法来指定wrap_content时的大小

重写onMeasure后,最终的的工作就是把测量后的宽高值作为参数设置给setMeasuredDimension方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //计算宽和高
    //模板代码...
    widthMeasureSpec = measureWidth(widthMeasureSpec);
    heightMeasureSpec = measureHeight(heightMeasureSpec);
    setMeasuredDimension(widthMeasureSpec,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;
        if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
        }
    }
    return result;
}

3、View的绘制

重写View的onDraw(Canvas canvas),在画布上绘图即可

通常情况下,为啥Canvas对象的创建要传入参数Bitmap?(好吧,通常情况?我为啥不知道)
穿进去的Bitmap与通过Bitmap创建的Canvas是紧紧联系在一起的,这个Bitmap用来存储所有绘制在Canvas上的像素信息,当使用Bitmap创建Canvas后,后面调用的所有Canvas.drawXXX方法都发生在这个Bitmap上。

还是不太明白,绘制内容都放在bitmap上而不绘制在画布上,貌似有利于重用bitmap在多个画布上?

4、ViewGroup的测量

ViewGroup在测量时遍历所有的子View,调用子View的measure方法来返回每一个子view的大小。
测量完毕后,就把子View放到合适的位置,就是Layout的过程。

5、ViewGroup的绘制

除非要指定ViewGroup的背景,否则ViewGroup的onDraw不会被调用,但是ViewGroup会使用dispatchDraw来遍历子View,调用他们的绘制方法。

6、自定义View

有三种自定义View的方式

1)对现有控件进行拓展

对现有控件进行拓展的代码结构:

@Override
protected void onDraw(Canvas canvas) {
    //在回调父类方法之前实现自己的逻辑,对TextView来说就是在绘制文本之前
    super.onDraw(canvas);
    //在回调父类方法之后实现自己的逻辑,对TextView来说就是在绘制文本之后
}

书中对TextView进行拓展的例子

private void initView() {
    mPaint1 = new Paint();
    mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
    mPaint1.setStyle(Paint.Style.FILL);
    mPaint2 = new Paint();
    mPaint2.setColor(Color.YELLOW);
    mPaint2.setStyle(Paint.Style.FILL);
}

@Override
protected void onDraw(Canvas canvas) {
    // 绘制外层矩形
    canvas.drawRect(
            0,
            0,
            getMeasuredWidth(),
            getMeasuredHeight(),
            mPaint1);
    // 绘制内层矩形
    canvas.drawRect(
            10,
            10,
            getMeasuredWidth() - 10,
            getMeasuredHeight() - 10,
            mPaint2);
    canvas.save();
    // 绘制文字前平移10像素
    canvas.translate(10, 0);
    // 父类完成的方法,即绘制文本
    super.onDraw(canvas);
    canvas.restore();
}

2)创建复合控件

一般需要继承一个合适的ViewGroup

a、定义属性

在res资源目录的value目录下创建一个attrs.xml,一般形如:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TitleBar">
        <attr name="title" format="string" />
        <attr name="titleTextColor" format="color" />
        <attr name="titleTextSize" format="dimension" />
    </declare-styleable>
</resources>

自定义属性的使用

//获取自定义属性的对象 TypedArray
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTitle);

 //getString 获取String类型的值
 mTitle = ta.getString(R.styleable.MyTitle_title);
 //getColor 获取Color类型的值
 mTitleColor = ta.getColor(R.styleable.MyTitle_titleTextColors, 0);
 //getDimension 获取尺寸类型的值
 mTitleSize = ta.getDimension(R.styleable.MyTitle_titleTextSize, 0);
 //一般到最后,调用recyle避免重新创建时的错误
 ta.recycle();
b、组合控件
  • 给自定义控件里面的基本控件设置前面获取的属性。
  • 定义接口,实现回调
c、引用UI模板
    //引入命名空间,此命名空间为custom
    xmlns:custom="http://schemas.android.com/apk/res-auto"

    //自定义属性的定义
    custom:title="标题"
    custom:titleTextColor="#123412"
    custom:titleTextSize="15sp"

3)重写View实现全新的控件

创建自定义View的难点在于绘制控件和实现交互,通常需要继承View类,并重写onDraw、onMeasure等方法来实现绘制逻辑,同时通过重写onTouchEvent等触控事方法来实现交互逻辑。

7、自定义ViewGroup

一般需要重写onMeasure方法来对子View进行测量,onLayout来确定子View的位置,onTouchEvent来增加响应时间。

8、事件拦截机制

核心类:MotionEvent
核心方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

dispatchTouchEvent方法一般不管。ViewGroup比View多重写了一个onInterceptTouchEvent方法,也就是事件拦截机制的核心方法。

onInterceptTouchEvent方法返回值:true,拦截,false,不拦截
onTouchEvent方法返回值:true,代表已自行处理,不往上传递,未处理(其实可以偷偷处理,就告诉你没处理false),继续往上传递

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值