IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> RecyclerView 性能优化 _ 把加载表项耗时减半 (三),最新出炉 -> 正文阅读

[移动开发]RecyclerView 性能优化 _ 把加载表项耗时减半 (三),最新出炉

 var width = 0
var height = 0
// 上下左右边距
var topMargin = 0
var bottomMargin = 0
var leftMargin = 0
var rightMargin = 0
// 用于保存测量宽高结果的变量
var measuredWidth = 0
var measuredHeight = 0
// 上下左右用于描述可绘制对象所处矩形
var left = 0
var right = 0
var top = 0
var bottom = 0
// 上下左右内边距
var paddingStart = 0
var paddingEnd = 0
var paddingTop = 0
var paddingBottom = 0

// 如何测量及绘制由子类定义
abstract fun measure(widthMeasureSpec: Int, heightMeasureSpec: Int)
abstract fun draw(canvas: Canvas?)

// 布局的结果保存在上下左右四个变量组成的矩形中
fun setRect(left: Int, top: Int, right: Int, bottom: Int) {
    this.left = left
    this.right = right
    this.top = top
    this.bottom = bottom
}

// 测量的结果保存在 measuredWidth 和 measuredHeight
fun setDimension(width: Int, height: Int) {
    this.measuredWidth = width
    this.measuredHeight = height
}

}


为`Drawable`新增了很多属性,用于描述它的尺寸及相对位置。还新增了两个方法用于保存测量和布局的结果。因为同时存在抽象和非抽象方法,就把原先的接口重构成了抽象类。

然后重写`onLayout()`以定位所有`Drawable`对象相对于父控件的位置:

class OneViewGroup
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {
// 容器1:保存 Drawable 及其 id 的对应关系
private val drawableMap = HashMap<Int, Drawable>()
// 容器2:按序保存所有 Drawable 实例
private val drawables = mutableListOf()

// 向父控件中添加 Drawable 对象,它的引用会同时存储在两种容器中
fun addDrawable(drawable: Drawable) {
    drawables.add(drawable)
    drawableMap[drawable.id] = drawable
}

// 按序测量所有 Drawable
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    drawables.forEach { it.measure(widthMeasureSpec, heightMeasureSpec) }
}

// 按序布局所有 Drawable
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    val parentWidth = right - left
    val parentHeight = bottom - top
    drawables.forEach {
        // 计算 Drawable 的 left
        val left = getChildLeft(it, parentWidth)
        // 计算 Drawable 的 top
        val top = getChildTop(it, parentHeight)
        // 确定 Drawable 上下左右四个角
        it.setRect(left, top, left + it.measuredWidth, top + it.measuredHeight)
    }
}

// 按序绘制所有 Drawable
override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    drawables.forEach { it.draw(canvas) }
}

}


现在`OneViewGroup`的代码和自定义控件的代码架子一模一样,都有三个步骤,测量、布局、绘制。只不过现在的对象不是 View,而是自定义的 Drawable。

其中`getChildTop()`和`getChildTop()`会读取刚才定义一系列属性,并根据属性值计算出`Drawable`相对于`OneViewGroup`左上角的坐标:

class OneViewGroup
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: View(context, attrs, defStyleAttr) {

private fun getChildTop(drawable: Drawable, parentHeight: Int): Int {
    return when {
        // 若指定了上百分比,则可和父控件高度相乘直接得出 drawable 的 top 值
        drawable.topPercent != -1f -> (parentHeight * drawable.topPercent).toInt()
        // 若指定了垂直对齐某 drawable
        drawable.centerVerticalOf != -1 -> {
            if (drawable.centerVerticalOf == parent_id) {
                (parentHeight - drawable.height) / 2
            } else {
                (drawableMap[drawable.centerVerticalOf]?.let { it.top + (it.bottom - it.top) / 2 } ?: 0) - drawable.measuredHeight / 2
            }
        }
        // 若指定了在某 drawable 下方
        drawable.topToBottomOf != -1 -> {
            val b = if (drawable.topToBottomOf == parent_id) bottom else drawableMap[drawable.topToBottomOf]?.bottom ?: 0
            (b + drawable.topMargin)
        }
        // 若指定了和某 drawable 上边对齐
        drawable.topToTopOf != -1 -> {
            val t = if (drawable.topToTopOf == parent_id) top else drawableMap[drawable.topToTopOf]?.top ?: 0
            (t + drawable.topMargin)
        }
        // 若指定了在某 drawable 上方
        drawable.bottomToTopOf != -1 -> {
            val t = if (drawable.bottomToTopOf == parent_id) top else drawableMap[drawable.bottomToTopOf]?.top ?: 0
            (t - drawable.bottomMargin) - drawable.measuredHeight
        }
        // 若指定了和某 drawable 底边对齐
        drawable.bottomToBottomOf != -1 -> {
            val b = if (drawable.bottomToBottomOf == parent_id) bottom else drawableMap[drawable.bottomToBottomOf]?.bottom ?: 0
            (b - drawable.bottomMargin) - drawable.measuredHeight
        }
        else -> 0
    }
}

private fun getChildLeft(drawable: Drawable, parentWidth: Int): Int {
    return when {
        // 若指定了左百分比,则可和父控件宽度相乘直接得出 drawable 的 left 值
        drawable.leftPercent != -1f -> (parentWidth * drawable.leftPercent).toInt()
        // 若指定了水平对齐某 drawable
        drawable.centerHorizontalOf != -1 -> {
            if (drawable.centerHorizontalOf == parent_id) {
                (parentWidth - drawable.width) / 2
            } else {
                (drawableMap[drawable.centerHorizontalOf]?.let { it.left + (it.right - it.left) / 2 } ?: 0) - drawable.measuredWidth / 2
            }
        }
        // 若指定了在某 drawable 右边
        drawable.startToEndOf != -1 -> {
            val r = if (drawable.startToEndOf == parent_id) right else drawableMap[drawable.startToEndOf]?.right ?: 0
            (r + drawable.leftMargin)
        }
        // 若指定了和某 drawable 左边对齐
        drawable.startToStartOf != -1 -> {
            val l = if (drawable.startToStartOf == parent_id) left else drawableMap[drawable.startToStartOf]?.left ?: 0
            (l + drawable.leftMargin)
        }
        // 若指定了在某 drawable 左边
        drawable.endToStartOf != -1 -> {
            val l = if (drawable.endToStartOf == parent_id) left else drawableMap[drawable.endToStartOf]?.left ?: 0
            (l - drawable.rightMargin) - drawable.measuredWidth
        }
        // 若指定了和某 drawable 右边对齐
        drawable.endToEndOf != -1 -> {
            val r = if (drawable.endToEndOf == parent_id) right else drawableMap[drawable.endToEndOf]?.right ?: 0
            (r - drawable.rightMargin) - drawable.measuredWidth
        }
        else -> 0
    }
}

}


`getChildTop()`和`getChildTop()`分类讨论了每一种相对布局的情况下,该如何计算 drawable 的 left 和 top 值。

其中被依赖的控件通过`drawableMap`获取,这个 Map 结构的目的是可以根据 id 快速获取 Drawable 对象。若只有列表结构的`drawables`,则需要遍历,就比较耗时。但遍历 Drawable 进行测量、布局、绘制的时候,使用的是后者,因为 Map 结构是无序的。为了确定一个 Drawable 的位置,必须将它依赖的 Drawable 先完成定位。这要求构建 Drawable 时,被依赖项必须优先定义。定义的顺序被列表结构的`drawables`记录。

然后就可以像这样定义具有相对位置的文字:

OneViewGroup {
layout_width = match_parent
layout_height = match_parent

text {
    id = "title"
    width = 100
    text = "title"
    textSize = 40f
    textColor = "#ffffff"
    leftPercent = 0.2f // 横向 20% 
    topPercent = 0.2f // 纵向 20%
}

text {
    id = "content"
    width = 60
    text = "content"
    textSize = 15f
    textColor ="#88ffffff"
    topToBottomOf = "title" // 在 title 的下面
    startToStartOf = "title" // 与 title 左边对齐
}

}


