自定义View基础及常用示例(一)

自定义View基础及常用示例(一)

  1. View类的结构
  2. View和ViewGroup绘制流程
  3. View和ViewGroup的事件分发
  4. Activity顶层View:DecorView的布局流程(Window)
  5. 实例

1. View类的结构

Google开发者指南:
View类是用户界面组件的基本构建块。一个View 组件占据了
的矩形区域,并屏幕上负责绘制和事件处理。以View是创建交互式UI组件的widgets的基类如按钮、文本字段,等等。ViewGroup
子类是布局的基础,对外界不可见的持有其他View(或其他ViewGroups容器)和定义他们的布局属性

1.1 View 和ViewGroup的组合模式

View 及其子类 ViewGroup的通过组合模式构成了Android控件以及布局,View的主要职责是负责绘制和处理事件,ViewGroup继承了View以后可以在其内部持有View或ViewGroup,同时可以分发事件至内部View或者ViewGroup。

这里写图片描述

1.2 构造函数

自定义 View 的三(四 API 21)构造函数:
一个参数:代码中手动创建
两个参数:在布局文件中声明时会调用,同时传递属性值
三个参数:在两个参数构造函数基础上增加了样式属性,在布局文件中传递样式属性可以被调用
四个参数:API 21新添加的可以多选资源style中的样式

// Simple constructor to use when creating a view from code.
public View(Context context)

// Constructor that is called when inflating a view from XML.
public View(Context context, @Nullable AttributeSet attrs)

// Perform inflation from XML and apply a class-specific base style from a theme attribute.
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)

// Perform inflation from XML and apply a class-specific base style from a theme attribute or style resource.
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

1.3 坐标系

android手机的坐标系是左手系,即以手机的左上角为原点,向左为x轴,向下为y轴。对于View来说其所在的位置实际是相对于其父布局的参考系来说的

获取视图的位置:

// 在屏幕中的位置
public void getLocationOnScreen(int[] location)
// 在当前窗体的位置
public void getLocationInWindow(int[] location) 
 // The visual x position of this view, in pixels. This is equivalent to the translationX property plus the current left property.
 // 获取到View实际显示的在父布局的位置(包括使用属性动画造成的移动)
 public float getY()
 public float getX()
// 返回未经处理的X/Y 坐标,即在屏幕中的坐标
getRawX()
getRawY()

1.4 重要的方法及回调

requestLayout()

View或ViewGroup中Layout实际上是布局自己在父布局中的位置,而这个位置是父布局通过Measure测量出来的,通过这个测量的位置由父布局来定位子视图或子ViewGroup,所以一个视图想要重新定位自己则需要传递给父布局来处理,这样就必须通过ViewParent这个接口来处理了

requestLayout()//通过 ViewParent这个接口让父布局重新布局自己

ViewParent

ViewParent 是一个子布局或视图和父布局通信的一个接口,只有ViewGroup实现了这个接口并将自己作为ViewParent设置到了其子布局或View中了,子布局或View通过requestLayout()请求父类重新布局自己的位置

//ViewGroup实现了ViewParent 这个接口
public abstract class ViewGroup extends View implements ViewParent, ViewManager

onSizeChanged

视图在布局过程中会调用和这个函数,第一次调用时oldw和oldh会返回0(一种说法是:测量出来的结果不一定是实际布局的值,因为受到父视图的影响会多次测量,所以在onSizeChanged()方法中获取到的则是实际的布局值)

This is called during layout when the size of this view has changed. If you were just added to the view hierarchy, you’re called with the old values of

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

invalidate

需要重新绘制视图需要使当前的显示无效,然后重新绘制,会调用ondraw(),如果视图大小没有发生变化则不会调用layout过程

Invalidate the whole view. If the view is visible

public void invalidate()//使当前视图失效,会调用ondraw()

requestFocus()

需要主动使得视图获取到焦点,如EditText

//获取焦点给这个View或者其内部的一个View,前提是isFocusable可获得焦点同时touch mode中isFocusableInTouchMode()是FOCUSABLE_IN_TOUCH_MODE
public final boolean requestFocus() {

setMeasuredDimension()

实际保存Measure测量过后的值

2. View和ViewGroup绘制流程

View的完整绘制包括 layout和drawing,通过测量,布局,渲染等步骤将定义好的视图绘制到屏幕上

这里写图片描述

2.1 layout流程

layout流程包括两个步骤:测量(measure)和布局(layout),使用requestLayout()方法则会触发layout流程重新初始化。

measure()

自上而下以树的遍历的方式遍历整个视图,测量子视图的位置,在遍历完成以后会保存测量值,当measure()执行完毕以后getMeasuredHight()一定会被设置,子视图的测量值一定是受父视图的约束的,同时父视图可能不止一次调用measure(),如所有子视图的总测量值太大或者太小

public **final** void measure(int widthMeasureSpec, int heightMeasureSpec) 

如果是ViewGroup的子类这必须覆盖这个方法,实现自己的测量

The actual measurement work of a view is performed in onMeasure(int, int)
在测量过程中实际的操作是onMeasure(int, int)中实现的,onMeasure()必须调用setMeasuredDimension()来保存测量的信息

// This method **must** be called by onMeasure(int, int) to store the measured width and measured height
setMeasuredDimension()

MeasureSpec

MeasureSpec 是View中的一个内部类,用来父视图和子视图之间传递的布局需求的一个封装,用32位的int表示,在32位二进制位中,31-30这两位表示测量模式 29~0 这三十位表示宽和高的实际值,其内部包含了宽或高的信息以及三种布局模式

模式解释
UNSPECIFIED未指定,此视图的宽高无限制
EXACTLY父视图指定了宽高
AT_MOST没有具体指定,但不能超过父布局
// 获取到实际的值
MeasureSpec.getSize(MeasureSpec measureSpec)
// 获取到指定的模式
MeasureSpec.getMode(MeasureSpec measureSpec)

layout()

这个方法是在View中的,不建议子类覆盖这个方法,如果有子视图则需要继承ViewGroup抽象类,并实现onLayout()来布局内部的子视图。在View中onLayout()是一个空实现,在ViewGroup中才会要求手动实现具体的布局代码
同样是自上而下地遍历视图树,在这个过程中负责根据值测量大小和位置设置子视图
Assign a size and position to a view and all of its descendants

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

2.2 Drawing流程

在完全执行完layout流程以后就会执行drawing绘制流程,draw(canvas)是在给定的canvas画板上渲染这个view。不建议子类直接覆盖这个方法,如果需要控制渲染,覆盖onDraw()方法可以达到相同目的

Draw()

渲染的步骤
Draw traversal performs several drawing steps which must be executed in the appropriate order:
1. Draw the background
2. If necessary, save the canvas’ layers to prepare for fading Draw view’s content
4. Draw children
5. If necessary, draw the fading edges and restore layers
6. Draw decorations (scrollbars for instance)

//Manually render this view (and all of its children) to the given Canvas.
public void draw(Canvas canvas)

onDraw()

同onMeasure()和onLayout()一样,如果是ViewGroup的子类就需要自己实现onDraw绘制过程

//Implement this to do your drawing.
protected void onDraw(Canvas canvas) 

dispatchDraw()

在ViewGroup中实现了分发渲染的方法,会调依次用子类的draw方法

protected void dispatchDraw(Canvas canvas) 

下节:

  1. View和ViewGroup的事件分发
  2. Activity顶层View:DecorView的布局流程(Window)
  3. 实例
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值