
onDraw 绘制
canvas 画布
paint 画笔
坐标系 x y
x 0 y 0 则屏幕左上角 y从上往下值增加
像素转换 dp2px


画线line
drawLine
圆circle
drawCircle

drawPath:

在onSizeChanged 时候初始化

addCircle 添加圆
CW顺时针
CCW 逆时针
CW CCW填充规则不同
填充规则:

默认 WINDING 填充 填满
EVEN_ODD 不管方向 相交点不填充

其他两个则是反方向的
path测量

画path虚线
paint.pathEffect = PathDashPathEffect()
onSizeChanged 尺寸改变时候调用
Xframe
离屏缓冲:单独拿出一块区域进行渲染

SOURSE 不仅包含图像还有底部(透明)区域

PorterDuff.Mode | Android Developers



var XFERMODE = PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
var bounds = RectF(150.dp,50.dp,300.dp,200.dp)
var paint = Paint(Paint.ANTI_ALIAS_FLAG)
var circleBitmap = Bitmap.createBitmap(150.dp.toInt(), 150.dp.toInt(),Bitmap.Config.ARGB_8888)
var squareBitmap = Bitmap.createBitmap(150.dp.toInt(), 150.dp.toInt(),Bitmap.Config.ARGB_8888)
init {
val canvas = Canvas(circleBitmap)
paint.color = Color.parseColor("#D81B60")
canvas.drawOval(50.dp,0.dp,150.dp,100.dp,paint)
paint.color = Color.parseColor("#2196F3")
canvas.setBitmap(squareBitmap)
canvas.drawRect(0.dp,50.dp,100.dp,150.dp,paint)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
val count = canvas.saveLayer(bounds,paint)
canvas.drawBitmap(circleBitmap,150.dp,50.dp,paint)
paint.xfermode = XFERMODE
canvas.drawBitmap(squareBitmap,150.dp,50.dp,paint)
paint.xfermode = null
canvas.restoreToCount(count)
}
文字测量:
typeface 设置 字体
![]()
baseLine


//设置居中
paint.style = Paint.Style.FILL
paint.getFontMetrics(fontMetrics)
canvas.drawText("abab",width / 2f,height / 2f - (fontMetrics.ascent + fontMetrics.descent) / 2f ,paint)
// paint.textAlign = Paint.Align.LEFT
// paint.getTextBounds("abab",0,"abab".length,bonds)
// canvas.drawText("abab",0f,-fontMetrics.ascent,paint)
paint.textAlign = Paint.Align.LEFT
paint.getTextBounds("abab",0,"abab".length,bonds)
canvas.drawText("abab",-bonds.left.toFloat(),-bonds.top.toFloat(),paint)
范围裁切和几何变换


方形,左上右下
变换View坐标系







坐标系放大


Camera 变换
未指定轴心:





指定轴心 移动坐标系



距离过近则图像大

后移


-8f = 8英寸 72像素 -8x72 576

camera 裁切

属性动画
animate

修改属性值
Object.Animation
单个View


tips 每次切换屏幕则执行onDraw
需要进行save 和 释放 : ondraw()

也可以使用 withSave

多个属性可以用AnimatorSet

扩展:PropertyValuesHolder

KeyFrame:

Interpolator:
加减速差值器

加速差值器
![]()
线性差值器

TypeEvaluator: 类型估值动画,每一个动画完成度的evaluator

初始值 + 动画完成度 * 剩余进度
自定义,不支持kt

ValueAnimator : 字符串动画:



硬件加速:
软绘:CPU ->软件绘制
硬绘:GPU绘制

硬件加速缺点是兼容性:

离屏缓冲 单独拿出一块区域 进行渲染 通过saveLayer开启,建议用hardware Layer

需要再onDraw方法外,执行会接着重绘
开启离屏缓冲

动画过程中临时开启硬件加速,离屏缓冲,自定义ObjectAnima不能使用,需要系统自带的动画

Bitmap / Drawable
互转


ktx 互转

源码:


bitmap 位图,像素数据 ,存储像素数据

drawable 绘制工具
默认是0 0,需要指定位置

drawble 将bitmap进行draw方法

自定义drawable



SDK 覆盖很多drawable 基本常用场景都有

自动使用起点 0f
obtainStyledAttributes int 数组,把不属于这个view的属性过滤

然后根据序列号取值

自定义写法:

布局流程:
确定子View相对于父View的位置

流程

可能会测量多次

1.第一次测量

2.第二次测量
多次测量,不确定大小


取最大的

流程2

LinearLayout 不会修正子view内部的测量绘制方法,其他viewGroup 有的会修正位置比如ConstraintLayout
在子onLayout时候修改位置尺寸,则父View不会修改
view 会这只l t r b 位置,进行保存
自定义尺寸:
子view的layout是父view传过来的宽高尺寸信息

getWidth getHeight 父View测量的尺寸 = r-l b-t