### 绘制形状

已经可以绘制文字,并且也可以指定文字间的相对位置了。还有一个常见的需求就是为文字添加圆形背景。在 xml 中对应的是`<shape>`标签。

可以直接使用`canvas.drawRoundRect()`在绘制文字之前先绘制一个圆形矩形作为背景。

抽象出一个形状类,它包含了绘制需要的参数:

class Shape {
var color: String? = null // 颜色
var radius: Float = 0f // 圆角半径
var radii: IntArray? = null // 为四个角单独指定圆角
}


`Text`持有一个`Shape`实例:

class Text : Drawable() {
var shapePaint: Paint? = null // 文字背景画笔
var shape: Shape? = null // 文字背景
set(value) {
field = value
shapePaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.parseColor(value?.color)
}
}
override fun draw(canvas: Canvas?) {
canvas?.save()
// 平移画布到文字绘制的左上角, 从这个点开始绘制文字背景
canvas?.translate(left, top)
// 绘制背景
drawBackground(canvas)
// 继续平移画布到文字的绘制点(文字和背景的距离用 padding 表示)
canvas?.translate(paddingStart, paddingTop)
// 绘制文字
staticLayout?.draw(canvas)
canvas?.restore()
}

private fun drawBackground(canvas: Canvas?) {
    // 绘制背景的具体实现
    shape?.let { shape ->
        canvas?.drawRoundRect(0f, 0f, measuredWidth, measuredHeight, shape.radius, shape.radius, shapePaint!!)
    }
}

}


为`OneViewGroup`新增一个扩展方法,以便用声明式的结构来构建`Shape`实例:

fun OneViewGroup.shape(init: OneViewGroup.Shape.() -> Unit): OneViewGroup.Shape
= OneViewGroup.Shape().apply(init)


然后就可以像这样为文字添加背景:

OneViewGroup {
layout_width = match_parent
layout_height = match_parent

text {
    id = "title"
    width = 100
    text = "title"
    textSize = 40f
    textColor = "#ffffff"
    shape = shape {
        color = "#ff0000"
        radius = 20f
    }
}

}


如果需要绘制这样的效果咋办?

![微信图片_20210416174604.jpg](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3aa82af00e8e436c972091e1d79bf5ee~tplv-k3u1fbpfcp-watermark.image)

即左上角和右上角是圆角,其余的不是。

`drawRoundRect()`做不到这个效果,只能用`canvas.drawPath()`。

抽象一个`Corners`类来表示四个角的圆角程度:

class Corners(
var leftTopRx: Float = 0f,
var leftTopRy: Float = 0f,
var leftBottomRx: Float = 0f,
var LeftBottomRy: Float = 0f,
var rightTopRx: Float = 0f,
var rightTopRy: Float = 0f,
var rightBottomRx: Float = 0f,
var rightBottomRy: Float = 0f
) {
// 将 8 个表示圆角的属性,按照 Android api 需要的顺序组织成数组
val radii: FloatArray
get() = floatArrayOf(
leftTopRx,
leftTopRy,
rightTopRx,
rightTopRy,
rightBottomRx,
rightBottomRy,
leftBottomRx,
LeftBottomRy
)
}


其中一共有 8 个属性,分为四对分别表示左上,右上,左下,右下四个角。

`Shape`会持有一个`Corners`实例:

class Shape {
var color: String? = null
var radius: Float = 0f
// 绘制的路径
internal var path: Path? = null
var corners: Corners? = null
set(value) {
field = value
// 当 corners 被赋值时构建 Path 实例
path = Path()
}
}


再需要改写一下`Text`中绘制背景的方法:

class Text : OneViewGroup.Drawable() {
var shapePaint: Paint? = null
var shape: Shape? = null
set(value) {
field = value
shapePaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.parseColor(value?.color)
}
}

