目录

自定义ViewGroup入门

效果:

布局

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.activity.MainActivity">

    <edu.tyut.helloworld.ui.view.FlowLayout
        android:id="@+id/flowLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_light"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        >
        <TextView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:textSize="18sp"
            android:text="你好"
            android:layout_marginBottom="12dp"
            android:background="@android:color/holo_blue_light"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="你好"
            android:layout_marginTop="12dp"
            android:layout_marginEnd="12dp"
            android:layout_marginStart="12dp"
            android:background="@android:color/holo_blue_light"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="你好"
            android:background="@android:color/holo_blue_light"
            />
        <TextView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:textSize="18sp"
            android:text="你好"
            android:layout_marginBottom="12dp"
            android:layout_marginTop="12dp"
            android:background="@android:color/holo_blue_light"
            />
        <!-- TODO -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="你好"
            android:background="@android:color/holo_blue_light"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="你好"
            android:layout_marginStart="12dp"
            android:padding="14dp"
            android:background="@android:color/holo_blue_light"
            />
        <TextView
            android:id="@+id/more"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="你好"
            android:layout_marginBottom="12dp"
            android:background="@android:color/holo_red_light"
            />
    </edu.tyut.helloworld.ui.view.FlowLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

源码

FlowLayout

kotlin 复制代码
package edu.tyut.helloworld.ui.view

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginBottom
import androidx.core.view.marginEnd
import androidx.core.view.marginStart
import androidx.core.view.marginTop
import kotlin.math.max

private const val TAG: String = "FlowLayout"

class FlowLayout : ViewGroup {

    constructor(context: Context) : this(context = context, attrs = null)

    constructor(context: Context, attrs: AttributeSet?) : this(context = context, attrs = attrs, defStyleAttr = 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ){
        init()
    }

    private fun init(){
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams? {
        return MarginLayoutParams(context, attrs)
    }

    private val rootViewList: MutableList<List<View>> = mutableListOf()
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthMode: Int = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize: Int = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode: Int = MeasureSpec.getMode(heightMeasureSpec)
        var flowLayoutWidth = 0
        var flowLayoutHeight = 0
        var lineWidth = 0
        var lineHeight = 0
        val lineViewList: MutableList<View> = mutableListOf()
        rootViewList.clear()
        for(i in 0 until childCount){
            val childView: View = getChildAt(i)
            childView.tag = i.toString()
            measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0)
            val childWidth: Int = childView.measuredWidth + childView.marginStart + childView.marginEnd
            val childHeight: Int = childView.measuredHeight + childView.marginTop + childView.marginBottom
            if (lineWidth + childWidth > widthSize){ // 超过FlowLayout大小
                lineWidth = childWidth
                lineHeight = childHeight
                flowLayoutHeight += lineHeight
                rootViewList.add(lineViewList.toList()) // copy
                lineViewList.clear()
                lineViewList.add(childView)
            }else{
                lineWidth += childWidth
                lineHeight = max(lineHeight, childHeight)
                lineViewList.add(childView)
            }
            flowLayoutWidth = max(flowLayoutWidth, lineWidth)
            flowLayoutHeight = max(flowLayoutHeight, lineHeight)
        }
        rootViewList.add(lineViewList.toList())
        when(widthMode){
            MeasureSpec.EXACTLY -> {
                flowLayoutWidth = widthSize + paddingStart + paddingEnd
            }
            else -> {
                Log.i(TAG, "onMeasure -> widthMode else...")
            }
        }
        when(heightMode){
            MeasureSpec.EXACTLY -> {
                flowLayoutHeight = flowLayoutHeight + paddingTop + paddingBottom
            }
            else -> {
                Log.i(TAG, "onMeasure -> heightMode else...")
            }
        }
        setMeasuredDimension(flowLayoutWidth, flowLayoutHeight)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        var curTop = 0
        for(line: List<View> in rootViewList){
            var curStart = 0
            var maxLineHeight = 0
            for(item: View in line){
                val left: Int = curStart + item.marginStart
                val top: Int = curTop + item.marginTop
                val right: Int = left + item.measuredWidth
                val bottom: Int = top + item.measuredHeight
                item.layout(left, top, right, bottom)
                maxLineHeight = max(item.measuredHeight + item.marginBottom + item.marginTop, maxLineHeight)
                curStart += item.measuredWidth + item.marginStart + item.marginEnd
            }
            curTop += maxLineHeight
        }
        rootViewList.clear()
    }
}

MainActivity

kotlin 复制代码
private const val TAG: String = "MainActivity"

internal class MainActivity : AppCompatActivity() {

    internal companion object{
        internal fun startActivity(context: Context) {
            context.startActivity(Intent(context, MainActivity::class.java))
        }
    }
    
    private val binding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        Log.i(TAG, "onCreate...")
        initView()
        initObserver()
    }

    private fun initObserver(){
    }

    private fun initView(){
        binding.more.text = "你好".repeat(100)
        binding.more.setOnClickListener {
            captureView(binding.flowLayout)
        }
    }

    private fun captureView(view: View) {
        val bitmap = createBitmap(view.width, view.height)
        val canvas = Canvas(bitmap)
        view.draw(canvas)
        FileOutputStream(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "image.png")).use {
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
            Toast.makeText(this, "截图成功", Toast.LENGTH_SHORT).show()
        }
    }
}

自定义ViewGroup三要素

  1. 继承ViewGroup
  2. 重写onMeasure并且调用子Viewmeasure方法
  3. 确定ViewGroup的大小
  4. 重写ViewGrouponLayout方法, 调用子Viewlayout方法
  5. 打完收工

ViewGroupaddView

java 复制代码
public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }

    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    // addViewInner() will call child.requestLayout() when setting the new LayoutParams
    // therefore, we call requestLayout() on ourselves before, so that the child's request
    // will be blocked at our level
    requestLayout(); // 注册垂直同步消息即mTraversalRunnable
    invalidate(true);
    addViewInner(child, index, params, false); 
}

requestLayout()方法,注册垂直同步消息即mTraversalRunnable 之将子View通过 addViewInner方法添加进ViewGroupView[] mChildren子数组中。 之后开始标准的绘制流程,如果卡顿的话需要检查是否实现有问题了。因为系统会丢弃耗时的mTraversalRunnable.

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
大胃粥2 小时前
Android V app 冷启动(8) 动画结束
android
ufo00l2 小时前
Kotlin在Android中有哪些重要的应用和知识点是需要学习或者重点关注的
android
AJi2 小时前
Android音视频框架探索(二):Binder——系统服务的通信基础
android·ffmpeg·音视频开发
tjsoft3 小时前
Nginx配置伪静态,URL重写
android·运维·nginx
努力学习的小廉3 小时前
【C++11(中)】—— 我与C++的不解之缘(三十一)
android·java·c++
tangweiguo030519873 小时前
打破界限:Android XML与Jetpack Compose深度互操作指南
android·kotlin·compose
Watink Cpper4 小时前
[MySQL初阶]MySQL(8)索引机制:下
android·数据库·b树·mysql·b+树·myisam·innodedb
一起搞IT吧5 小时前
高通camx IOVA内存不足,导致10-15x持续拍照后,点击拍照键定屏无反应,过一会相机闪退
android·数码相机
前行的小黑炭7 小时前
设计模式:为什么使用模板设计模式(不相同的步骤进行抽取,使用不同的子类实现)减少重复代码,让代码更好维护。
android·java·kotlin
ufo00l7 小时前
2025年了,Rxjava解决的用户痛点,是否kotlin协程也能解决,他们各有什么优缺点?
android