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 嵌套滑动
实现接口,然后重写嵌套滑动事件逻辑