private fun drawBackground(canvas: Canvas?) {
    if (shape == null) return
    val _shape = shape!!
    // 如果设置了 radius 表示四个角都是圆角
    if (_shape.radius != 0f) {
        canvas?.drawRoundRect(0f, 0f, measuredWidth, measuredHeight, _shape.radius, _shape.radius, shapePaint!!)
    } 
    // 如果设置了 corners 表示有些角是圆角
    else if (_shape.corners != null) {
        // 根据 radii 属性构建 path
        _shape.path!!.apply {
            addRoundRect(
                RectF(0f, 0f, measuredWidth, measuredHeight),
                _shape.corners!!.radii,
                Path.Direction.CCW
            )
        }
        // 绘制 path
        canvas?.drawPath(_shape.path!!, shapePaint!!)
    }
}

}


还是借助于 Kotlin 的语法糖,让构建`Corners`变得更加可读:

fun Shape.corners(init: Shape.Corners.() -> Unit): Corners
= Corners().apply(init)


然后就可以像这样构建上面截图中的形状了:

OneViewGroup {
layout_width = match_parent
layout_height = match_parent

text {
    id = "title"
    width = 100
    text = "title"
    textSize = 40f
    textColor = "#000000"
    shape = shape {
        color = "#ffffff"
        corners = corners{
            leftTopRx = 30f
            leftTopRy = 30f
            rightTopRx = 30f
            rightTopRy = 30f
        }
    }
}

}


绘制图片
----

图片的加载就要复杂很多。如何异步获取图片?如何绘制图片?即使解决了这两个问题,如果没有办法做到局部刷新,那当图片显示时,布局中的文字也会跟着闪一下。(欢迎有思路的小伙伴留言)

又没看过`ImageView`的源码,自己也很难较好地处理这些问题。那就先退一步,图片依然采用`ImageView`控件展示。但这样的话就产生了一个新的问题:_**如何确定 `ImageView` 控件和 `OneViewGroup` 控件中绘制文字的相对位置?**_

控件与控件之间的相对位置很好确定,但如何确定一个控件和另一个控件中绘制内容的相对位置?

`OneViewGroup`中的绘制内容被抽象为一个`Drawable`对象,该对象用一组属性来标识和另一个`Drawable`对象的相对位置。如果`ImageView`也是一个`Drawable`对象,那就能很方便的确定它和绘制文字的相对位置了!

怎么把一个类装扮成另一个类?—— 多重继承

但 Kotlin 不支持多重继承,所以只能把抽象类`Drawable`重构成接口:

interface Drawable {
// 测量后的宽高
var layoutMeasuredWidth: Int
var layoutMeasuredHeight: Int
// 布局后的上下左右边框
var layoutLeft: Int
var layoutRight: Int
var layoutTop: Int
var layoutBottom: Int
// 唯一标识 id
var layoutId: Int
// 相对布局属性
var leftPercent: Float
var topPercent: Float
var startToStartOf: Int
var startToEndOf: Int
var endToEndOf: Int
var endToStartOf: Int
var topToTopOf: Int
var topToBottomOf: Int
var bottomToTopOf: Int
var bottomToBottomOf: Int
var centerHorizontalOf: Int
var centerVerticalOf: Int
// 记录业务层设置的宽高
var layoutWidth: Int
var layoutHeight: Int
// 内边距
var layoutPaddingStart: Int
var layoutPaddingEnd: Int
var layoutPaddingTop: Int
var layoutPaddingBottom: Int
// 外边距
var layoutTopMargin: Int
var layoutBottomMargin: Int
var layoutLeftMargin: Int
var layoutRightMargin: Int
// 布局的终点:确定上下左右
fun setRect(left: Int, top: Int, right: Int, bottom: Int) {
this.layoutLeft = left
this.layoutRight = right
this.layoutTop = top
this.layoutBottom = bottom
}
// 测量的终点:确定宽高
fun setDimension(width: Int, height: Int) {
this.layoutMeasuredWidth = width
this.layoutMeasuredHeight = height
}
// 抽象的 测量 布局 绘制 , 供子类实现多态
fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
fun doLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)
fun doDraw(canvas: Canvas?)
}


