为了账号安全,请及时绑定邮箱和手机立即绑定

Android坐标系与View绘制流程

标签:
Android

涉及知识

绘制过程

类别

API

描述

布局

onMeasure

测量View与Child   View的大小


onLayout

确定Child View的位置


onSizeChanged

确定View的大小

绘制

onDraw

实际绘制View的内容

事件处理

onTouchEvent

处理屏幕触摸事件

重绘

invalidate

调用onDraw方法,重绘View中变化的部分

Canvas涉及方法

类别

API

描述

绘制图形

drawPoint,   drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval,   drawCircle, drawArc

依次为绘制点、直线、矩形、圆角矩形、椭圆、圆、扇形

绘制文本

drawText,   drawPosText, drawTextOnPath

依次为绘制文字、指定每个字符位置绘制文字、根据路径绘制文字

画布变换

translate,   scale, rotate, skew

依次为平移、缩放、旋转、倾斜(错切)

画布裁剪

clipPath,   clipRect, clipRegion

依次为按路径、按矩形、按区域对画布进行裁剪

Paint涉及方法

类别

API

描述

颜色

setColor,setARGB,setAlpha

依次为设置画笔颜色、透明度

类型

setStyle

填充(FILL),描边(STROKE),填充加描边(FILL_AND_STROKE)

抗锯齿

setAntiAlias

画笔是否抗锯齿

字体大小

setTextSize

设置字体大小

字体测量

getFontMetrics(),getFontMetricsInt()

返回字体的测量,返回值一次为float、int

文字宽度

measureText

返回文字的宽度

文字对齐方式

setTextAlign

左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT)

宽度

setStrokeWidth

设置画笔宽度

笔锋

setStrokeCap

默认(BUTT),半圆形(ROUND),方形(SQUARE)

PS: API较多,只列出了涉及的方法,想了解更多,请查看官方文档)

 

一、坐标系

1、屏幕坐标系

屏幕坐标系以手机屏幕的左上角为坐标原点,过的原点水平直线为X轴,向右为正方向;过原点的垂线为Y轴,向下为正方向。

2、View坐标系

View坐标系以父视图的左上角为坐标原点,过的原点水平直线为X轴,向右为正方向;过原点的垂线为Y轴,向下为正方向。

View内部拥有四个函数,用于获取View的位置

[代码]java代码:

?

1

2

3

4

getTop(); //View的顶边到其Parent View的顶边的距离,即View的顶边与View坐标系的X轴之间的距离

getLeft(); //View的左边到其Parent View的左边的距离,即View的左边与View坐标系的Y轴之间的距离

getBottom(); //View的底边到其Parent View的顶边的距离,即View的底边与View坐标系的X轴之间的距离

getRight(); //View的右边到其Parent View的左边的距离,即View的右边与View坐标系的Y轴之间的距离


二、绘制过程

1、构造函数

  构造函数用于读取一些参数、属性对View进行初始化操作
  View的构造函数有四种重载方法,分别如下:

[代码]java代码:

?

1

2

3

4

public BaseChart(Context context) {}

public BaseChart(Context context, AttributeSet attrs) {}

public BaseChart(Context context, AttributeSet attrs, int defStyleAttr) {}

public BaseChart(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}

 

context:上下文,新建时传入,如:

[代码]java代码:

?

1

BaseChart baseChart = new BaseChart(this);

 

AttributeSet:是节点的属性集合,如:

[代码]xml代码:

?

1

2

3

4

5

<com.customview.BaseChart

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:attr1="attr1   from xml"

    app:attr2="attr2   from xml"/>

 

即com.customview.BaseChart节点中的属性集合

defStyleAttr:默认风格,是指它在当前Application或Activity所用的Theme中的默认Style,如:
在attrs.xml中添加

[代码]xml代码:

?

1

<attr name="base_chart_style" format="reference" />

 

引用的是styles.xml文件中

[代码]xml代码:

?

1

2

3

4

<style name="base_chart_style">

    <item name="attr2">@string/attr2</item>

    <item name="attr3">@string/attr3</item>

</style>

 

在当前默认主题中添加这个style

[代码]xml代码:

?

1

2

3

4

5

<style name="AppTheme"parent="Theme.AppCompat.Light.DarkActionBar">

    ...

    <item name="base_chart_style">@stylebase_chart_style</item>

    ...

</style>

 

defStyleRes:默认风格,只有当defStyleAttr无效时,才会使用这个值,如:
在style.xml中添加

[代码]xml代码:

?

1

2

3

4

<style name="base_chart_res">

    <item name="attr4">attr4 from   base_chart_res</item>

    <item name="attr5">attr5 from   base_chart_res</item>

</style>

 

一个实例——BaseChart

新建BaseChart类机成自view

[代码]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

public class BaseChart   extends View {

 

    private String TAG = "BaseChart";

    public BaseChart(Context context) {

        this(context,null);

    }

 

    public BaseChart(Context context, AttributeSet   attrs) {

        this(context,   attrs,R.attr.base_chart_style);

    }

 

    public BaseChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

