自定义View是Android基础
1、什么是自定义View?
一个效果只要它能够在手机上面实现,我们就应该具有实现它的能力。
学习方式?实践->理论
2、自定义View包含什么?
布局:onmeasure、onlayout
例如 Layout ViewGroup
显示:onDraw
例如:View: canvas、paint、matrix、clip、rect、animation、path(贝塞尔曲线)、
line
交互:onTouchEvent:组合的ViewGroup
3、自定义View如何分类?
(1)自定义View
在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他View
(2)自定义ViewGroup
一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout
4、自定义View的绘制流程
(1)测量(onMeasure())
先测量子View的大小,再测量自己的大小
(2)布局(onLayout())
(3)实际绘制内容(onDraw())
自定义View的关键是:
(1)自定义View主要实现 onMeasure() + onDraw()
(2)自定义ViewGroup主要实现 onMeasure() + onLayout()
阅读源码可以根据这两种分类,确定关键代码在哪里。
5、自定义流式布局(FlowLayout)源码
先测量onMeasure(),再布局onLayout()
package com.example.myany.widgets
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
class FlowLayout: ViewGroup {
//new
constructor(context: Context)
: this(context, null) {
}
//反射
constructor(context: Context, attrs: AttributeSet?)
: this(context, attrs, 0) {
}
//主题style
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr) {
}
//自定义属性
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int)
: super(context, attrs, defStyleAttr, defStyleRes) {
}
private val mHorizontalSpacing = 30
private val mVerticalSpacing = 30
private val allLines = mutableListOf<List<View>>()//记录所有的行,一行一行的存储,用于layout
private val lineHeights = mutableListOf<Int>()//记录每一行的高度,用于layout
private fun clearMData(){
allLines.clear()
lineHeights.clear()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
Log.e("FlowLayout", "onMeasure")
clearMData()
//先度量孩子
val childCount = childCount
val paddingLeft = paddingLeft
val paddingRight = paddingRight
val paddingTop = paddingTop
val paddingBottom = paddingBottom
//解析父亲给我的宽高
val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
val selfHeight = MeasureSpec.getSize(heightMeasureSpec)
var lineViews = mutableListOf<View>()//保存一行中所有的view
var lineUsedWidth: Int = 0//记录这行已经使用的size
var lineHeight: Int = 0//一行的高度
var parentNeededWidth = 0 //measure过程中,子view要求父ViewGroup的宽
var parentNeededHeight = 0 //measure过程中,子view要求父ViewGroup的高
for(i in 0 until childCount) {
Log.e("FlowLayout", "measure child $i")
val childView = getChildAt(i)
val childLp = childView.layoutParams
//将layoutParams转换成 measureSpec
val childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft+paddingRight, childLp.width)
val childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop+paddingBottom, childLp.height)
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
//获得子view的度量宽高
val childMeasureWidth = childView.measuredWidth
val childMeasureHeight = childView.measuredHeight
//若需要换行
if (childMeasureWidth + lineUsedWidth + mHorizontalSpacing >= selfWidth) {
//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时记录下来
allLines.add(lineViews)
lineHeights.add(lineHeight)
parentNeededHeight += lineHeight + mVerticalSpacing
parentNeededWidth = parentNeededWidth.coerceAtLeast(lineUsedWidth + mHorizontalSpacing)
lineViews = mutableListOf()
lineUsedWidth = 0
lineHeight = 0
}
//view 是分行的layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView)
//每行都会有自己的宽和高
lineUsedWidth += childMeasureWidth + mHorizontalSpacing
lineHeight = lineHeight.coerceAtLeast(childMeasureHeight)
//处理最后一行
if (i == childCount - 1) {
allLines.add(lineViews)
lineHeights.add(lineHeight)
parentNeededHeight += lineHeight + mVerticalSpacing
parentNeededWidth = parentNeededWidth.coerceAtLeast(lineUsedWidth + mHorizontalSpacing)
}
}
parentNeededHeight += paddingTop + paddingBottom
//再度量自己,保存
//根据子view的度量结果,来重新度量自己ViewGroup
//作为一个ViewGroup,它自己也是一个view,它的大小也需要根据它的父亲给它提供的宽高来度量
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val realWidth = if (widthMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
val realHeight = if (heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight
setMeasuredDimension(realWidth, realHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
Log.e("FlowLayout", "onLayout")
var curL = paddingLeft
var curT = paddingTop
val lineCount = allLines.size
for (i in 0 until lineCount) {
Log.e("FlowLayout", "layout line $i")
val lineViews = allLines[i]
val count = lineViews.size
for (j in 0 until count) {
Log.e("FlowLayout", "layout line view $j")
val view = lineViews[j]
val left = curL
val top = curT
val right = left + view.measuredWidth
val bottom = top + view.measuredHeight
view.layout(left, top, right, bottom)
curL = right + mHorizontalSpacing
}
curL = paddingLeft
curT += lineHeights[i] + mVerticalSpacing
}
}
}