然后新建一个类,即继承了`ImageView`又实现了`Drawable`接口:

class ImageDrawable
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: AppCompatImageView(context, attrs, defStyleAttr), OneViewGroup.Drawable {

override var leftPercent: Float = -1f
override var topPercent: Float = -1f
override var startToStartOf: Int = -1
override var startToEndOf: Int = -1
override var endToEndOf: Int = -1
override var endToStartOf: Int = -1
override var topToTopOf: Int = -1
override var topToBottomOf: Int = -1
override var bottomToTopOf: Int = -1
override var bottomToBottomOf: Int = -1
override var centerHorizontalOf: Int = -1
override var centerVerticalOf: Int = -1
override var layoutWidth: Int = 0
override var layoutHeight: Int = 0
override var layoutMeasuredWidth: Int = 0
    get() = measuredWidth
override var layoutMeasuredHeight: Int = 0
    get() = measuredHeight
override var layoutLeft: Int = 0
    get() = left
override var layoutRight: Int = 0
    get() = right
override var layoutTop: Int = 0
    get() = top
override var layoutBottom: Int = 0
    get() = bottom
override var layoutId: Int = 0
    get() = id
override var layoutPaddingStart: Int = 0
    get() = paddingStart
override var layoutPaddingEnd: Int = 0
    get() = paddingEnd
override var layoutPaddingTop: Int = 0
    get() = paddingTop
override var layoutPaddingBottom: Int = 0
    get() = paddingBottom
override var layoutTopMargin: Int = 0
    get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0
override var layoutBottomMargin: Int = 0
    get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.bottomMargin ?: 0
override var layoutLeftMargin: Int = 0
    get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.leftMargin ?: 0
override var layoutRightMargin: Int = 0
    get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.rightMargin ?: 0

override fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // 在 View 体系中测量
}

override fun doLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    // 在 View 体系中布局
    layout(left, top, right, bottom)
}

override fun doDraw(canvas: Canvas?) {
    // 在 View 体系中绘制
}

}


接口中的属性都是抽象的,在子类中如果不给它指定一个初始值,就要添加`set()`和`get()`方法。

`ImageDrawable`的测量宽高,上下左右,内外边距的获取都委托给了`View`体系中的值,并且在布局自己的时候调用了`View.layout()`,以确定自己和其他`Drawable`的相对位置。相对位置的计算在`OneViewGroup.onLayout()`中完成:

class OneViewGroup
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: ViewGroup(context, attrs, defStyleAttr) {// 重构为 ViewGroup

private val drawables = mutableListOf<Drawable>()

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    val parentWidth = right - left
    val parentHeight = bottom - top
    // 依次计算每个 Drawable 的相对位置
    drawables.forEach {
        val left = getChildLeft(it, parentWidth)
        val top = getChildTop(it, parentHeight)
        it.doLayout(changed, left, top, left + it.layoutMeasuredWidth, top + it.layoutMeasuredHeight)
    }
}

}


为了让`OneViewGroup`除了容纳`Drawable`之外,还能容纳`View`,所以不得不将其继承自`ViewGroup`。

`OneViewGroup`必须得测量自己的孩子`ImageDrawable`,否则孩子就没有宽高数据:

class OneViewGroup
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: ViewGroup(context, attrs, defStyleAttr) {

private val drawables = mutableListOf<Drawable>()

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // 测量 ImageDrawable
    measureChildren(widthMeasureSpec,heightMeasureSpec) 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    // 测量其他 Drawable
    drawables.forEach { it.doMeasure(widthMeasureSpec, heightMeasureSpec) }
}

}


用 Kotlin 语法糖构建 ImageDrawable:

inline fun OneViewGroup.image(init: ImageDrawable.() -> Unit)
= ImageDrawable(context).apply(init).also {
addView(it) // 添加为 OneViewGroup 的子控件
addDrawable(it) // 添加为 OneViewGroup 的子 Drawable
}


就像多重继承的语义一样,`ImageDrawable`有双重身份,它既是`OneViewGroup`的子控件,又是`OneViewGroup`的子`Drawable`。

`ImageDrawable`的测量、布局、绘制都依赖于 View 体系,唯独布局的参数依赖于其他的`Drawable`。

然后就可以像这样图文混排了:

OneViewGroup {
layout_width = match_parent
layout_height = match_parent

text {
    id = "title"
    width = 100
    text = "title"
    textSize = 40f
    textColor = "#000000"
}

image {
    id = "avatar"
    layout_width = 40
    layout_height = 40
    scaleType = fit_xy
    startToEndOf = "title" // 位于 title 的后面
    centerVerticalOf = "title" // 和 title 垂直居中
}

}


因为没有将 ImageView 去掉,所以这是一个曲线救国的方案。但从另一个角度看,这也是将`OneViewGroup`和任何其他控件组合使用的通用方案。

点击事件
----

原先可以通过`View.setOnClickListener()`分别为子控件设置点击事件。`OneViewGroup`把子控件抽象为`Drawable`后该如何处理点击事件?

[更好的 RecyclerView 表项子控件点击监听器]( )中提到一种解决方案,即判断触点坐标是否和子控件有交集。可以沿用到`OneViewGroup`上:

先为`Drawable`新增一个表示其矩形区域的属性`rect`:

interface Drawable {
var layoutLeft: Int
var layoutRight: Int
var layoutTop: Int
var layoutBottom: Int
// 用上下左右构建矩形对象
val rect: Rect
get() = Rect(layoutLeft, layoutTop, layoutRight, layoutBottom)

}


再在`OneViewGroup`中拦截触摸事件:

class OneViewGroup
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: ViewGroup(context, attrs, defStyleAttr) {

private val drawables = mutableListOf<Drawable>()
// 手势监听器, 用于将触摸事件解析成点击事件
private var gestureDetector: GestureDetector? = null

// 设置 Drawable 点击监听器
fun setOnItemClickListener(onItemClickListener: (String) -> Unit) {
    // 构造手势监听器
    gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
        override fun onShowPress(e: MotionEvent?) {
        }

        // 当单击事件发生时
        override fun onSingleTapUp(e: MotionEvent?): Boolean {
            e?.let {
                // 若在触摸点找到对应 Drawable 则回调点击事件
                findDrawableUnder(e.x, e.y)?.let { onItemClickListener.invoke(it.layoutIdString) }
            }
            return true
        }

        // 必须返回 true 表示处理 ACTION_DOWN 事件, 否则后续事件不会传递到 OneViewGroup
        override fun onDown(e: MotionEvent?): Boolean {
            return true
        }

        override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
            return false
        }

        override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
            return false
        }

        override fun onLongPress(e: MotionEvent?) {
        }
    })
}

// 根据坐标查找 Drawable 对象
private fun findDrawableUnder(x: Float, y: Float): Drawable? {
    // 遍历所有 Drawable ,返回矩形区域内包含触点的 Drawable
    drawables.forEach {
        if (it.rect.contains(x.toInt(), y.toInt())) {
            return it
        }
    }
    return null
}

// 将触摸事件传递给手势监听器
override fun onTouchEvent(event: MotionEvent?): Boolean {
    return gestureDetector?.onTouchEvent(event) ?: super.onTouchEvent(event)
}

}


然后就可以像这样为`OneViewGroup`设置子 Drawable 的点击事件了:

OneViewGroup {
layout_width = match_parent
layout_height = match_parent

text {
    id = "title"
    width = 100
    text = "title"
    textSize = 40f
    textColor = "#000000"
}

setOnItemClickListener { id->
    when (id) {
        "title" -> {
            Log.v("test", "title is clicked")
        }
    }
}

}


Talk is cheap, show me the code
-------------------------------