        this(context,   attrs, defStyleAttr,R.style.base_chart_res);

    }

 

    public BaseChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {

        super(context,   attrs, defStyleAttr, defStyleRes);

        TypedArray   array = context.obtainStyledAttributes(attrs, R.styleable.base_chart,   defStyleAttr,defStyleRes);

        int n = array.getIndexCount();

        for (int i=0; i<n; i++){="" int="" attr="array.getIndex(i);" switch="" (attr){="" case="" r.styleable.base_chart_attr1:="" log.d(tag,"attr1="">" + array.getString(attr));

                    break;

                case R.styleable.base_chart_attr2:

                    Log.d(TAG,"attr2   =>" +   array.getString(attr));

                    break;

                case R.styleable.base_chart_attr3:

                    Log.d(TAG,"attr3   =>" +   array.getString(attr));

                    break;

                case R.styleable.base_chart_attr4:

                    Log.d(TAG,"attr4   =>" +   array.getString(attr));

                    break;

                case R.styleable.base_chart_attr5:

                    Log.d(TAG,"attr5   =>" +   array.getString(attr));

                    break;

            }

        }

    }</n;>

 

obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)新增加的attrs属性说明如下:
attrs:默认属性,告诉系统需要获取那些属性的值,有多种Value类型,这里使用string类型,如:
在attrs.xml中添加

[代码]xml代码:

?

1

2

3

4

5

6

7

<declare-styleable name="base_chart">

    <attr name="attr1" format="string" />

    <attr name="attr2" format="string"/>

    <attr name="attr3" format="string"/>

    <attr name="attr4" format="string"/>

    <attr name="attr5" format="string"/>

</declare-styleable>

 

使用上面提到的变量属性和布局文件

a、defStyleAttr与defStyleRes参数先设置为0

运行后显示如下:

[代码]xml代码:

?

1

2

3

4

5

BaseChart: attr1 =>attr1 from xml

BaseChart: attr2 =>attr2 from xml

BaseChart: attr3 =>null

BaseChart: attr4 =>null

BaseChart: attr5 =>null

 

attr1与attr2输出均来自布局文件的设置

b、修改BaseView.java设置,引入defStyleAttr:

[代码]java代码:

?

1

TypedArray array =   context.obtainStyledAttributes(attrs, R.styleable.base_chart, defStyleAttr,0);

 

相当于在布局文件中设置:

[代码]java代码:

?

1

app:theme="@style/base_chart_style"

 

运行后显示如下:

[代码]java代码:

?

1

2

3

4

5

BaseChart: attr1 =>attr1 from xml

BaseChart: attr2 =>attr2 from xml

BaseChart: attr3 =>attr3 from   BaseChartStyle

BaseChart: attr4 =>null

BaseChart: attr5 =>null

 

attr1:仅在布局文件中设置,所以输出为 attr1 from xml
attr2:在布局文件与默认主题的base_chart_style都进行了设置,布局文件中的设置优先级更高,所以输出为 attr2 from xml
attr3:仅在默认主题base_chart_style中进行了设置,所以输出为 attr3 from BaseChartStyle

c、在布局文件中增加自定义的style

 

运行后结果如下:

[代码]xml代码:

?

1

2

3

4

5

BaseChart: attr1 =>attr1 from xml

BaseChart: attr2 =>attr2 from xml

BaseChart: attr3 =>attr3 from   xml_style

BaseChart: attr4 =>attr4 from   xml_style

BaseChart: attr5 =>null

 

attr1:仅在布局文件中设置,所以输出为 attr1 from xml
attr2:在布局文件与默认主题的base_chart_style都进行了设置,布局文件中的设置优先级更高,所以输出为 attr2 from xml
attr3:在默认主题base_chart_style与自定义主题的xml_style都进行了设置,自定义主题优先级更高,所以输出为 attr3 from xml_style
attr4:仅在自定义主题xml_style中进行了设置,所以输出为 attr4 from xml_style

d、修改BaseView.java设置,引入defStyleRes,修改defStyleAttr为0,否则引入的R.style.base_chart_res不会生效:

[代码]java代码:

?

1

TypedArray array =   context.obtainStyledAttributes(attrs, R.styleable.base_chart, 0 ,R.style.base_chart_res);

 

运行后输入结果如下:

[代码]xml代码:

?

1

2

3

4

5

BaseChart: attr1 =>attr1 from xml

BaseChart: attr2 =>attr2 from xml

BaseChart: attr3 =>attr3 from   xml_style

BaseChart: attr4 =>attr4 from   xml_style

BaseChart: attr5 =>attr5 =>attr5   from base_chart_res

 

attr1:仅在布局文件中设置,所以输出为 attr1 from xml
attr2:仅在布局文件中进行了设置,所以输出为 attr2 from xml
attr3:仅在自定义主题xml_style中进行了设置,所以输出为 attr3 from xml_style
attr4:在自定义主题xml_style和defStyleRes中都进行了设置,自定义主题优先级更高,所以输出为 attr4 from xml_style
attr5:仅在defStyleRes中进行了设置,所以输出为 attr5 from base_chart_res

2、onMeasure

