kotlin語法總結(jié)
硬件加速
概述
GPU的英文全名為 graphics processing unit 中文翻譯為 圖形處理器 悠瞬。 與CPU不同 ,GPU是專門為處理圖形任務(wù)而產(chǎn)生的芯片
軟件繪制與硬件加速的區(qū)別
CPU繪制流程
1.讓View層次結(jié)構(gòu)失效
2.繪制View層次結(jié)構(gòu)
硬件加速模式下的GPU繪制流程
1.讓View層次結(jié)構(gòu)失效
2.記錄、更新顯示列表
3.繪制View層次結(jié)構(gòu)
在GPU加速時(shí),流程中多了一步,表示在第一步View層次結(jié)構(gòu)失效后,并不是直接開始逐層繪制的摸吠,而是首先把這些View的繪制函數(shù)作為繪制指令記錄在一個(gè)顯示列表中,然后在讀取顯示列表中的繪制指令,調(diào)用OpenGL的相關(guān)函數(shù)完成實(shí)際繪制胸墙。 也就是說,在GPU加速時(shí)按咒,實(shí)際上是使用OpenGL的相關(guān)函數(shù)來完成繪制的
GPU加速優(yōu)點(diǎn):
提高了android系統(tǒng)顯示和刷新的速度
缺點(diǎn):
1.兼容性問題迟隅。由于是將繪制函數(shù)轉(zhuǎn)換成OpenGL指令來繪制的,所以必然會(huì)存在PpenGL并不能完全支持原始繪制函數(shù)的問題励七,從而造成在打開GPU加速時(shí)效果會(huì)失效的問題
2.內(nèi)存消耗問題智袭。 需要把OpenGL相關(guān)的包加載到內(nèi)存中來
3.電量消耗問題 會(huì)更耗電
禁用GPU硬件加速的方法
可以根據(jù)不同的類型 實(shí)現(xiàn)禁用方法
1.Application 在清單文件中為application 添加屬性 android:hardwareAccelerated="true/false" 開啟或關(guān)閉
2.Activity 在清單文件中危activity添加屬性 android:hardwareAccelerated="true/false" 開啟或關(guān)閉
3.Window 只支持開啟
window.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
4.View 只支持關(guān)閉
在代碼中關(guān)閉
setLayerType(LAYER_TYPE_SOFTWARE,null)
在布局文件中關(guān)閉
android:layerType="software"
文字
drawtext 相關(guān)四格線與基線 、 textAlign 與 FontMetrics
https://blog.csdn.net/qq_27981847/article/details/50748041
Paint 相關(guān)函數(shù)
void reset() //重置畫筆
void setColor(@ColorInt int color) // 設(shè)置畫筆顏色
void setAlpha(int a) // 設(shè)置畫筆透明度
void setStyle(Style style) // 設(shè)置畫筆樣式
void setStrokeWidth(float width) // 設(shè)置畫筆寬度
void setAntiAlias(boolean aa) // 設(shè)置畫筆是否抗鋸齒
void setStrokeMiter(float miter) //設(shè)置畫筆傾斜度
PathEffect setPathEffect(PathEffect effect) //設(shè)置路徑樣式
void setStrokeCap(Cap cap) // 設(shè)置線帽子=樣式
void setDither(boolean dither) // 設(shè)置抗抖動(dòng)效果
float measureText(String text) //計(jì)算text所需要的width
void getTextBounds(@NonNull CharSequence text, int start, int end, @NonNull Rect bounds) //計(jì)算text所需要的最小矩形 并賦值到第三個(gè)參數(shù)
Paint的setStrokeCap掠抬、setStrokeJoin吼野、setPathEffect
貝濟(jì)埃曲線
http://www.reibang.com/p/1af5c3655fa3
path 有四個(gè)函數(shù)與貝濟(jì)埃曲線相關(guān)
二階貝濟(jì)埃曲線
void quadTo(float x1, float y1, float x2, float y2)
void rQuadTo(float dx1, float dy1, float dx2, float dy2)
三階貝濟(jì)埃曲線
void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
這里以quadTo函數(shù)為例 x1,y1 是控制點(diǎn)坐標(biāo) x2,y2是終點(diǎn)坐標(biāo)
整條線的起點(diǎn)是通過moveTo函數(shù)來指定的 如果連續(xù)調(diào)用quadTo函數(shù)那么上一個(gè)函數(shù)的終點(diǎn)就是下一個(gè)函數(shù)的起點(diǎn)
如果沒有調(diào)用moveTo 默認(rèn)起點(diǎn)為左上角(0,0)
var path = Path()
path.moveTo(100f,300f)
path.quadTo(200f,200f,300f,300f)
path.quadTo(400f,400f,500f,300f)
canvas?.drawPath(path,paint)
示例 傳統(tǒng)捕捉手勢(shì)軌跡
要實(shí)現(xiàn)手勢(shì)軌跡我們可以攔截onTouchEvent 然后使用Path.lineTo 函數(shù)即可
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
path.moveTo(event.x , event.y)
return true
}
MotionEvent.ACTION_MOVE -> {
path.lineTo(event.x , event.y)
postInvalidate()
return true
}
}
return super.onTouchEvent(event)
}
override fun draw(canvas: Canvas?) {
super.draw(canvas)
canvas?.drawPath(path,paint)
}
path.lineTo 最大的問題就是線段轉(zhuǎn)折處不夠平滑 把圖像放大兩點(diǎn)連接處會(huì)有很明顯的轉(zhuǎn)折
使用quadTo 實(shí)現(xiàn) 修改draw方法即可
var preX = 0f
var preY = 0f
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
path.moveTo(event.x , event.y)
preX = event.x
preY = event.y
return true
}
MotionEvent.ACTION_MOVE -> {
// path.lineTo(event.x , event.y)
var endX = (preX + event.x)/2
var endY = (preY + event.y)/2
path.quadTo(endX,endY , event.x , event.y)
preX = event.x
preY = event.y
postInvalidate()
return true
}
}
return super.onTouchEvent(event)
}
void rQuadTo(float dx1, float dy1, float dx2, float dy2)
rQuadTo 的四個(gè)參數(shù)都是針對(duì)上一個(gè)終點(diǎn)的坐標(biāo) 正值表示相加 負(fù)值表示相減
陰影效果
需要關(guān)閉硬件加速
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var paint = Paint()
paint.color = Color.BLACK
paint.textSize = 25f
// 需要關(guān)閉硬件加速
setLayerType(LAYER_TYPE_SOFTWARE,null)
// 陰影的半徑 陰影x軸的偏移量 陰影y軸的偏移量 陰影的顏色(對(duì)圖片無效)
paint.setShadowLayer(10f , 20f,20f, Color.GRAY)
// 清除陰影的兩種方法
// paint.clearShadowLayer()
// 將陰影半徑設(shè)為0
// paint.setShadowLayer(0f , 10f,10f, Color.GRAY)
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.animal1)
canvas?.drawText("hello" , 100f,100f,paint)
canvas?.drawCircle(300f,100f,50f , paint)
canvas?.drawBitmap(bitmap , null , Rect(100 , 400 ,100+bitmap.width,400+bitmap.height) ,paint)
}
發(fā)光效果與圖片陰影 MaskFilter setMaskFilter(MaskFilter maskfilter)
需要關(guān)閉硬件加速
MaskFilter 有兩個(gè)子類
EmbossMaskFilter 浮雕效果 很少使用
BlurMaskFilter 發(fā)光效果
BlurMaskFilter 的構(gòu)造函數(shù) 需要傳入style BlurMaskFilter(float radius, Blur style)
Blur 枚舉 | 效果 |
---|---|
NORMAL | 內(nèi)外發(fā)光 |
SOLID | 外發(fā)光 |
OUTER | 僅顯示發(fā)光效果 |
INNER | 內(nèi)發(fā)光 |
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setLayerType(LAYER_TYPE_SOFTWARE,null)
var paint = Paint()
paint.color = Color.BLACK
paint.textSize = 25f
paint.style = Paint.Style.FILL
paint.maskFilter = BlurMaskFilter(50f, BlurMaskFilter.Blur.INNER)
canvas?.drawCircle(200f,200f,100f,paint)
}
給圖片設(shè)置純色陰影
class View6imageShadow : View {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.translate(100f,100f)
var paint = Paint()
paint.color = Color.BLACK
paint.textSize = 25f
// 繪制原圖
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.animal_dog)
canvas?.drawBitmap(bitmap , Matrix(),paint)
canvas?.drawText("原圖",0f, (bitmap.height+20).toFloat(),paint)
//繪制setShadowLayer 的陰影圖
canvas?.translate(200f , 0f)
canvas?.drawText("添加陰影效果",0f , (bitmap.height+40).toFloat(), paint)
paint.setShadowLayer(10f,20f,20f,Color.GREEN)
canvas?.drawBitmap(bitmap , Matrix(),paint)
//使用extractAlpha函數(shù)獲取alpha圖
paint.clearShadowLayer()
var alphaBitmp = bitmap.extractAlpha()
canvas?.translate(200f , 0f)
canvas?.drawBitmap(alphaBitmp , Matrix(),paint)
canvas?.drawText("alpha圖",0f , (alphaBitmp.height+20).toFloat(), paint)
// 使用alpha圖 和原圖組合成陰影效果
canvas?.translate(200f , 0f)
canvas?.drawBitmap(alphaBitmp , Matrix(),paint)
canvas?.translate(-10f,-10f)
canvas?.drawBitmap(bitmap , Matrix(),paint)
canvas?.drawText("純色陰影圖",0f , (alphaBitmp.height+20).toFloat(), paint)
}
}
Shader
// Shader setShader(Shader shader)
// Shader 的效果與ps中的印章類似 用來實(shí)現(xiàn)圖片 顏色 漸變的 填充效果
// BitmapShader 基本用法
// BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
// TitleMode 取值如下
// CLAMP 用邊緣色彩來填充剩余空間
// REPEAT 重復(fù)圖像來填充空間
// MIRROR 重復(fù)使用鏡像圖像來填充空間
class View6Shader : View {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var paint = Paint()
paint.color = Color.BLACK
paint.textSize = 50f
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.mgirl)
// 繪制原圖
canvas?.drawBitmap(bitmap , Matrix() ,paint)
var fm = paint.getFontMetrics()
// (bitmap.width+20, -fm.top) text的基線坐標(biāo)
canvas?.drawText("原圖",(bitmap.width+20).toFloat() , -fm.top , paint)
// 繪制矩形 使用圖片填充
canvas?.translate(0f , (bitmap.height+20).toFloat())
paint.setShader(BitmapShader(bitmap ,Shader.TileMode.MIRROR,Shader.TileMode.MIRROR))
canvas?.drawRect(0f,0f , width.toFloat(), height.toFloat(), paint)
}
}
放大鏡效果
在手指觸摸的地方繪制一個(gè)圓 然后顯示該圓范圍內(nèi)的圖像
class View6ShaderSample1 : View {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
}
var dx = -1f
var dy = -1f
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
Log.e("view5","touch--down")
// down事件返回true 或者 xml中 android:clickable="true" 否則只會(huì)觸發(fā)down事件 不會(huì)觸發(fā)move事件
return true
}
MotionEvent.ACTION_MOVE -> {
Log.e("view5","touch--move")
dx = event?.x
dy = event?.y
}
}
postInvalidate()
return super.onTouchEvent(event)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var paint = Paint()
paint.color = Color.BLACK
paint.textSize = 50f
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.mgirl)
// 該步驟是為了畫出一張能夠填滿整個(gè)控件大小的背景圖
var bitmapBg = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888)
var canvasBg = Canvas(bitmapBg)
canvasBg?.drawBitmap(bitmap , null ,Rect(0 ,0 ,width ,height) , paint)
if(dx != -1f && dy != -1f){
// 也可直接使用原圖 bitmap
// paint.setShader(BitmapShader(bitmap , Shader.TileMode.REPEAT , Shader.TileMode.REPEAT))
paint.setShader(BitmapShader(bitmapBg , Shader.TileMode.REPEAT , Shader.TileMode.REPEAT))
canvas?.drawCircle(dx, dy, 150f ,paint)
}
}
}
放大鏡效果
class View6ShaderSample1 : View {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
}
var dx = -1f
var dy = -1f
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
Log.e("view5","touch--down")
// down事件返回true 或者 xml中 android:clickable="true" 否則只會(huì)觸發(fā)down事件 不會(huì)觸發(fā)move事件
return true
}
MotionEvent.ACTION_MOVE -> {
Log.e("view5","touch--move")
dx = event?.x
dy = event?.y
}
}
postInvalidate()
return super.onTouchEvent(event)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var paint = Paint()
paint.color = Color.BLACK
paint.textSize = 50f
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.mgirl)
// 該步驟是為了畫出一張能夠填滿整個(gè)控件大小的背景圖
var bitmapBg = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888)
var canvasBg = Canvas(bitmapBg)
canvasBg?.drawBitmap(bitmap , null ,Rect(0 ,0 ,width ,height) , paint)
if(dx != -1f && dy != -1f){
// 也可直接使用原圖 bitmap
// paint.setShader(BitmapShader(bitmap , Shader.TileMode.REPEAT , Shader.TileMode.REPEAT))
paint.setShader(BitmapShader(bitmapBg , Shader.TileMode.REPEAT , Shader.TileMode.REPEAT))
canvas?.drawCircle(dx, dy, 150f ,paint)
}
}
}
圓形頭像
class View6ShaderSample2 : View {
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var paint = Paint()
paint.color = Color.BLACK
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.girl)
var matrix = Matrix()
var scale = width.toFloat()/bitmap.width
matrix.setScale(scale,scale)
var bitmapShader = BitmapShader(bitmap , Shader.TileMode.CLAMP , Shader.TileMode.CLAMP)
bitmapShader.setLocalMatrix(matrix)
paint.setShader(bitmapShader)
var half = width.toFloat()/2
canvas?.drawCircle(half,half,half,paint)
}
}
線性漸變 LinearGradient
// 構(gòu)造函數(shù) 坐標(biāo)1到坐標(biāo)2 實(shí)現(xiàn) color1到color2的漸變
// LinearGradient(float x0, float y0, float x1, float y1, @ColorInt int color0, @ColorInt int color1,@NonNull TileMode tile)
// 構(gòu)造函數(shù) 坐標(biāo)1到坐標(biāo)2 實(shí)現(xiàn) color1到colorn 的漸變 positions取值0-1 表示每種顏色在整條漸變線中的百分比位置
// LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors, @Nullable float[] positions, @NonNull TileMode tile)
// 雙色漸變
var linearGradient = LinearGradient(0f, 0f ,width.toFloat() , height/2.toFloat()-50 ,Color.BLUE , Color.GREEN , Shader.TileMode.CLAMP)
paint.setShader(linearGradient)
canvas?.drawRect(0f,0f,width.toFloat() , height/2.toFloat()-50 , paint)
// 多色漸變
var colors = intArrayOf(Color.BLUE,Color.CYAN,Color.RED ,Color.YELLOW)
var position = floatArrayOf(0f,.3f , .7f , 1f)
var linearGradient2 = LinearGradient(0f, height/2.toFloat()+50 ,width.toFloat() , height.toFloat() , colors , position , Shader.TileMode.CLAMP)
paint.setShader(linearGradient2)
canvas?.drawRect(0f,height/2.toFloat()+50,width.toFloat() , height.toFloat() , paint)
示例 文字漸變效果
class View6LinearGradientText : View {
var paint : Paint
var length = 0f
var text = "文字漸變效果"
var fontMetrics : Paint.FontMetrics
var dx = 0f
// 延遲初始化
lateinit var linearGradient : LinearGradient
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
paint = Paint()
paint.color = Color.BLACK
paint.textSize = 50f
fontMetrics = paint.getFontMetrics()
var length = paint.measureText(text)
createAnim(length)
createLinearGradient(length)
}
private fun createAnim( length : Float){
var anim =ValueAnimator.ofFloat(0f , 2 * length)
anim.addUpdateListener({
dx = it.animatedValue as Float
postInvalidate()
})
anim.repeatMode = ValueAnimator.RESTART
anim.repeatCount = ValueAnimator.INFINITE
anim.duration = 2000
anim.start()
}
fun createLinearGradient(length : Float){
linearGradient = LinearGradient(-length , 0f , 0f , 0f , intArrayOf(Color.BLACK , Color.GREEN, Color.YELLOW , Color.BLACK) ,
floatArrayOf(0f , 0.3f , .7f , 1f) , Shader.TileMode.CLAMP)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var matrix = Matrix()
matrix.setTranslate(dx , 0f )
linearGradient.setLocalMatrix(matrix)
paint.setShader(linearGradient)
canvas?.drawText(text , 0f , -fontMetrics.top , paint)
}
放射漸變 RadialGradient
class View6RadialGradient : View {
var paint : Paint
constructor(context: Context?, attrs: AttributeSet?):super(context, attrs){
setLayerType(LAYER_TYPE_SOFTWARE,null)
paint = Paint()
paint.color = Color.BLACK
paint.textSize = 50f
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// RadialGradient 從一個(gè)點(diǎn)向周圍擴(kuò)散的漸變
// 雙色漸變
var radialGradient = RadialGradient(100f , 100f , 100f ,Color.BLUE , Color.YELLOW ,Shader.TileMode.REPEAT)
paint.setShader(radialGradient)
canvas?.drawCircle(100f , 100f , 100f , paint)
// 多色漸變
radialGradient = RadialGradient(width/2.toFloat() , height/2.toFloat() , 200f ,
intArrayOf(Color.BLUE , Color.RED,Color.GREEN , Color.YELLOW) , floatArrayOf(0f, .2f ,.6f , 1f),Shader.TileMode.REPEAT)
paint.setShader(radialGradient)
canvas?.drawCircle(width/2.toFloat() , height/2.toFloat() , 200f , paint)
}
混合模式 Xfermode
// 混合模式相關(guān)知識(shí)是Paint繪圖中最難的部分 它可以將兩張圖片無縫結(jié)合,實(shí)現(xiàn)類似ps的效果
// 混合模式通過 Xfermode類 來實(shí)現(xiàn) paint.setXfermode
// 在使用Xfermode時(shí)需要做兩件事
// 完全不支持硬件加速
// 離屏繪制 canvas.saveLayer() 繪制代碼 canvas.restoreToCount()
//替換顏色 API 16之后無法使用
// paint.xfermode = AvoidXfermode()
// PorterDuffXfermode(PorterDuff.Mode mode) 16種模式可選
// paint.xfermode = PorterDuffXfermode()
// 可以實(shí)現(xiàn) 橡皮檫 刮刮卡等效果
圖像操縱大師Xfermode講解與實(shí)戰(zhàn)——Android高級(jí)UI
Canvas
// 如何獲取canvas對(duì)象
// 1重寫onDraw 用于繪制自身 dispatchDraw用于繪制子視圖
// 2 使用bitmap 創(chuàng)建 new Canvas(bitmap) 或者 Canvas c = new Canvas() c.setBitmap(bitmap)
// 3 SurfaceHolder.lockCanvas() 函數(shù)
這里我們關(guān)注兩個(gè)方法
saveLayer 與 restoreToCount
saveLayer 會(huì)生成一個(gè)新的圖層
之后所有的操作都在新的圖層上進(jìn)行
然后調(diào)用restoreToCount 將圖層合并到原圖層上
與saveLayer相關(guān)的還有 一個(gè)函數(shù) saveLayerAlpha 用于生成一個(gè)帶透明度的圖層
int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
@Saveflags int saveFlags) 最后一個(gè)參數(shù) saveFlags 有許多個(gè)選擇
flag | 描述 |
---|---|
ALL_SAVE_FLAG | 保存所有的標(biāo)識(shí) |
MATRIX_SAVE_FLAG | 保存canvas的matrix信息 |
CLIP_SAVE_FLAG | 保存canvas 的大小信息 |
HAS_ALPHA_LAYER_SAVE_FLAG | 標(biāo)識(shí)新建的bmp具有透明度 在與上層畫布結(jié)合時(shí) 透明位置顯示上層圖像 |
FULL_COLOR_LAYER_SAVE_FLAG | 標(biāo)識(shí)新建的bmp顏色完全獨(dú)立 在與上層畫布結(jié)合時(shí) 先清空上層畫布在進(jìn)行覆蓋 |
CLIP_TO_LAYER_SAVE_FLAG | 在保存圖層前先把當(dāng)前畫布根據(jù)bounds裁剪 |
SurfaceView
概述 android 屏幕刷新的時(shí)間間隔是16ms 如果View能夠在16ms內(nèi)完成所需執(zhí)行的繪圖操作那么在視圖上就是流暢的,否則就會(huì)出現(xiàn)卡頓两波。 有時(shí)候箫锤,在自定義View的日志中,經(jīng)常會(huì)看到如下警告:
Skipped 60 frames! The application may be doing too much work on its main thread
之所以會(huì)出現(xiàn)警告 大部分是因?yàn)槲覀儾粌H在繪制過程中執(zhí)行了繪圖操作 也夾雜了很多邏輯處理 導(dǎo)致在指定的16ms內(nèi)沒有完成繪制
然而很多情況下這些邏輯操作處理又是必須的 為了解決這個(gè)問題 Android引入了SurfaceView雨女。
SurfaceView在兩個(gè)方面改進(jìn)了View的繪圖操作
1.使用雙緩沖技術(shù)
2.自帶畫布谚攒,支持在子線程中更新畫布內(nèi)容
所謂的雙緩沖技術(shù) ,簡(jiǎn)單的說就是多加一塊緩沖畫布氛堕, 當(dāng)需要執(zhí)行繪圖操作時(shí)馏臭,先在緩沖畫布上繪制,繪制好之后在將緩沖畫布上的內(nèi)容更新到主畫布上
SurfaceView 繼承自View 所以SurfaceView可以使用View中的所有方法讼稚,即用View實(shí)現(xiàn)的自定義控件都可以使用SurfaceView來實(shí)現(xiàn) 括儒。
使用SurfaceView來實(shí)現(xiàn)捕捉用戶手勢(shì)軌跡的自定義控件
class View10SurfaceView : SurfaceView {
var paint: Paint
var path: Path
// 方法一
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
// setLayerType(LAYER_TYPE_SOFTWARE , null)
// setWillNotDraw(false)
paint = Paint()
paint.isAntiAlias = true
paint.style = Paint.Style.STROKE
paint.strokeWidth = 10f
paint.color = Color.BLUE
path = Path()
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
var x = event?.x
var y = event?.y
if (x == null || y == null) {
return super.onTouchEvent(event)
}
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
path.moveTo(x, y)
return true
}
MotionEvent.ACTION_MOVE -> {
path.lineTo(x, y)
postInvalidate()
}
}
return super.onTouchEvent(event)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawPath(path,paint)
}
}
我們會(huì)發(fā)現(xiàn)上述代碼沒有反應(yīng) 通過添加日志我們可以發(fā)現(xiàn) onDraw方法并沒有被執(zhí)行
然后我們把其中一行注釋打開 就可以了 setWillNotDraw(false)
setWillNotDraw
這個(gè)函數(shù)存在于View類中, 它主要用在View派生子類的初始化中锐想, 如果 設(shè)置為true 表示當(dāng)前控件沒有繪制內(nèi)容帮寻,當(dāng)屏幕重繪時(shí),這個(gè)控件不需要繪制 所以在重繪的時(shí)候也不會(huì)調(diào)用這個(gè)類的onDraw函數(shù) 相反如果設(shè)置為false 則表示當(dāng)前控件在每次重繪時(shí)都需要繪制該控件
可見setWillNotDraw 是一種優(yōu)化策略赠摇,它可以顯示的告訴系統(tǒng)固逗,在重繪屏幕時(shí)浅蚪,哪個(gè)控件需要重繪,哪個(gè)控件不需要重繪烫罩,這樣就可以大大提高重繪效率
一般而言惜傲,像LinearLayout、relativelayout 等布局控件贝攒,它們的主要功能是布局其中的控件盗誊,它們本身是不需要繪制的, 所以它們?cè)跇?gòu)造的時(shí)候都會(huì)顯式地設(shè)置
setWillNotDraw(true) 而上面SurfaceView重繪時(shí),之所以沒有調(diào)用onDraw函數(shù) 是因?yàn)镾urfaceView在初始化的時(shí)候顯式調(diào)用了setWillNotDraw(true)
雖然我們找到了原因 但是從這里也看的出來SurfaceView的開發(fā)人員并不想讓我們通過重寫onDraw方法來繪制界面 而要使用SurfaceView獨(dú)有的特性來操作畫布
使用SurfaceView來實(shí)現(xiàn)捕捉用戶手勢(shì)軌跡的自定義控件 方法二
class View10SurfaceView : SurfaceView {
var paint: Paint
var path: Path
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
// setLayerType(LAYER_TYPE_SOFTWARE , null)
// setWillNotDraw(false)
paint = Paint()
paint.isAntiAlias = true
paint.style = Paint.Style.STROKE
paint.strokeWidth = 10f
paint.color = Color.BLUE
path = Path()
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
var x = event?.x
var y = event?.y
if (x == null || y == null) {
return super.onTouchEvent(event)
}
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
path.moveTo(x, y)
return true
}
MotionEvent.ACTION_MOVE -> {
path.lineTo(x, y)
// postInvalidate()
}
}
drawCanvas()
return super.onTouchEvent(event)
}
fun drawCanvas() {
Thread(Runnable {
var holder = holder
var canvas = holder.lockCanvas()
canvas.drawPath(path, paint)
holder.unlockCanvasAndPost(canvas)
}).start()
var holder = holder
//監(jiān)聽Surface生命周期
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
override fun surfaceCreated(holder: SurfaceHolder) {
}
})
}
}
方法一 setWillNotDraw(false) + ACTION_MOVE時(shí)postInvalidate + onDraw
方法二 ACTION_MOVE時(shí) 調(diào)用 drawCanvas
SurfaceView移動(dòng)背景
class View10SurfaceViewBg : SurfaceView {
var surfaceHolder : SurfaceHolder
var flag = false
lateinit var bitmapBg : Bitmap
var surfaceWidth = 0
var surfaceHeight = 0
// 圖片移動(dòng)的距離
var bitPosX = 0f
lateinit var mCanvas : Canvas
lateinit var thread : Thread
// 圖片移動(dòng)的方向
enum class State{
LEFT ,RIGHT
}
var state = State.LEFT
// 圖片每一步移動(dòng)的單位
val BITMAP_STEP = 10
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
// setLayerType(LAYER_TYPE_SOFTWARE , null)
// setWillNotDraw(false)
surfaceHolder = holder
surfaceHolder.addCallback(object : SurfaceHolder.Callback{
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
flag = false
}
override fun surfaceCreated(holder: SurfaceHolder) {
// 設(shè)置監(jiān)聽 創(chuàng)建時(shí)啟動(dòng)
flag = true
startAnimation()
}
} )
}
fun startAnimation(){
surfaceWidth = width
surfaceHeight = height
var minWidth = surfaceWidth*3/2
// 獲取原圖
var bitmap = BitmapFactory.decodeResource(resources , R.mipmap.bg)
// 繪制縮放圖 高==屏幕高度 寬==1.5倍屏幕寬度
bitmapBg = Bitmap.createScaledBitmap(bitmap ,minWidth , height ,true)
thread = Thread(Runnable {
while (flag){
mCanvas = surfaceHolder.lockCanvas()
drawView()
surfaceHolder.unlockCanvasAndPost(mCanvas)
try {
Thread.sleep(50)
}catch (e : Exception){
e.printStackTrace()
}
}
})
thread.start()
}
fun drawView(){
// 清空屏幕
mCanvas.drawColor(Color.TRANSPARENT , PorterDuff.Mode.CLEAR)
// 繪制顯示圖片 從圖片的 (bitPosX,0)位置開始
mCanvas.drawBitmap(bitmapBg , bitPosX , 0f ,null)
when(state){
State.LEFT -> {
bitPosX -= BITMAP_STEP
}
State.RIGHT -> {
bitPosX += BITMAP_STEP
}
}
if(bitPosX <= -surfaceWidth/2){
state = State.RIGHT
}
if(bitPosX >= 0){
state = State.LEFT
}
}
}
* SurfaceView 雙緩沖技術(shù)
/**
- 依次繪制 0-9 按照我們的邏輯一共有兩塊畫布 那么兩塊畫布依次繪制的是 0 2 4 6 8 和 1 3 5 7 9
- 因?yàn)樽詈罄L制的是9 那么最后顯示在屏幕上的應(yīng)該是1 3 5 7 9 但是實(shí)際最后顯示的是 0 3 6 9
- 為什么是0 3 6 9 那是因?yàn)檫@里有三塊緩沖畫布
- 假如我們?cè)诿看卫L制完數(shù)字的時(shí)候 讓線程休息一段時(shí)間 那么就可以看見每次繪制的數(shù)字了 這時(shí)最后顯示的是 1 3 5 7 9 說明此時(shí)只有兩塊畫布
- 根據(jù)官方的解釋 緩沖畫布的數(shù)量是根據(jù)需求動(dòng)態(tài)分配的 如果用戶獲取畫布的頻率較慢哈踱,那么分配兩塊緩沖畫布 。 否則將分配3的倍數(shù)快緩沖畫布 具體數(shù)量由實(shí)際情況而定
- 總的來說 Surface肯定會(huì)配分配>=2個(gè)緩沖區(qū)域 具體分配多少由實(shí)際情況而定
- 雙緩沖局部更新 holder.lockCanvas(Rect())
- lockCanvas 用于獲取整屏畫布 當(dāng)前屏幕顯示的內(nèi)容不會(huì)被更新到新畫布上
- lockCanvas(Rect()) 用于獲取指定區(qū)域的畫布 梨熙, 畫布以外的區(qū)域會(huì)與屏幕內(nèi)容一致 畫布以內(nèi)的區(qū)域依然保持對(duì)應(yīng)的緩沖畫布的內(nèi)容
*/
class View10SurfaceView3p3 : SurfaceView {
var paint: Paint
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
paint = Paint()
paint.isAntiAlias = true
paint.color = Color.BLUE
paint.textSize = 30f
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
override fun surfaceCreated(holder: SurfaceHolder) {
drawCanvas()
}
})
}
fun drawCanvas() {
Thread(Runnable {
var holder = holder
for (index in 0..9) {
var canvas = holder.lockCanvas()
canvas.drawText(index.toString() , (index+1) * 30f , 50f , paint)
holder.unlockCanvasAndPost(canvas)
Thread.sleep(500)
}
}).start()
}
}
若使用holder.lockCanvas(Rect()) 則需要先對(duì)緩沖屏幕進(jìn)行清屏 不然我們拿到的畫圖區(qū)域不一定是我們指定的區(qū)域
while (true){
var dirtyRecf = Rect(0,0,1,1)
var locakcanvas = holder.lockCanvas(dirtyRecf)
var canvasRect = locakcanvas.clipBounds
if(width == canvasRect.width() && height == canvasRect.height() ){
locakcanvas.drawColor(Color.BLACK)
holder.unlockCanvasAndPost(locakcanvas)
}else{
holder.unlockCanvasAndPost(locakcanvas)
break
}
}
這是因?yàn)橛幸粔K畫布初始化的被顯示在了屏幕上 嚣鄙,已經(jīng)被默認(rèn)填充為黑色 而另外兩塊畫布都還沒有被畫過
雖然我們指定了獲取畫布的區(qū)域范圍,但是系統(tǒng)認(rèn)為 整塊畫布都是臟區(qū)域串结, 都應(yīng)該被畫上哑子,所以會(huì)返回屏幕大小的畫布
只有我們將每塊畫布都畫過以后, 才會(huì)按照我們指定的區(qū)域來返回畫布大小