*   `OneViewGroup`源码可以点击[这里]( )
*   Demo 源码地址可以点击[这里]( ) (其中的`RecyclerViewPerformanceActivity`)

最后附上,文章开头截图布局用`OneViewGroup`的重构版本(经重构, 其中和 OneViewGroup 有关的属性都以 drawable 开头):

class OneRankProxy : VarietyAdapter2.Proxy<BetterRank, OneRankViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val itemView = parent.context.run {
LinearLayout {
layout_id = “container”
layout_width = match_parent
layout_height = wrap_content
orientation = vertical
margin_start = 20
margin_end = 20
padding_bottom = 16
shape = shape {
corner_radius = 20
solid_color = “#ffffff”
}

            OneViewGroup { // 用 OneViewGroup 重构的表头
                layout_id = "one"
                layout_width = match_parent
                layout_height = 60
                shape = shape {
                    corner_radii = intArrayOf(20, 20, 20, 20, 0, 0, 0, 0)
                    solid_color = "#ffffff"
                }

                text {
                    drawable_layout_id = "tvTitle"
                    drawable_max_width = 60
                    drawable_text_size = 16f
                    drawable_text_color = "#3F4658"
                    drawable_start_to_start_of = parent_id
                    drawable_left_margin = 20
                    topPercent = 0.23f
                }

                text {
                    drawable_layout_id = "tvRank"
                    drawable_max_width = 60
                    drawable_text_size = 11f
                    drawable_text_color = "#9DA4AD"
                    leftPercent = 0.06f
                    topPercent = 0.78f
                }

                text {
                    drawable_layout_id = "tvName"
                    drawable_max_width = 60
                    drawable_text_size = 11f
                    drawable_text_color = "#9DA4AD"
                    leftPercent = 0.18f
                    topPercent = 0.78f
                }

                text {
                    drawable_layout_id = "tvCount"
                    drawable_max_width = 100
                    drawable_text_size = 11f
                    drawable_text_color = "#9DA4AD"
                    drawable_end_to_end_of = parent_id
                    drawable_right_margin = 20
                    topPercent = 0.78f
                }

            }
        }

    }
    return OneRankViewHolder(itemView)
}


override fun onBindViewHolder(holder: OneRankViewHolder, data: BetterRank, index: Int, action: ((Any?) -> Unit)?) {
    holder.tvTitle?.text = data.title
    holder.tvCount?.text = data.countColumn
    holder.tvRank?.text = data.rankColumn
    holder.tvName?.text = data.nameColumn

    holder.container?.apply {
        // 遍历主播数据, 动态构建每个主播的布局
        data.ranks.forEachIndexed { index, rank ->
            OneViewGroup {
                layout_width = match_parent
                layout_height = 35
                background_color = "#ffffff"

                text {
                    drawable_layout_id = "tvRank"
                    drawable_layout_width = 18
                    drawable_text_size = 14f
                    drawable_text_color = "#9DA4AD"
                    leftPercent = 0.08f
                    drawable_center_vertical_of = parent_id
                    text = rank.rank.toString()
                }

                image {
                    layout_id = "ivAvatar"
                    layout_width = 20
                    layout_height = 20
                    scaleType = scale_center_crop
                    drawable_center_vertical_of = parent_id
                    leftPercent = 0.15f
                    load(rank.avatarUrl) 
                }

                text {
                    drawable_layout_id = "tvName"
                    drawable_max_width = 200
                    drawable_text_size = 11f
                    drawable_text_color = "#3F4658"
                    drawable_gravity = gravity_center
                    drawable_max_lines = 1
                    drawable_start_to_end_of = "ivAvatar"
                    drawable_top_to_top_of = "ivAvatar"
                    drawable_left_margin = 5
                    drawable_text = rank.name
                }

                text {
                    drawable_layout_id = "tvTag"
                    drawable_max_width = 100
                    drawable_text_size = 8f
                    drawable_text_color = "#ffffff"
                    drawable_gravity = gravity_center
                    drawable_padding_top = 1
                    drawable_padding_bottom = 1
                    drawable_padding_start = 2
                    drawable_padding_end = 2
                    drawable_text = "save"
                    drawable_shape = drawableShape {
                        radius = 4f
                        color = "#8cc8c8c8"
                    }
                    drawable_start_to_start_of = "tvName"
                    drawable_top_to_bottom_of = "tvName"
                }

                image {
                    layout_id = "ivLevel"
                    layout_width = 10
                    layout_height = 10
                    scaleType = scale_fit_xy
                    drawable_center_vertical_of = "tvName"
                    drawable_start_to_end_of = "tvName"
                    drawable_left_margin = 5
                    load(rank.levelUrl)
                }

                text {
                    drawable_layout_id = "tvLevel"
                    drawable_max_width = 200
                    drawable_text_size = 7f
                    drawable_text_color = "#ffffff"
                    drawable_gravity = gravity_center
                    drawable_padding_start = 2
                    drawable_padding_end = 2
                    drawable_shape = drawableShape {
                        color = "#FFC39E"
                        radius = 20f
                    }
                    drawable_center_vertical_of = "tvName"
                    drawable_start_to_end_of = "ivLevel"
                    drawable_left_margin = 5
                    drawable_text = rank.level.toString()
                }

                text {
                    drawable_layout_id = "tvCount"
                    drawable_max_width = 200
                    drawable_text_size = 14f
                    drawable_text_color ="#3F4658"
                    drawable_gravity = gravity_center
                    drawable_center_vertical_of = parent_id
                    drawable_end_to_end_of = parent_id
                    drawable_right_margin = 20
                    drawable_text = rank.count.formatNums()
                }
            }
        }
    }
}

}

class OneRankViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val oneViewGroup = itemView.find(“one”)
val container = itemView.find(“container”)
val tvTitle = oneViewGroup?.findDrawable (“tvTitle”)
val tvRank = oneViewGroup?.findDrawable (“tvRank”)
val tvName = oneViewGroup?.findDrawable (“tvName”)
val tvCount = oneViewGroup?.findDrawable (“tvCount”)
}


代码中沿用了[上一篇]( )中提到的将首屏的多个表项合并成一个表项的方案,动态地为每个主播构建表项并添加到表项容器中。

其中`OneViewGroup.findDrawable()`的作用类似于`View.findViewById()`:

**最后我还整理了很多Android中高级的PDF技术文档。以及一些大厂面试真题解析文档。**

**[CodeChina开源项目地址:https://codechina.csdn.net/m0_60958482/android_p7](https://codechina.csdn.net/m0_60958482/android_p7)**

![image](https://img-blog.csdnimg.cn/img_convert/96a6ceea8caac70c5e359488f8d0d173.png)

Android高级架构师之路很漫长,一起共勉吧!

       drawable_text = rank.count.formatNums()
                    }
                }
            }
        }
    }
}

class OneRankViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val oneViewGroup = itemView.find<OneViewGroup>("one")
    val container = itemView.find<LinearLayout>("container")
    val tvTitle = oneViewGroup?.findDrawable<Text>("tvTitle")
    val tvRank = oneViewGroup?.findDrawable<Text>("tvRank")
    val tvName = oneViewGroup?.findDrawable<Text>("tvName")
    val tvCount = oneViewGroup?.findDrawable<Text>("tvCount")
} 

代码中沿用了上一篇中提到的将首屏的多个表项合并成一个表项的方案,动态地为每个主播构建表项并添加到表项容器中。

其中OneViewGroup.findDrawable()的作用类似于View.findViewById()

最后我还整理了很多Android中高级的PDF技术文档。以及一些大厂面试真题解析文档。

CodeChina开源项目地址:https://codechina.csdn.net/m0_60958482/android_p7

[外链图片转存中…(img-veWzb13B-1630584398582)]

Android高级架构师之路很漫长,一起共勉吧!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 12:02:02  更:2021-09-03 12:02:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 10:44:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码