前言
上两篇文章对安卓自定义view的事件分发做了一些应用,但是对于自定义view来讲,并不仅仅是事件分发这么简单,还有一个很重要的内容就是view的绘制流程。接下来我这通过带header和footer的Layout,来学习一下ViewGroup的自定义流程,并对其中的MeasureSpec、onMeasure以及onLayout加深理解。
需求
这里就是一个有header和footer的滚动控件,可以在XML中当Layout使用,核心思想如下:
- 1、由header、XML内容、footer三部分组成
- 2、滚动中间控件时,上面有内容时header不显示,下面有内容时footer不显示
- 3、滑动到header和footer最大值时不能滑动,释放的时候需要回弹
- 4、完全显示时隐藏footer
编写代码
编写代码这部分还真让我头疼了一会,主要就是MeasureSpec的运用,如何让控件能够超出给定的高度,如何获得实际高度和控件高度,真是纸上得来终觉浅,绝知此事要躬行,看书那么多遍,实际叫自己写起来真的费劲,不过最终写完,才真的敢说自己对measure和layout有一定了解了。
老习惯,先看代码,再讲问题吧!
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Scroller
import android.widget.TextView
import androidx.core.view.forEach
import kotlin.math.min
/**
* 有header和footer的滚动控件
* 核心思想:
* 1、由header、container、footer三部分组成
* 2、滚动中间控件时,上面有内容时header不显示,下面有内容时footer不显示
* 3、滑动到header和footer最大值时不能滑动,释放的时候需要回弹
* 4、完全显示时隐藏footer
*/
@SuppressLint("SetTextI18n", "ViewConstructor")
class HeaderFooterView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0,
var header: View? = null,
var footer: View? = null
): ViewGroup(context, attributeSet, defStyleAttr){
var onReachHeadListener: OnReachHeadListener? = null
var onReachFootListener: OnReachFootListener? = null
//上次事件的横坐标
private var mLastY = 0f
//总高度
private var totalHeight = 0
//是否全部显示
private var isAllDisplay = false
//流畅滑动
private var mScroller = Scroller(context)
init {
//设置默认的Header、Footer,这里是从构造来的,如果外部设置需要另外处理
header = header ?: makeTextView(context, "Header")
footer = footer ?: makeTextView(context, "Footer")
//添加对应控件
addView(header, 0)
//这里还没有加入XML中的控件
//Log.e("TAG", "init: childCount=$childCount", )
addView(footer, 1)
}
//创建默认的Header\Footer
private fun makeTextView(context: Context, textStr: String): TextView {
return TextView(context).apply {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT