View From
View 分类
单一视图 如 TextView
视图组 如 ViewGroup, LinearLayout
ViewGroup 继承自 View
View 构造函数
分为两大类
//如果是在 Java 代码里面 new 的
private CarsonView(Context context){
super(context);
}
// 如果是在.xml里声明的
// 自定义属性由 attrs 传递
private CarsonView(Context context, Attribute attrs){
super(context, attrs);
}
// 在第二种情况下,
// 当 view 包含 style 属性
private CarsonView(Context context, Attribute attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
// API21后
// 在第二种情况下,
// 当 view 包含 style 属性时
private CarsonView(Context context, Attribute attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
}
attrs XML中的属性
defStyleAttr 应用到 View 的默认风格(定义在主题中)
defStyleRes 如果没有使用 defStyleAttr , 应用到View的默认风格
View 视图结构
无论是 measure, layout 还是 draw, 永远都是从根View开始测量/计算, 最终确定整个View的属性
View 坐标系
top, left, right, bottom 属性都是相对父容器的
View 获取位置
view.getLeft() 获取view左上角到父容器左侧的距离
-
MotionEvent中
触摸点相对于其所在坐标系中的距离e.getX() e.getY()
-
触摸点相对于屏幕的距离
e.getRawX(); e.getRawY();
Android 中角度和弧度
角度的增大方向为顺时针
自定义View From
1. Measure
1.1 方法
- measure()
final 类型, 无法重写, 最终会调用 onMeasure()- onMeasure()
自己重写-
setMeasureDimension()
存储测量后的宽高- setMeasuredDimensionRaw()
设置 mMeasuredWidth 和 mMeasuredHeight
- setMeasuredDimensionRaw()
getDefaultSize()
根据测量规格的模式, 给出测量值
-
- onMeasure()
1.2 测量过程
一个view的值由 自身的LayoutParams 和 父布局的 MeasureSpec 决定 (DecorView除外, 它没有父布局, 由自身的 LayoutParams 和 窗口尺寸决定)
2. Layout
2.1方法
- layout();
确定 View 本身的位置 - onLayout;
单一 View 空实现,
ViewGroup 调用 onLayout 遍历子View 并调用子View layout()确定自身子View 的位置
ViewGroup中 因为子view的确定位置与布局有关, 需重写
2.2 layout 过程
2.3 getWidth/getHeight 和 getMeasureWidth/getMeasureHeight 差别
- 定义
- getWidth/getHeight view最终的宽高
- getMeasureWidth/getMeasureHeight view的测量的宽高
-
代码
// View的测量的宽 / 高: public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; // measure 过程中返回的 mMeasuredWidth } public final int getMeasuredHeight() { return mMeasuredHeight & MEASURED_SIZE_MASK; // measure 过程中返回的 mMeasuredHeight } // View最终的宽 / 高 public final int getWidth() { return mRight - mLeft; // View最终的宽 = 子View的右边界 - 子view的左边界。 } public final int getHeight() { return mBottom - mTop; // View最终的高 = 子View的下边界 - 子view的上边界。 }
时机
- getMeasureWidth/getMeasureHeight
在 measure 的 setMeasureDimension()中setMeasureDimensionRaw 赋值, 在 onLayout 用它来获取宽高 - getWidth/getHeight
在 layout 的 layout()中赋值, 可以用于除 onLayout() 之外的地方获取宽高
- 结论
在onLayout中用getMeasureWidth/Height(), 在其他地方用getWidth/Height()
一般情况下两者的值是永远相等的,与View是否超出屏幕无关
,
除非人为设置通过重写View的layout()强行设置, @Override public void layout( int l , int t, int r , int b){ // 改变传入的顶点位置参数 super.layout(l,t,r+100,b+100) // 如此一来,在任何情况下,getWidth() ( getHeight())获得的宽 / 高总是比getMeasuredWidth() (getMeasuredHeight())获取的宽 (高)大100px // View的最终宽 / 高总是比测量宽 / 高大100px }
3. draw
3.1 方法
- draw()
绘制自身- drawBackground()
绘制自身View的背景 - onDraw()
绘制自身View的内容, 自定义View中必须且只需要重写onDraw - dispatchDraw()
单一View空实现 - onDrawScrollBar()
绘制装饰
- drawBackground()
3.2 draw 过程
setContentView
Activity 在 android 中的根布局被分为 标题栏 + ContentView(外面包着 FrameLayout), 所以我们平时操作的都是 ContentView, 布局都是 setContentView, 而不是setView
因为外部有父布局FrameLayout, 所以属性 layout_width 和 layout_height 里的layout都是有的, 设置能起作用的
而如果我们通过纯代码方式创建布局, 如下:
LinearLayout mainLayout = (LinearLayout)findViewById(R.layout.main_layout);
LayoutInflater layoutInflater = LayoutInflater.from(this);
View buttonView = layoutInflater.inflate(R.layout.button_layout, null);
mainLayout.addView(buttonView);
再给button_layout添加 layout_width 和 layout_height 属性
会发现无效, 因为button_layout没有父布局, 所以套一个布局就能让属性生效了
参考
Android LayoutInflater原理分析,带你一步步深入了解View(一)
深入理解Android View的构造函数
自定义View Measure过程 - 最易懂的自定义View原理系列(2)
自定义View Layout过程 - 最易懂的自定义View原理系列(3)
自定义View Draw过程- 最易懂的自定义View原理系列(4)
Android开发之getMeasuredWidth和getWidth区别从源码分析