Android自定义View系列之理论基础知识

目录

一、View是怎么来的?

二、坐标系(屏幕坐标系和布局坐标系)

三、View的绘制流程

四、自定义View的实现方式分类

五、自定义View的构造函数

六、自定义View的自定义属性

七、总结

一、View是怎么来的?

做Android移动端开发,应该都接触过这个类:LayoutInflater,对我们所有的View都是都是通过这个类吹出来的,inflate就是“鼓吹”的意思。

三个inflate方法提供给外界调用,最终都会走到三参数的inflate方法。(第一个参数就是xml资源文件)

关于LayoutInflater这个类后面会出文章详细讲解。

二、坐标系(屏幕坐标系和布局坐标系)

在Android坐标系中,以屏幕左上角作为原点,这个原点向右是X轴的正轴,向下是Y轴正轴,所以开发中调用方法的时候就有了以屏幕上边和左边为参考值,有了参考值你才好定位呀。

除了上面说的Android坐标系,到了View布局的时候有布局坐标系(也称View坐标系,但这种坐标系是相对的,因为Android中的View/ViewGroup是嵌套存在),布局坐标的关系如下图

上图中最里层是一个View(如TextView/Button/ImageView...),里二层是一个ViewGroup(如FrameLayout/LinearLayout/RelativeLayout...),最外层当前也是个ViewGroup(可以是你自己写的layout的root布局,也可以是系统底层的FrameLayout,这个不重要,我们来分析里面的两层),由上图可以看出:

1、getTop()、getLeft()、getBottom()、getRight()得到的值的含义

我们平时调用的getTop()、getLeft()、getBottom()、getRight()就是获取View在布局坐标系中的位置值,这个值是相对于View父布局而言的,因为布局坐标系本来就是相对的。

所以通过如下方法可以获取View到其父控件的距离。

  • getTop();获取View顶部到其父布局顶边的距离。
  • getLeft();获取View左边到其父布局左边的距离。
  • getBottom();获取View底部到其父布局顶边的距离。
  • getRight();获取View右边到其父布局左边的距离。

实测证明上述结论:

所以就可算出View的高度:

  • width = getRight() - getLeft();
  • height = getBottom() - getTop();

View的源码当中提供了getWidth()和getHeight()方法用来获取View的宽度和高度,其内部方法和上文所示是相同的,我们可以直接调用来获取View得宽高。

2、getX()和getY()     ||     getRawX()和getRawY()得到的值的含义

getX()和getY()是触屏到某个View时候调用的方法,代表触碰点到View的左边和顶部的距离;getRawX()和getRawY()也是触屏到某个View时候调用的方法,但获取到的距离是相对屏幕Android坐标系的X和Y轴而言的,所以代表触碰点到屏幕左边和顶部的距离。(这个验证要在View的onTouchEvent里面验证)

三、View的绘制流程

我们平时在手机上看到的花花绿绿的图片、文字等等是如何形成的,为什么规规矩矩的、整整齐齐的、花花绿绿的放在了屏幕上?

View的绘制基本由规规矩矩的measure()、整整齐齐的layout()、花花绿绿的draw()这个三个函数完成。

形同生活中建房子的例子:先规规矩矩的测量每个房间的大小,然后整整齐齐的将刚刚测量好大小的房间摆放布局好,最后就是装修每个测量好并摆放好的房间,这样一个完整的房子就建设好了,这其中的顺序就是measure()----->layout()---->draw()。

其中measure()和layout()就需要设计到基础知识1中的坐标系的相关知识,draw()就涉及到paint、canvas、matrix、rect、clip、line......

四、自定义View的实现方式分类

做过Android移动端开发的都知道,View是左右布局的最底层最底层的一个类,所有的布局都是继承View的(就包括ViewGroup,只是ViewGroup是组件容器的基础类),但是我们经常用到的TextView、ImageView、LinearLayout...都是系统继承View(也叫系统自定义View)对外提供的一些基础常用的组件。

另外要知道,一般而言:

在自定义View中常需要measure测量和Draw绘制就行(因为作为单独的个体,我只需要对自己大小、美丽负责就好,我不需要考虑layout自己应该放在哪个位置,应该考虑这个事的是要装我这个自定义View的父容器,特殊情况除外:)

在自定义ViewGroup中常需要measure测量和layout布局就行(因为作为一个容器,我要考虑自己大小和我装的每个子View应该如何摆放位置)

有了这些概念就好分类:后面实战篇每个分类写个Demo

五、自定义View的构造函数

无论是上面那种分类的自定义View,都需要对构造函数进行重写,构造函数有多个,至少要重写其中一个才行,否则会编译都通不过:

public class TestView extends View {
    /**
     * 使用场景:在java代码里new的时候会用到
     */
    public TestView(Context context) {
        super(context);
    }