计算自己测量的尺寸,然后保存结果传给父view
子View修正宽高
// //修正 宽度
// val speceMode = MeasureSpec.getMode(widthMeasureSpec)
// val speceSize = MeasureSpec.getSize(widthMeasureSpec)
// val width = when(speceMode){
// MeasureSpec.EXACTLY -> speceSize
// MeasureSpec.AT_MOST -> if (size > speceSize) speceSize else size
// MeasureSpec.UNSPECIFIED -> size
// }
//
// //修正高度
// val speceHeightMode = MeasureSpec.getMode(heightMeasureSpec)
// val speceHeightSize = MeasureSpec.getSize(heightMeasureSpec)
// val height = when(speceHeightMode){
// MeasureSpec.EXACTLY -> speceHeightSize
// MeasureSpec.AT_MOST -> if (size > speceHeightSize) speceHeightSize else size
// MeasureSpec.UNSPECIFIED -> size
// }
//等同于 resolveSize resolveSizeAndState
resolveSizeAndState 会传一个SMALL 传给父view 重新测量
但是一般父view不会读to small

resolveSize
val width = resolveSize(size,widthMeasureSpec) val height = resolveSize(size,heightMeasureSpec) setMeasuredDimension(width, height)
自定义Layout

取layoutparams会得到marginLayoutParams


需要重写方法

View 绘制流程
子线程更新UI
onCreate 不会报错

点击事件

不会报错,如果用wrap_content 则会报错
如果调用requestLayout 则不会报错

不会报错
报错则会触发checkThread

所有的view都会往上调用requestLayout,传递,然后触发检查机制

onResume之后可能也没完成测绘流程
Activity 包含一个mWindow对象,mWindow在
Activity的OnCreate在ActivityThread中的 - handleCreateActivity , onStart ,onResume 也是在类似的方法调用
Activity对象创建也是在ActivityThread中创建


然后调用


通过classLoader 实例化Activity
创建出然后调用attch

然后调用onCreate



attch方法之后调用onCreate
然后创建mWindow = PhoneWindow

setContentView 则是在调用window.setContentView

phoneWindow 包含Decor


关联

PhoneWindow -- localFeatures特征

![]()
写在setContentView前调用,设置window属性
替换不同的布局 NO_TITLE =

布局



源码findViewById 则会调用decorview的findViewByid


installDecor

创建Decor 然后添加contentView

创建布局然后添加到Parent


ViewRootImpl

子线程更新UI报错栈

如果Partnet = null 则不会触发checkThread

mThread


创建ViewRootImpl 时 创建Thread
ViewRootImpl由
创建
Activity CallActivityResume


windowmanager 是个接口


windowmangerglobal 是个单例对象






然后找到viewrootimpl

SurfaceView则不会执行这样的流程



触摸反馈:
true 消费事件 不在传递
点击事件

TooLTP 辅助事件

down 设置longclick 延时器

setProssed 设置为按下按下状态
每次滑动都会等待一定时间

500ms = longclick,TapTime = 100
调用先后关系



View 多点触控

单点触控 拖拽View,记录当前按下的位置和偏移量,下一次移动时加上偏移量= 现在按下的位置

触摸事件 是针对view的 每次事件是个序列,会有id,index x , y
getX 获取的是序号0,第一根手指的X
![]()
源码:

通过getX(index) 获取指定手指的坐标系
获取当前按下手指的坐标
双指移动view 判断是哪根手指 然后进行取值

配合型 计算焦点和差值

ViewGroup 触控:

mesaureChildren 给所有的子View一个统一的宽和高

遍历子view 指定位置 从左上角开始设置为填满

当自定义的ViewGroup是个滑动控件,如果拦截子View,返回true之外,还需要通知父View不再拦截
需要自己处理

如果滑动内层view 父view会收到拦截事件,子View会收到Touchevnent
外层则子View收不到消息

scrollTo View的方法,往上则会是负向滑动,是反方向设置

velocityTancker 速度记录器,惯性滑动

滑动速度变量,最小/最大/滑动速度,先初始化,然后clear

然后做计算,参数为单位和上限,1000则表示每秒移动的速度



给定一个位置,然后让其内部计算,然后最终值为0,切换更线性

通过scrollTo和postAnimation 会调用draw方法

滑动startScroll 和postInvalidateOnAnimation 成对调用

mesaureChildren 测量子View,规定其大小
拖拽:

onDragListener
开始拖拽 startDrag,data,只有等松手时才能取到
拖拽监听器

布局加载完成,每个view都设置监听器,拖拽其中一个,其他的都会收到回调

外接式拖拽监听:跟onDragListener不相关

dragCallback:

当View被拖拽时 ,显示在上方

当View坐标改变

当View放下

完成滑动后自动计算并且重排


startDragAndDrop,判断支持哪种方式进行拖拽


设置监听器:
![]()
![]()
回调

支持跨进程回调,松手时数据才会被调用

上下滑动拖拽





ScrollView 嵌套滑动




实现接口,然后重写嵌套滑动事件逻辑



















