Android 自定义View小结(重于理解,不作为教程)

感谢 郭神 的思路。

大家都知道 SetContextView是给Activity设置布局的, 但是内部还是用的LayoutInflater去实现的,关于LayoutInFlater的实例有两种方法获取得到。

    通过传入Contxt对象生成
    LayoutInflater layoutInflater = LayoutInflater.from(context); 

    第二种就是通过系统服务拿到
        LayoutInflater layoutInflater = (LayoutInflater) context  
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 

其实两种方法没有什么异同, 第一种是系统帮我们封装好了的,然后通过实例的方法inflate方法去加载布局。

其实LayoutInflater技术广泛应用于需要动态添加View的时候,通过addview方法, 添加某一个子View 比如在ScrollView和ListView中,经常都可以看到LayoutInflater的身影。
在Activity布局中, 最外层的其实是FrameLayout,这并不是我们自己去写的, 而是系统给我们

LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,它是于根据节点名来创建View对象的,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。

其实不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。

那么我相信到这里很多哥们都是心存疑虑的, 我们平常用的布局也是 不在任何布局当中阿, 其实不然,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果。就是说,系统自动的帮我们弄了一个FrameLayout布局放在最外面。

说到这里其实SetContentView()方法大家都会用,但是实际上Android 界面显示的原理要比我们所看到的东西复杂得多,其实任何一个Activity中显示的界面都是有两部分组成的,那就是标题栏和内容布局,标题栏就是在很多界面顶部显示的那部分内容,比如我们刚刚的那个例子当中就有标题栏, 可以在代码中控制让他是否显示, 内容布局就是一个FrameLayout,这个布局的id就叫做content,我们调用SetContentView所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名称叫做SetContentView(),而不是SetView()。

这里写图片描述

1.View的绘制流程

要知道,任何一个试图都不可能凭空突然出现在屏幕上,他们都是要经过非常科学的绘制流程后才能显示出来的,每一个视图的绘制过程都必须经历三个主要的阶段, 即onMeasure()、onLayout()和onDraw(),下面我们就逐个对这三个阶段展开探讨,

1.1 onMeasure()

Measure(测量) Measure是测量的意思, 在代码中也是来测量控件的大小,其onMeasure(int widthMeasureSpec, int heightMeasureSpec)是可以被重写的,如果你不喜欢系统给你画的,完全可以自己设置;
public class MyView extends View {  

    ......  

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

}  

而且需要注意的是, 在重写onMesure方法时,内部要调用setMeasuredDimension 方法之后,我们才可以调用getMeasuredWidth()和getMeasureHeight()来获取试图测量出的宽高,如果在此之前调用这两个方法得到的值都会是0.

1.2onLayout

测量完了具体的宽高,那么就该说说具体的控件位置了, onlayout的主要作用就是确定好控件的位置,而且这个方法还是抽象的,意思就是说, 我们必须要重写他, view的方法layout其中有四个参数,就是上下左右,然后在layout方法中会调用onlayout方法,然后判断是不是又子视图, 如果有的话就设置好位置,

其实这里有一个点相信大家不是很明白,

在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。说到这里,我相信很多朋友长久以来都会有一个疑问,getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。

首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

首先 getMesyreWidth()方法是在measure()过程结束之后就可以获取到了,而GetWidth()方法要再layout()过程结束后才可以获取得到, 另外, getMeasureWidth()方法中的值是通过setmeasuredDimension()方法来进行设置的, 而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

1.3 onDraw

有了宽高,有个位置,下面就是绘制了,正如同Draw英文意思一样,这主要是绘制,她会首先在VireRoot中的代码 创建出 Canvas 然后利用Canvas去设置一系列的操作,然后还有设置画笔 Paint,这一点不再简述, 因为太多了。。

大家已经知道,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西,所以想要实现复杂的动画,算法是不可少的,因为这些都是需要画布配合画笔画出来的。

2.View的状态切换

1.enabled
表示当前视图是否可用, 可以调用setEnable ()方法来改变视图的可用状态,传入true表示可用, 传入false表示不可用, 他们之间最大的区别就是, 不可用的视图是无法响应onTouch事件的,

2.forcused
表示当前视图是否获得到焦点, 通常情况下有两种方法可以让视图获得焦点, 即通过键盘的上下左右键切换视图, 以及调用requestFocus()方法,但是现在基本上没有带键盘的手机了, 因此只有一个请求焦点的方法了,而且requestfocus也不一定可以让视图获得焦点, 他会有一个布尔值的返回值, 如果返回true说明获取成功了, 返回false说明获得焦点失败, 一般只有视图在focusable和 focusable in touch mode 同时成立的情况下才能成功的获取焦点,比如说,Edittext。

3.window_focused
表示当前视图是否处于正在交互的窗口中,这个值由系统去自动决定, 应用程序不能进行改变。

  1. selected
    表示当前视图是否处于选中状态。一个界面当中可以有多个视图处于选中状态,调用setSelected()方法能够改变视图的选中状态,传入true表示选中,传入false表示未选中。

4.Selected
表示当前视图是否处于选中状态, 一个界面当中可以有多个视图处于选中状态, 调用setSelected()方法能够改变视图的选中状态, 传入True表示选中, 传入False表示未选中, 有一个状态选择器(selector 可以设置背景,根据是否处于pressed状态去动态改变)

5.pressed
表示当前视图是否处于按下状态,可以调用seetPressed()方法来对这一状态进行改变, 传入true表示按下, 传入flase表示未按下, 通常情况下这个状态都是由系统自动赋值的, 但是我们也可以自己调用这个方法来进行改变。

3.控件的自绘及组合、继承

3.1 控件的自绘

这个无需多讲, 其实控件的自绘,就是视图全部由我们自己去绘制, 最重要的就是onDraw方法,,调用invalidate()方法会导致视图进行重绘,因此onDraw()方法在稍后就将会得到调用,

3.2 组合控件

组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。

就是将原生的控件组合在一起,例如说一个按钮,一个textview,都在一个布局中,那么我就可以写一个布局,继承自FrameLayout,然后获得这两个按钮的实例(通过layoutInflate),将一些列的set/get方法暴露出去,下次的时候要用这两个控件,直接引用就好了。 例如说标题栏的自定义!

3.3继承控件

我的理解主要是, 你在某一个控件的基础上去增加功能,例如listveiw。

继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能,比如 Android PowerImageView实现,可以播放动画的强大ImageView 这篇文章中介绍的PowerImageView就是一个典型的继承控件。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值