    /**
     * 使用场景:在xml布局文件中使用时自动调用
     */
    public TestView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 不会自动调用,如果有默认style时,在第二个构造函数中调用
     */
    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
        super(context, attrs, defStyleAttr);
    }


    /**
     * 只有在API版本>21时才会用到
     * 不会自动调用,如果有默认style时,在第二个构造函数中调用
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
}

关于四个构造函数的解释:

1.在代码中直接new一个Custom View实例的时候,会调用第一个构造函数;

2.在xml布局文件中调用Custom View的时候,会自动调用第二个构造函数,你在xml中的那些属性都会在第二个参数中attrs;

3.这个方法就不会存在主动调用的时候,在xml布局文件中调用Custom View,并且Custom View标签中还有自定义属性时,这里调用的还是第二个构造函数,当有defStyleAttr或者defStyleRes,可以在第二个构造方法中手动调用传值:this(context, attrs, R.attr.listViewStyle);  这是ListView的构造方法

结论:在我们一般的自定义View中,写前两个或者前三个就行。(还有一个4个参数的构造方法)

一般初始化工作放在构造方法中进行,写法也有两种:

方式一:多个构造方法调用公共初始化方法

方式二:级联式调用构造方法

第二种方式简单明了,第一种方式安全(因为当你自定义View继承ListView的时候你会发现,他的第三个构造方法第三个参数是有defStyleAttr的,用第二种方法就覆盖了这个参数传0导致出现问题)

注意:自定义View的构造方法有4个,关于第三个参数和第四个参数的介绍和使用可以参考这篇详细:https://blog.csdn.net/wzy_1988/article/details/49619773

六、自定义View的自定义属性

Android系统的控件以android开头的都是系统自带的属性。为了方便配置自定义View的属性,我们也可以自定义属性值。
Android自定义属性可分为以下几步:

  1. 自定义一个View
  2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
  3. 在布局文件中View使用自定义的属性(注意namespace命名空间)
  4. 在View的构造方法中通过TypedArray获取

第二步示例步骤:

第三步示例步骤:

第四步java代码:

public class TestView extends View {
    public TestView(Context context) {
        this(context,null);
    }

    public TestView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

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

    //这里演示自定义属性,所以带上了attrs参数,因为在xml写的属性值都在这个参数里面
    private void initView(Context context,AttributeSet attrs) {
        //将自定义属性里面的各个属性转成TypedArray,其实是个数组保存了各个属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.Test);
        //获取自定义属性的值
        String text = typedArray.getString(R.styleable.Test_text);
        int textSize = typedArray.getInteger(R.styleable.Test_textSize,0);
        Log.e("Test", "text = " + text + " , textAttr = " + textSize);
        //回收使用过的资源
        typedArray.recycle();
    }
}

运行结果:

总结:有了自定义属性,你就可以随便在xml中配置自己要的值,在java代码中获取并使用。

介绍一下第二步中自定义属性有哪些类型format,我们可以设置哪些类型的自定义属性:

(1). reference:参考某一资源ID
属性定义:
<declare-styleable name = "名称">
     <attr name = "background" format = "reference" />
</declare-styleable>
属性使用:
<ImageView custom:background = "@drawable/图片ID"/>

(2). color:颜色值
属性定义:
<attr name = "textColor" format = "color" />
属性使用:
<TextView custom:textColor = "#00FF00" />

(3). boolean:布尔值
属性定义:
<attr name = "focusable" format = "boolean" />
属性使用:
<Button custom:focusable = "true"/>

(4). dimension:尺寸值
属性定义:
<attr name = "layout_width" format = "dimension" />
属性使用:
<Button custom:layout_width = "42dip"/>

(5). float:浮点值
属性定义:
<attr name = "fromAlpha" format = "float" />
属性使用:
<alpha custom:fromAlpha = "1.0"/>

(6). integer:整型值
属性定义:
<attr name = "framesCount" format="integer" />
属性使用:
<animated-rotate custom:framesCount = "12"/>

(7). string:字符串
属性定义:
<attr name = "text" format = "string" />
属性使用:
<TextView custom:text = "我是文本"/>

(8). fraction:百分数
属性定义:
<attr name = "pivotX" format = "fraction" />
属性使用:
<rotate custom:pivotX = "200%"/>

(9). enum:枚举值
属性定义:
<declare-styleable name="名称">
    <attr name="orientation">
        <enum name="horizontal" value="0" />
        <enum name="vertical" value="1" />
    </attr>
</declare-styleable>
属性使用:
<LinearLayout  
    custom:orientation = "vertical">
</LinearLayout>
注意:枚举类型的属性在使用的过程中只能同时使用其中一个,不能 android:orientation = “horizontal|vertical"

(10). flag:位或运算
属性定义:
<declare-styleable name="名称">
    <attr name="gravity">
            <flag name="top" value="0x01" />
            <flag name="bottom" value="0x02" />
            <flag name="left" value="0x04" />
            <flag name="right" value="0x08" />
            <flag name="center_vertical" value="0x16" />
            ...
    </attr>
</declare-styleable>
属性使用:
<TextView custom:gravity="bottom|left"/>
注意:位运算类型的属性在使用的过程中可以使用多个值

(11). 混合类型:属性定义时可以指定多种类型值
属性定义:
<declare-styleable name = "名称">
     <attr name = "background" format = "reference|color" />
</declare-styleable>
属性使用:
<ImageView custom:background = "@drawable/图片ID" />
或者:
<ImageView custom:background = "#00FF00" />

七、总结

有了上面的基础知识:手机坐标系、自定义View的构造方法、自定义View的自定义属性,我们就可以进入下一篇文章深入了解View绘制流程和实战自定义View(继承原始View、继承原始ViewGroup、继承系统TextView拓展、继承系统现有LinearLayout拓展)

猜你喜欢

转载自blog.csdn.net/sunbinkang/article/details/112003228