天天看点

Android学习笔记之-自定义View实例及View的绘制过程(一)

View组件的作用类似于 Swing 编程中的JPanel, 它只是一个矩形的空白区域, View组件没有任何内容. 对于Android 应用的其他UI组件来说, 他们都继承了View组件. 然后在View组件提供的空白区域上绘制外观.

基于Android UI 组件的实现原理, 开发者完全开发出项目定制的组件—-当Android系统提供的UI组件不足以满足项目需求时, 开发者可以通过继承View 来派生自定义组件.

基于Android UI 组件的实现原理, 开发者完全开发出项目定制的组件—-当Android系统提供的UI组件不足以满足项目需求时, 开发者可以通过继承View 来派生自定义组件.

开发者打算派生自己UI组件时, 首先定义一个继承View基类的子类, 然后重写View类的一个或多个方法, 通常可以被用户重写的方法如下.

构造器: 重写构造器是定制View的最基本方式, 当Java代码创建一个View实例, 或根据XML布局文件加载并构建界面时将需要调用构造器.

> onFinishInflate(): 这是一个回调方法, 当应用从XML布局文件加载该组件并利用它来构建界面之后, 该方法将会回调.

> onMeasure(int, int):调用该方法来检测View组件及它所包含的所有子类组件的大小.

> onLayout(boolean, int, int, int, int):当该组件需要分配其子组件的位置-大小时, 该方法就会被调用.

> onSizeChanged(int, int, int, int):当该组件的大小被改变时回调该方法.

> onDraw(Canvas):当该组件将要绘制它的内容时回调该方法进行绘制.

> onKeyDown(int, KeyEvent):当某个键被按下时触发该方法.

> onKeyUp(int, KeyEvent):当松开某个键时触发该方法.

> onTrackballEvent(MotionEvent):当发生轨迹球事件时触发该方法.

> onTouchEvent(MotionEvent):当发生触摸屏事件时触发该方法.

> onWindowFocusChanged(boolean):当该组件得到-失去焦点时触发该方法.

> onAttachedToWindow():当把该组件放入某个窗口时触发该方法.

> onDetachedFromWindow():当把该组件从某个窗口上分离时触发该方法.

> onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该方法.

当需要开发自定义View时, 开发者并不需要重写上面列出的所有方法, 而是可以根据业务需求重写上面的部分方法.

实例 : 跟随手指的小球.
package com.yuyang.customview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class DrawView extends View {

    private float currentX = ;
    private float currentY = ;

    /** 创建画笔 */
    private Paint mPaint = new Paint();

    public DrawView(Context context) {
        this(context, null);
        // TODO Auto-generated constructor stub
    }

    public DrawView(Context context, AttributeSet attrs) {
        this(context, attrs, );
        // TODO Auto-generated constructor stub
    }

    public DrawView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        //设置画笔颜色
        mPaint.setColor(Color.RED);
        //绘制一个小圆(作为小球)
        canvas.drawCircle(currentX, currentY, , mPaint);
    }

    //为该组件的触碰事件重写事件处理方法
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        //修改currentX currentY 两个属性
        currentX = event.getX();
        currentY = event.getY();
        //通知当前组件重绘自己
        invalidate();
        //返回 true 表明该处理方法已经处理该事件
        return true;
    }
}
           

上面的DrawView组件继承了View基类, 并重写了onDraw方法—-该方法负责在该组件的指定位置绘制一个小球. 除此之外, 该组件还重写了onTouchEvent(MotionEvent event)方法, 该方法用于处理该组件的触碰事件, 当用户手指触碰该组事件时将会激发该方法.

有了这个自定义组件之后, 把这个自定义组件添加到XML文件中就可以实现跟随手指移动的小球了.

View 绘制的流程

上面的例子比较简单, 主要是为了说明自定义控件的属性. 下面我们来说说View的绘制步骤.

View 系统获得消息后, 会按照默认的逻辑来派发消息, 主要就是把该消息派发给所有的子视图(View), 以便相应的子视图能够获得消息并执行不同的任务. 如果任务是一个纯后台任务, 即该任务不会引起任何界面的变化, 那么View系统接下来仅仅按照默认逻辑继续派发下一个用户消息. 而如果该任务会引起界面变化, 那么View系统则要重新绘制界面.

重绘界面的过程大致分为三步:

  1. 计算该窗口中所有视图的大小, 该步骤也称之为测量(measure)过程, 即测量出所有视图的实际尺寸大小. 但是, 并不是所有导致界面重绘的操作都需要重新计算窗口的大小, 在View的内部逻辑中, 使用了一个内部变量保存相应的状态, 当用户的某个操作导致改变了视图大小时, 会设置该变量, 而View的内部逻辑会根据该变量, 决定是否需要重新测量.
  2. 为所有视图分配位置, 该步骤也称之为布局(layout)过程, 即应该把视图放在屏幕的什么位置上.
  3. 把视图绘制到屏幕上. 当系统获知了该窗口中所有视图的大小位置后, 就可以完全确定屏幕上应该显示哪些视图了. 在绘制时, 系统会为每一个窗口创建一个画布(Canvas)对象, 并把这个Canvas对象传递给从根视图到所有子视图. View系统将Canvas传递给子视图时, 都先将对该画布进行一次裁切(Clip), 从而在子视图看来, 总是从画布的(0, 0)坐标开始绘制.

以上是本人通过各种手段获取的View绘制机制, 对于纯使用的同学应该是没多大问题, 有想要深究同学还是推荐去看Android官方API.