View会在此函数中完成自己的Measure以及递归的遍历完成Child View的Measure,某些情况下需要多次Measure才能确定View的大小。
可以从onMeasure中取出宽高及其他属性:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec,   heightMeasureSpec);

    //Width

    int widthMode =   MeasureSpec.getMode(widthMeasureSpec);//宽度值

    int widthSize =   MeasureSpec.getSize(widthMeasureSpec);//宽度测量模式

    //Height

    int heightMode =   MeasureSpec.getMode(heightMeasureSpec);//高度值

    int heightSize =   MeasureSpec.getSize(heightMeasureSpec);//高度测量模式

}

 

由此可见widthMeasureSpec, heightMeasureSpec并不仅仅是宽高的值,还对应了宽高的测量模式。
MeasureSpec是View内部的一个静态类,下面给出它的部分源码:

[代码]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

public static class MeasureSpec {

    private static final int MODE_SHIFT = 30;

    private static final int MODE_MASK = 0x3 << MODE_SHIFT;

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    public static final int EXACTLY = 1 << MODE_SHIFT;

    public static final int AT_MOST = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size, int mode) {

        if (sUseBrokenMakeMeasureSpec) {

        return size + mode;

        }   else {

            return (size & ~MODE_MASK) | (mode &   MODE_MASK);

        }

    }

}

 

public static int getMode(int measureSpec) {

    return (measureSpec & MODE_MASK);

}

 

public static int getSize(int measureSpec) {

    return (measureSpec & ~MODE_MASK);

}

 

...

}

 

可以看出MeasureSpec代表一个32int值,高2位代表测量模式SpecMode,低30位代表测量值SpecSize。拥有3种测量模式,分别为UNSPECIFIEDEXACTLYAT_MOST

测量类型

对应数值

描述

UNSPECIFIED

0

父容器不对 view 有任何限制,要多大给多大

EXACTLY

1

父容器已经检测出 view 所需要的大小,比如固定大小xxdp

AT_MOST

2

父容器指定了一个大小, view 的大小不能大于这个值

3、onLayout

用于确定View以及其子View的布局位置,在ViewGroup中,当位置被确定后,它在onLayout中会遍历所有的child并调用其layout,然后layout内部会再调用child的onLayout确定child View的布局位置。
layout方法如下:

[代码]java代码:

?

1

2

3

4

5

6

7

8

9

public void layout(int l, int t, int r, int b) {

    ...

    int oldL = mLeft;

    int oldT = mTop;

    int oldB = mBottom;

    int oldR = mRight;

    ...

 

}

 

mLeft, mTop, mBottom, mRight四个参数分别通过getLeft(),getTop(),getRight(),getBottom()四个函数获得。这一组old值会在位置改变时,调用onLayoutChange时使用到。

4、onSizeChanged

如其名,在View大小改变时调用此函数,用于确定View的大小。至于View大小为什么会改变,因为View的大小不仅由本身确定,同时还受父View的影响。

[代码]java代码:

?

1

2

3

4

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

    super.onSizeChanged(w,   h, oldw, oldh);

}

 

这里的wh就是确定后的宽高值,如果查看View中的onLayoutChange也会看到类似的情况,拥有l, t, r, b, oldL, oldT, oldR, oldB,新旧两组参数。

5、onDraw

onDraw是View的绘制部分,给了我们一张空白的画布,使用Canvas进行绘制。也是后面几篇文章所要分享的内容。

[代码]java代码:

?

1

2

3

4

@Override

protected void onDraw(Canvas   canvas) {

    super.onDraw(canvas);

}

 

6、其他方法以及监听回调

如onTouchEvent、invalidate、requestLayout、setOnTouchListener等方法。
onTouchEvent用于处理传递到的View手势事件。

[代码]java代码:

?

1

2

3

4

@Override

public boolean onTouchEvent(MotionEvent   event) {

    return super.onTouchEvent(event);

}

 

当返回true时,说明该View消耗了触摸事件,后续的触摸事件也由它来进行处理。返回false时,说明该View对触摸事件不感兴趣,事件继续传递下去。
触屏事件类型被封装在MotionEvent中,MotionEvent提供了很多类型的事件,主要关心如下几种类型:

事件类型

描述

ACTION_DOWN

手指按下

ACTION_MOVE

手指移动

ACTION_UP

手指抬起

事件效果如下:

在MotionEvent中有两组可以获得触摸位置的函数

[代码]java代码:

?

1

2

3

4

event.getX(); //触摸点相对于View坐标系的X坐标

event.getY(); //触摸点相对于View坐标系的Y坐标

event.getRawX(); //触摸点相对于屏幕坐标系的X坐标

event.getRawY(); //触摸点相对于屏幕坐标系的Y坐标


onWindowFocusChanged
运行于onMeasure与onLayout之后,可以获取到正确的width、height、top、left等属性值。

三、小结

简单分析了自定义View的入门准备知识,包括屏幕坐标系、View坐标、View的绘制过程中的主要函数、以及屏幕触摸事件。后面的内容将会围绕onDraw函数展开,在完成涉及知识点的分析之后,将会实战去编写PieView的代码。

原文链接:http://www.apkbus.com/blog-705730-61796.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消