在Android开发中,传统View系统是构建用户界面的基础。理解View与ViewGroup的工作原理,掌握RecyclerView、ViewPager2等关键控件以及ConstraintLayout布局,是开发高质量Android应用的必备技能。本文将深入探讨这些核心概念,并通过丰富的Kotlin代码示例展示实际应用。

一、View与ViewGroup基础

1.1 View:UI的基本单元

View是Android UI的基本构建块,代表屏幕上的一个矩形区域,负责绘制和事件处理。所有UI控件都继承自View类。

class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    
    companion object {
        private const val TAG = "CustomView"
    }
    
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.RED
        style = Paint.Style.FILL
        textSize = 48f
    }
    
    private var text: String = "Hello View"
    
    // 1. 测量阶段:确定View的大小
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.d(TAG, "onMeasure被调用")
        
        // 获取测量规格的模式和大小
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        
        var width = 0
        var height = 0
        
        when (widthMode) {
            MeasureSpec.EXACTLY -> {
                // 精确模式:父容器指定了确切大小
                width = widthSize
            }
            MeasureSpec.AT_MOST -> {
                // 最大模式:不能超过指定大小
                val desiredWidth = (paint.measureText(text) + paddingLeft + paddingRight).toInt()
                width = desiredWidth.coerceAtMost(widthSize)
            }
            MeasureSpec.UNSPECIFIED -> {
                // 未指定模式:想要多大就多大
                width = (paint.measureText(text) + paddingLeft + paddingRight).toInt()
            }
        }
        
        when (heightMode) {
            MeasureSpec.EXACTLY -> {
                height = heightSize
            }
            MeasureSpec.AT_MOST -> {
                val desiredHeight = (paint.textSize + paddingTop + paddingBottom).toInt()
                height = desiredHeight.coerceAtMost(heightSize)
            }
            MeasureSpec.UNSPECIFIED -> {
                height = (paint.textSize + paddingTop + paddingBottom).toInt()
            }
        }
        
        // 设置测量结果
        setMeasuredDimension(width, height)
    }
    
    // 2. 布局阶段:确定View的位置
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        Log.d(TAG, "onLayout被调用: left=$left, top=$top, right=$right, bottom=$bottom")
        super.onLayout(changed, left, top, right, bottom)
    }
    
    // 3. 绘制阶段:绘制View的内容
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        Log.d(TAG, "onDraw被调用")
        
        // 绘制背景
        canvas.drawColor(Color.LTGRAY)
        
        // 绘制文本
        val x = paddingLeft.toFloat()
        val y = height / 2f + paint.textSize / 3
        canvas.drawText(text, x, y, paint)
        
        // 绘制边框
        paint.style = Paint.Style.STROKE
        paint.color = Color.BLUE
        paint.strokeWidth = 5f
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
    }
    
    // 4. 事件处理
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(TAG, "触摸按下 at (${event.x}, ${event.y})")
                return true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(TAG, "触摸移动 to (${event.x}, ${event.y})")
            }
            MotionEvent.ACTION_UP -> {
                Log.d(TAG, "触摸抬起")
                performClick()
            }
        }
        return super.onTouchEvent(event)
    }
    
    override fun performClick(): Boolean {
        super.performClick()
        Log.d(TAG, "View被点击")
        text = "Clicked!"
        invalidate() // 请求重绘
        return true
    }
    
    // 5. 焦点处理
    override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
        Log.d(TAG, "焦点变化: $gainFocus")
    }
    
    // 6. 可见性变化
    override fun onVisibilityChanged(changedView: View, visibility: Int) {
        super.onVisibilityChanged(changedView, visibility)
        Log.d(TAG, "可见性变化: $visibility")
    }
    
    // 7. 获取焦点
    override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
        super.onWindowFocusChanged(hasWindowFocus)
        Log.d(TAG, "窗口焦点变化: $hasWindowFocus")
    }
    
    // 自定义方法
    fun setCustomText(newText: String) {
        text = newText
        // 请求重新测量和布局
        requestLayout()
        // 请求重绘
        invalidate()
    }
}

1.2 ViewGroup:View的容器

ViewGroup是View的子类,可以包含其他View(子View),负责管理子View的布局和测量。

class CustomViewGroup(context: Context, attrs: AttributeSet?) : ViewGroup(context, attrs) {
    
    companion object {
        private const val TAG = "CustomViewGroup"
    }
    
    // 测量阶段:确定自己和子View的大小
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.d(TAG, "ViewGroup onMeasure被调用")
        
        // 测量所有子View
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        
        var totalWidth = 0
        var totalHeight = 0
        var maxChildHeight = 0
        
        // 计算总宽度和最大高度(水平排列)
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility != View.GONE) {
                totalWidth += child.measuredWidth
                maxChildHeight = max(maxChildHeight, child.measuredHeight)
            }
        }
        
        totalHeight = maxChildHeight + paddingTop + paddingBottom
        totalWidth += paddingLeft + paddingRight
        
        // 考虑测量规格
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        
        val finalWidth = when (widthMode) {
            MeasureSpec.EXACTLY -> widthSize
            MeasureSpec.AT_MOST -> min(totalWidth, widthSize)
            else -> totalWidth
        }
        
        val finalHeight = when (heightMode) {
            MeasureSpec.EXACTLY -> heightSize
            MeasureSpec.AT_MOST -> min(totalHeight, heightSize)
            else -> totalHeight
        }
        
        setMeasuredDimension(finalWidth, finalHeight)
    }
    
    // 布局阶段:确定子View的位置
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        Log.d(TAG, "ViewGroup onLayout被调用")
        
        var currentLeft = paddingLeft
        
        // 水平排列所有子View
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility != View.GONE) {
                val childWidth = child.measuredWidth
                val childHeight = child.measuredHeight
                
                // 计算子View的位置(垂直居中)
                val childTop = paddingTop + (height - paddingTop - paddingBottom - childHeight) / 2
                val childBottom = childTop + childHeight
                
                child.layout(
                    currentLeft,
                    childTop,
                    currentLeft + childWidth,
                    childBottom
                )
                
                currentLeft += childWidth
            }
        }
    }
    
    // 添加子View
    fun addCustomView(text: String) {
        val textView = TextView(context).apply {
            this.text = text
            setBackgroundColor(Color.CYAN)
            setPadding(16, 8, 16, 8)
            gravity = Gravity.CENTER
        }
        
        addView(textView)
        requestLayout() // 需要重新布局
    }
    
    // 移除子View
    fun removeLastView() {
        if (childCount > 0) {
            removeViewAt(childCount - 1)
            requestLayout()
        }
    }
    
    // 自定义布局参数
    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return CustomLayoutParams(context, attrs)
    }
    
    override fun generateDefaultLayoutParams(): LayoutParams {
        return CustomLayoutParams(
            LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT
        )
    }
    
    override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
        return CustomLayoutParams(p)
    }
    
    override fun checkLayoutParams(p: LayoutParams?): Boolean {
        return p is CustomLayoutParams
    }
    
    // 自定义LayoutParams
    class CustomLayoutParams : MarginLayoutParams {
        
        var customProperty: String = "default"
        
        constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
            // 解析自定义属性
            val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomViewGroup)
            customProperty = typedArray.getString(
                R.styleable.CustomViewGroup_custom_property
            ) ?: "default"
            typedArray.recycle()
        }
        
        constructor(width: Int, height: Int) : super(width, height)
        
        constructor(source: LayoutParams?) : super(source)
    }
}

1.3 View的绘制流程详解

class DrawProcessActivity : AppCompatActivity() {
    
    fun explainDrawProcess() {
        // View的绘制流程分为三个阶段:
        
        // 1. Measure(测量)阶段
        //    - 目的:确定View的尺寸
        //    - 调用:measure() -> onMeasure()
        //    - 关键:MeasureSpec(模式 + 大小)
        //      • EXACTLY:精确值,对应match_parent或具体数值
        //      • AT_MOST:最大值,对应wrap_content
        //      • UNSPECIFIED:未指定,ScrollView等情况
        
        // 2. Layout(布局)阶段
        //    - 目的:确定View的位置
        //    - 调用:layout() -> onLayout()
        //    - 关键:确定四个边界坐标(left, top, right, bottom)
        
        // 3. Draw(绘制)阶段
        //    - 目的:绘制View的内容
        //    - 调用:draw() -> onDraw()
        //    - 顺序:背景 -> 自身内容 -> 子View -> 装饰(如滚动条)
        
        // 触发重绘的方法:
        // - invalidate(): 请求重绘(只在UI线程调用)
        // - postInvalidate(): 在非UI线程请求重绘
        // - requestLayout(): 请求重新测量和布局
        // - forceLayout(): 强制下一次布局时重新测量
    }
    
    fun demonstrateInvalidation() {
        val textView = TextView(this)
        
        // 1. invalidate() - 只重绘,不重新测量
        textView.invalidate() // 会触发onDraw()
        
        // 2. requestLayout() - 重新测量和布局
        textView.requestLayout() // 会触发onMeasure() -> onLayout() -> onDraw()
        
        // 3. 对比:
        // invalidate()用于内容变化但尺寸不变的情况
        // requestLayout()用于尺寸可能变化的情况
        
        // 4. 优化技巧:
        // - 避免在onDraw()中创建对象
        // - 使用Canvas.clipRect()减少绘制区域
        // - 启用硬件加速(默认开启)
        // - 使用View.isHardwareAccelerated()检查
    }
}

二、ConstraintLayout:现代布局王者

2.1 ConstraintLayout基础

ConstraintLayout是Android Studio 2.2引入的新布局,它通过约束关系来定位View,可以创建扁平化的复杂布局,减少嵌套,提升性能。

<!-- 基础ConstraintLayout示例 -->
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <!-- 按钮1:左上角 -->
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"/>
    
    <!-- 按钮2:右上角 -->
    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button 2"
        app:layout_constraintStart_toEndOf="@id/button1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@id/button1"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"/>
    
    <!-- 文本:水平居中,在按钮下方 -->
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello ConstraintLayout!"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/button1"
        android:layout_marginTop="32dp"/>
    
    <!-- 图片:底部居中 -->
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/ic_launcher"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="32dp"/>
    
    <!-- 输入框:在文本下方,占满宽度 -->
    <EditText
        android:id="@+id/editText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Enter text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginTop="16dp"/>
    
</androidx.constraintlayout.widget.ConstraintLayout>

2.2 ConstraintLayout高级特性

class AdvancedConstraintLayoutActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_constraint_advanced)
        
        demonstrateAdvancedFeatures()
    }
    
    private fun demonstrateAdvancedFeatures() {
        val constraintLayout = findViewById<ConstraintLayout>(R.id.constraintLayout)
        
        // 1. 比例约束(Ratio)
        // 可以在XML中使用app:layout_constraintDimensionRatio="H,16:9"
        // 或者在代码中设置
        
        // 2. 屏障(Barrier)
        setupBarrier(constraintLayout)
        
        // 3. 引导线(Guideline)
        setupGuideline(constraintLayout)
        
        // 4. 组(Group)
        setupGroup(constraintLayout)
        
        // 5. 流(Flow)
        setupFlow(constraintLayout)
        
        // 6. 层(Layer)
        setupLayer(constraintLayout)
    }
    
    private fun setupBarrier(constraintLayout: ConstraintLayout) {
        // 创建屏障:根据多个View的最大边界创建虚拟屏障
        val barrier = Barrier(constraintLayout.context).apply {
            id = View.generateViewId()
            type = Barrier.END // 屏障在右侧
            referencedIds = intArrayOf(R.id.button1, R.id.button2)
        }
        
        constraintLayout.addView(barrier)
        
        // 将其他View约束到屏障
        val textView = findViewById<TextView>(R.id.textView)
        val params = textView.layoutParams as ConstraintLayout.LayoutParams
        params.startToEnd = barrier.id
        textView.layoutParams = params
    }
    
    private fun setupGuideline(constraintLayout: ConstraintLayout) {
        // 创建垂直引导线:距离左侧30%
        val guideline = Guideline(constraintLayout.context).apply {
            id = View.generateViewId()
            orientation = Guideline.VERTICAL
        }
        
        constraintLayout.addView(guideline)
        
        val params = guideline.layoutParams as ConstraintLayout.LayoutParams
        params.guidePercent = 0.3f // 30%
        guideline.layoutParams = params
        
        // 将View约束到引导线
        val button = findViewById<Button>(R.id.button3)
        val buttonParams = button.layoutParams as ConstraintLayout.LayoutParams
        buttonParams.endToStart = guideline.id
        button.layoutParams = buttonParams
    }
    
    private fun setupGroup(constraintLayout: ConstraintLayout) {
        // 创建组:同时控制多个View的可见性
        val group = Group(constraintLayout.context).apply {
            id = View.generateViewId()
            referencedIds = intArrayOf(R.id.button1, R.id.button2, R.id.textView)
        }
        
        constraintLayout.addView(group)
        
        // 切换组内所有View的可见性
        group.visibility = View.GONE
    }
    
    private fun setupFlow(constraintLayout: ConstraintLayout) {
        // 创建流:自动排列多个View
        val flow = Flow(constraintLayout.context).apply {
            id = View.generateViewId()
            referencedIds = intArrayOf(R.id.item1, R.id.item2, R.id.item3, R.id.item4, R.id.item5)
            horizontalGap = 16.dpToPx()
            verticalGap = 16.dpToPx()
            wrapMode = Flow.WRAP_CHAIN // 链式换行
            maxElementsWrap = 3 // 每行最多3个
        }
        
        constraintLayout.addView(flow)
        
        val params = flow.layoutParams as ConstraintLayout.LayoutParams
        params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
        params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID
        params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
        params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
        flow.layoutParams = params
    }
    
    private fun setupLayer(constraintLayout: ConstraintLayout) {
        // 创建层:将多个View作为一个整体进行变换
        val layer = Layer(constraintLayout.context).apply {
            id = View.generateViewId()
            referencedIds = intArrayOf(R.id.button1, R.id.button2, R.id.textView)
        }
        
        constraintLayout.addView(layer)
        
        // 对层应用动画
        layer.animate()
            .rotation(45f)
            .scaleX(1.2f)
            .scaleY(1.2f)
            .setDuration(1000)
            .start()
    }
    
    private fun Int.dpToPx(): Int {
        return (this * resources.displayMetrics.density).toInt()
    }
}

2.3 ConstraintLayout性能优化

class ConstraintLayoutPerformanceActivity : AppCompatActivity() {
    
    fun demonstratePerformanceTips() {
        // 1. 减少嵌套层级
        // ConstraintLayout的最大优势是扁平化布局
        
        // 2. 使用Chain(链)优化布局
        setupChains()
        
        // 3. 避免过度约束
        // 每个View应该有最少且足够的约束
        
        // 4. 使用Guideline进行百分比布局
        setupPercentageLayout()
        
        // 5. 使用Group控制多个View
        setupViewGroups()
        
        // 6. 使用Placeholder动态替换内容
        setupPlaceholder()
    }
    
    private fun setupChains() {
        // 链:将多个View连接在一起,形成链式布局
        // 链的模式:
        // - CHAIN_SPREAD:均匀分布(默认)
        // - CHAIN_SPREAD_INSIDE:均匀分布,但两端不留空间
        // - CHAIN_PACKED:打包在一起
        
        val constraintLayout = findViewById<ConstraintLayout>(R.id.constraintLayout)
        
        // 创建水平链
        val button1 = findViewById<Button>(R.id.button1)
        val button2 = findViewById<Button>(R.id.button2)
        val button3 = findViewById<Button>(R.id.button3)
        
        val params1 = button1.layoutParams as ConstraintLayout.LayoutParams
        val params2 = button2.layoutParams as ConstraintLayout.LayoutParams
        val params3 = button3.layoutParams as ConstraintLayout.LayoutParams
        
        // 设置链头
        params1.chainStyle = ConstraintLayout.LayoutParams.CHAIN_SPREAD
        
        // 设置权重(在CHAIN_SPREAD模式下)
        params2.horizontalWeight = 2f
        params3.horizontalWeight = 1f
        
        button1.layoutParams = params1
        button2.layoutParams = params2
        button3.layoutParams = params3
    }
    
    private fun setupPercentageLayout() {
        // 使用百分比进行响应式布局
        val constraintLayout = findViewById<ConstraintLayout>(R.id.constraintLayout)
        
        val guideline1 = Guideline(constraintLayout.context).apply {
            id = View.generateViewId()
            orientation = Guideline.VERTICAL
        }
        
        val guideline2 = Guideline(constraintLayout.context).apply {
            id = View.generateViewId()
            orientation = Guideline.HORIZONTAL
        }
        
        constraintLayout.addView(guideline1)
        constraintLayout.addView(guideline2)
        
        val params1 = guideline1.layoutParams as ConstraintLayout.LayoutParams
        val params2 = guideline2.layoutParams as ConstraintLayout.LayoutParams
        
        // 设置百分比位置
        params1.guidePercent = 0.3f // 30%
        params2.guidePercent = 0.5f // 50%
        
        guideline1.layoutParams = params1
        guideline2.layoutParams = params2
    }
    
    private fun setupViewGroups() {
        // 使用Group提高性能
        val group = findViewById<Group>(R.id.viewGroup)
        
        // 一次性控制多个View的可见性
        group.visibility = View.VISIBLE
        
        // 或者
        group.visibility = View.GONE
    }
    
    private fun setupPlaceholder() {
        // 使用Placeholder动态替换内容
        val placeholder = findViewById<Placeholder>(R.id.placeholder)
        val contentView = findViewById<TextView>(R.id.contentView)
        
        // 将contentView设置到placeholder的位置
        placeholder.setContentId(contentView.id)
        
        // 或者动态设置其他View
        val newView = TextView(this).apply {
            text = "动态内容"
            setBackgroundColor(Color.YELLOW)
        }
        
        // 注意:需要先将newView添加到布局中
        val constraintLayout = findViewById<ConstraintLayout>(R.id.constraintLayout)
        constraintLayout.addView(newView)
        
        placeholder.setContentId(newView.id)
    }
}

三、RecyclerView:高效的列表控件

3.1 RecyclerView基础

RecyclerView是ListView的增强版,通过ViewHolder模式实现高效复用,支持多种布局管理器。

class BasicRecyclerViewActivity : AppCompatActivity() {
    
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: SimpleAdapter
    private val data = mutableListOf<String>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view)
        
        // 初始化数据
        for (i in 1..50) {
            data.add("Item $i")
        }
        
        setupRecyclerView()
        setupItemDecoration()
        setupItemAnimator()
    }
    
    private fun setupRecyclerView() {
        recyclerView = findViewById(R.id.recyclerView)
        
        // 1. 设置布局管理器
        // LinearLayoutManager - 线性布局
        // GridLayoutManager - 网格布局
        // StaggeredGridLayoutManager - 瀑布流布局
        
        recyclerView.layoutManager = LinearLayoutManager(this)
        
        // 2. 设置适配器
        adapter = SimpleAdapter(data)
        recyclerView.adapter = adapter
        
        // 3. 设置固定大小(如果所有Item高度相同)
        recyclerView.setHasFixedSize(true)
        
        // 4. 添加滚动监听
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                val visibleItemCount = layoutManager.childCount
                val totalItemCount = layoutManager.itemCount
                val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
                
                // 实现加载更多
                if (visibleItemCount + firstVisibleItemPosition >= totalItemCount
                    && firstVisibleItemPosition >= 0) {
                    loadMoreData()
                }
            }
        })
    }
    
    private fun setupItemDecoration() {
        // 添加Item装饰(如分割线)
        val dividerItemDecoration = DividerItemDecoration(
            this,
            DividerItemDecoration.VERTICAL
        )
        
        // 自定义分割线
        val drawable = ContextCompat.getDrawable(this, R.drawable.divider)
        drawable?.let {
            dividerItemDecoration.setDrawable(it)
        }
        
        recyclerView.addItemDecoration(dividerItemDecoration)
        
        // 添加间距装饰
        recyclerView.addItemDecoration(
            MarginItemDecoration(
                resources.getDimensionPixelSize(R.dimen.item_margin)
            )
        )
    }
    
    private fun setupItemAnimator() {
        // 设置Item动画
        recyclerView.itemAnimator = DefaultItemAnimator()
        
        // 自定义动画
        recyclerView.itemAnimator = object : DefaultItemAnimator() {
            override fun onAddFinished(item: RecyclerView.ViewHolder) {
                super.onAddFinished(item)
                // 自定义添加完成动画
            }
        }
    }
    
    private fun loadMoreData() {
        // 模拟加载更多数据
        val currentSize = data.size
        val newItems = mutableListOf<String>()
        
        for (i in 1..10) {
            newItems.add("New Item ${currentSize + i}")
        }
        
        data.addAll(newItems)
        adapter.notifyItemRangeInserted(currentSize, newItems.size)
    }
    
    // 简单适配器
    inner class SimpleAdapter(private val items: List<String>) : 
        RecyclerView.Adapter<SimpleAdapter.ViewHolder>() {
        
        inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val textView: TextView = view.findViewById(R.id.textView)
            val deleteButton: Button = view.findViewById(R.id.deleteButton)
        }
        
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_simple, parent, false)
            return ViewHolder(view)
        }
        
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.textView.text = items[position]
            
            // 点击事件
            holder.itemView.setOnClickListener {
                Toast.makeText(
                    this@BasicRecyclerViewActivity,
                    "Clicked: ${items[position]}",
                    Toast.LENGTH_SHORT
                ).show()
            }
            
            // 长按事件
            holder.itemView.setOnLongClickListener {
                showContextMenu(position)
                true
            }
            
            // 删除按钮
            holder.deleteButton.setOnClickListener {
                removeItem(position)
            }
        }
        
        override fun getItemCount(): Int = items.size
    }
    
    private fun showContextMenu(position: Int) {
        // 显示上下文菜单
        val item = data[position]
        val builder = AlertDialog.Builder(this)
            .setTitle("操作")
            .setMessage("对 $item 执行什么操作?")
            .setPositiveButton("删除") { _, _ ->
                removeItem(position)
            }
            .setNegativeButton("取消", null)
            .setNeutralButton("编辑") { _, _ ->
                editItem(position)
            }
        
        builder.show()
    }
    
    private fun removeItem(position: Int) {
        data.removeAt(position)
        adapter.notifyItemRemoved(position)
        
        // 可选:显示撤销操作
        showUndoSnackbar(position)
    }
    
    private fun editItem(position: Int) {
        val builder = AlertDialog.Builder(this)
            .setTitle("编辑项目")
            .setView(EditText(this).apply {
                setText(data[position])
            })
            .setPositiveButton("保存") { dialog, _ ->
                val editText = (dialog as AlertDialog).findViewById<EditText>(android.R.id.edit)
                data[position] = editText?.text.toString()
                adapter.notifyItemChanged(position)
            }
            .setNegativeButton("取消", null)
        
        builder.show()
    }
    
    private fun showUndoSnackbar(position: Int) {
        val removedItem = data[position]
        
        Snackbar.make(recyclerView, "项目已删除", Snackbar.LENGTH_LONG)
            .setAction("撤销") {
                // 撤销删除
                data.add(position, removedItem)
                adapter.notifyItemInserted(position)
                recyclerView.scrollToPosition(position)
            }
            .show()
    }
}

// 自定义Item装饰(间距)
class MarginItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() {
    
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        outRect.left = space
        outRect.right = space
        outRect.bottom = space
        
        // 第一个Item不添加上边距
        if (parent.getChildAdapterPosition(view) == 0) {
            outRect.top = space
        }
    }
}

3.2 RecyclerView高级特性

class AdvancedRecyclerViewActivity : AppCompatActivity() {
    
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: MultiTypeAdapter
    private val data = mutableListOf<Any>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_advanced_recycler)
        
        // 初始化多类型数据
        data.apply {
            add(Header("通讯录"))
            for (i in 1..10) {
                add(Contact("联系人 $i", "13800138${String.format("%03d", i)}"))
            }
            add(Header("最近通话"))
            for (i in 1..5) {
                add(CallRecord("通话记录 $i", "昨天 ${i}:00"))
            }
            add(Header("收藏"))
            for (i in 1..3) {
                add(Favorite("收藏联系人 $i", "⭐".repeat(i)))
            }
        }
        
        setupAdvancedRecyclerView()
        setupSwipeToRefresh()
        setupDragAndDrop()
    }
    
    private fun setupAdvancedRecyclerView() {
        recyclerView = findViewById(R.id.recyclerView)
        
        // 1. 使用ConcatAdapter合并多个适配器
        setupConcatAdapter()
        
        // 2. 使用GridLayoutManager
        // recyclerView.layoutManager = GridLayoutManager(this, 3)
        
        // 3. 使用StaggeredGridLayoutManager(瀑布流)
        setupStaggeredGrid()
        
        // 4. 设置边缘效果
        recyclerView.overScrollMode = View.OVER_SCROLL_ALWAYS
        
        // 5. 使用SnapHelper实现分页效果
        setupSnapHelper()
        
        // 6. 预加载优化
        setupPrefetch()
    }
    
    private fun setupConcatAdapter() {
        // ConcatAdapter允许合并多个适配器
        val headerAdapter = HeaderAdapter()
        val contactAdapter = ContactAdapter()
        val footerAdapter = FooterAdapter()
        
        val concatAdapter = ConcatAdapter(headerAdapter, contactAdapter, footerAdapter)
        recyclerView.adapter = concatAdapter
    }
    
    private fun setupStaggeredGrid() {
        val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
        
        // 设置GapStrategy防止item跳动
        layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE
        
        recyclerView.layoutManager = layoutManager
        
        // 解决瀑布流布局滑动错乱问题
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                layoutManager.invalidateSpanAssignments()
            }
        })
    }
    
    private fun setupSnapHelper() {
        // PagerSnapHelper - 像ViewPager一样分页
        // LinearSnapHelper - 居中停靠
        
        val snapHelper = PagerSnapHelper()
        snapHelper.attachToRecyclerView(recyclerView)
        
        // 添加页面指示器
        setupPageIndicator()
    }
    
    private fun setupPageIndicator() {
        val pageIndicator = findViewById<LinearLayout>(R.id.pageIndicator)
        
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
                
                // 更新页面指示器
                updatePageIndicator(pageIndicator, firstVisibleItem)
            }
        })
    }
    
    private fun setupPrefetch() {
        // 启用预取优化
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            recyclerView.setItemViewCacheSize(20) // 增加缓存大小
            recyclerView.isDrawingCacheEnabled = true
            recyclerView.drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH
        }
        
        // 使用RecycledViewPool共享ViewHolder
        val viewPool = RecyclerView.RecycledViewPool()
        recyclerView.setRecycledViewPool(viewPool)
        viewPool.setMaxRecycledViews(0, 20) // ViewType为0的最大缓存数
    }
    
    private fun setupSwipeToRefresh() {
        val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.swipeRefreshLayout)
        
        swipeRefreshLayout.setOnRefreshListener {
            // 模拟刷新数据
            Handler(Looper.getMainLooper()).postDelayed({
                refreshData()
                swipeRefreshLayout.isRefreshing = false
            }, 2000)
        }
        
        // 自定义刷新样式
        swipeRefreshLayout.setColorSchemeResources(
            android.R.color.holo_blue_bright,
            android.R.color.holo_green_light,
            android.R.color.holo_orange_light,
            android.R.color.holo_red_light
        )
    }
    
    private fun setupDragAndDrop() {
        // 启用拖拽排序
        val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
            
            override fun getMovementFlags(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder
            ): Int {
                // 允许上下拖拽,左右滑动删除
                val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
                val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
                return makeMovementFlags(dragFlags, swipeFlags)
            }
            
            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean {
                val fromPosition = viewHolder.adapterPosition
                val toPosition = target.adapterPosition
                
                // 交换数据位置
                Collections.swap(data, fromPosition, toPosition)
                adapter.notifyItemMoved(fromPosition, toPosition)
                return true
            }
            
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                val position = viewHolder.adapterPosition
                data.removeAt(position)
                adapter.notifyItemRemoved(position)
            }
            
            override fun isLongPressDragEnabled(): Boolean = true
            
            override fun isItemViewSwipeEnabled(): Boolean = true
            
            override fun onSelectedChanged(
                viewHolder: RecyclerView.ViewHolder?, 
                actionState: Int
            ) {
                super.onSelectedChanged(viewHolder, actionState)
                if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
                    viewHolder?.itemView?.alpha = 0.7f
                }
            }
            
            override fun clearView(
                recyclerView: RecyclerView, 
                viewHolder: RecyclerView.ViewHolder
            ) {
                super.clearView(recyclerView, viewHolder)
                viewHolder.itemView.alpha = 1.0f
            }
        })
        
        itemTouchHelper.attachToRecyclerView(recyclerView)
    }
    
    // 多类型适配器
    inner class MultiTypeAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        
        companion object {
            private const val TYPE_HEADER = 0
            private const val TYPE_CONTACT = 1
            private const val TYPE_CALL_RECORD = 2
            private const val TYPE_FAVORITE = 3
        }
        
        override fun getItemViewType(position: Int): Int {
            return when (data[position]) {
                is Header -> TYPE_HEADER
                is Contact -> TYPE_CONTACT
                is CallRecord -> TYPE_CALL_RECORD
                is Favorite -> TYPE_FAVORITE
                else -> throw IllegalArgumentException("未知类型")
            }
        }
        
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            return when (viewType) {
                TYPE_HEADER -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_header, parent, false)
                    HeaderViewHolder(view)
                }
                TYPE_CONTACT -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_contact, parent, false)
                    ContactViewHolder(view)
                }
                TYPE_CALL_RECORD -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_call_record, parent, false)
                    CallRecordViewHolder(view)
                }
                TYPE_FAVORITE -> {
                    val view = LayoutInflater.from(parent.context)
                        .inflate(R.layout.item_favorite, parent, false)
                    FavoriteViewHolder(view)
                }
                else -> throw IllegalArgumentException("未知ViewType: $viewType")
            }
        }
        
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            when (val item = data[position]) {
                is Header -> (holder as HeaderViewHolder).bind(item)
                is Contact -> (holder as ContactViewHolder).bind(item)
                is CallRecord -> (holder as CallRecordViewHolder).bind(item)
                is Favorite -> (holder as FavoriteViewHolder).bind(item)
            }
        }
        
        override fun getItemCount(): Int = data.size
        
        // ViewHolder类
        inner class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            private val titleTextView: TextView = view.findViewById(R.id.titleTextView)
            
            fun bind(header: Header) {
                titleTextView.text = header.title
            }
        }
        
        inner class ContactViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            private val nameTextView: TextView = view.findViewById(R.id.nameTextView)
            private val phoneTextView: TextView = view.findViewById(R.id.phoneTextView)
            
            fun bind(contact: Contact) {
                nameTextView.text = contact.name
                phoneTextView.text = contact.phone
            }
        }
        
        // 其他ViewHolder类似...
    }
    
    // 数据类
    data class Header(val title: String)
    data class Contact(val name: String, val phone: String)
    data class CallRecord(val name: String, val time: String)
    data class Favorite(val name: String, val stars: String)
}

3.3 RecyclerView性能优化

class RecyclerViewPerformanceActivity : AppCompatActivity() {
    
    fun demonstratePerformanceOptimizations() {
        // 1. 使用ViewHolder模式
        // 这是RecyclerView的核心优化,避免重复findViewById
        
        // 2. 使用DiffUtil进行高效更新
        setupDiffUtil()
        
        // 3. 使用ListAdapter自动处理DiffUtil
        setupListAdapter()
        
        // 4. 优化onBindViewHolder
        optimizeOnBindViewHolder()
        
        // 5. 使用AsyncListDiffer
        setupAsyncListDiffer()
        
        // 6. 内存优化
        optimizeMemoryUsage()
    }
    
    private fun setupDiffUtil() {
        val oldList = listOf("A", "B", "C", "D")
        val newList = listOf("A", "C", "D", "E", "F")
        
        val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize(): Int = oldList.size
            
            override fun getNewListSize(): Int = newList.size
            
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return oldList[oldItemPosition] == newList[newItemPosition]
            }
            
            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return oldList[oldItemPosition] == newList[newItemPosition]
            }
            
            override fun getChangePayload(
                oldItemPosition: Int, 
                newItemPosition: Int
            ): Any? {
                // 返回变化的部分,用于部分更新
                return "payload"
            }
        })
        
        // 使用diffResult更新适配器
        // adapter.updateList(newList)
        // diffResult.dispatchUpdatesTo(adapter)
    }
    
    private fun setupListAdapter() {
        // ListAdapter内部使用AsyncListDiffer
        val adapter = object : ListAdapter<String, RecyclerView.ViewHolder>(DIFF_CALLBACK) {
            
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                // 创建ViewHolder
                return object : RecyclerView.ViewHolder(
                    TextView(parent.context)
                ) {}
            }
            
            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = getItem(position)
            }
            
            companion object {
                private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
                    override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
                        return oldItem == newItem
                    }
                    
                    override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
                        return oldItem == newItem
                    }
                }
            }
        }
    }
    
    private fun optimizeOnBindViewHolder() {
        // 优化技巧:
        // 1. 避免在onBindViewHolder中创建对象
        // 2. 使用数据绑定或ViewBinding
        // 3. 异步加载图片
        // 4. 使用payload进行部分更新
    }
    
    private fun setupAsyncListDiffer() {
        // AsyncListDiffer在后台线程计算Diff
        val asyncDiffer = AsyncListDiffer(
            object : ListUpdateCallback {
                override fun onInserted(position: Int, count: Int) {
                    // 处理插入
                }
                
                override fun onRemoved(position: Int, count: Int) {
                    // 处理删除
                }
                
                override fun onMoved(fromPosition: Int, toPosition: Int) {
                    // 处理移动
                }
                
                override fun onChanged(position: Int, count: Int, payload: Any?) {
                    // 处理变化
                }
            },
            AsyncDifferConfig.Builder(StringDiffCallback())
                .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
                .build()
        )
    }
    
    private fun optimizeMemoryUsage() {
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        
        // 1. 设置合适的缓存大小
        recyclerView.setItemViewCacheSize(20)
        
        // 2. 回收池配置
        val recycledViewPool = RecyclerView.RecycledViewPool()
        recycledViewPool.setMaxRecycledViews(0, 10) // ViewType 0 缓存10个
        
        // 3. 使用setHasFixedSize(true)
        recyclerView.setHasFixedSize(true)
        
        // 4. 移除不必要的ItemDecoration
        // recyclerView.removeItemDecorationAt(0)
        
        // 5. 使用弱引用或软引用持有大数据
        val weakData = WeakReference<Any>(BigDataObject())
    }
    
    class StringDiffCallback : DiffUtil.ItemCallback<String>() {
        override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
            return oldItem == newItem
        }
        
        override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
            return oldItem == newItem
        }
    }
}

四、ViewPager2:现代化的页面切换

4.1 ViewPager2基础

ViewPager2是ViewPager的替代品,基于RecyclerView实现,支持垂直滑动和RTL布局。

class BasicViewPager2Activity : AppCompatActivity() {
    
    private lateinit var viewPager2: ViewPager2
    private lateinit var adapter: ImageAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_viewpager2)
        
        setupViewPager2()
        setupTabLayout()
        setupPageTransformer()
    }
    
    private fun setupViewPager2() {
        viewPager2 = findViewById(R.id.viewPager2)
        
        // 1. 设置适配器
        adapter = ImageAdapter()
        viewPager2.adapter = adapter
        
        // 2. 设置方向
        viewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL
        // viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
        
        // 3. 设置预加载页面数
        viewPager2.offscreenPageLimit = 1
        
        // 4. 禁用用户滑动(仍然可以通过代码控制)
        // viewPager2.isUserInputEnabled = false
        
        // 5. 注册页面变化回调
        viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                Log.d("ViewPager2", "页面选择: $position")
            }
            
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels)
                // 处理滚动
            }
            
            override fun onPageScrollStateChanged(state: Int) {
                super.onPageScrollStateChanged(state)
                // IDLE, DRAGGING, SETTLING
            }
        })
        
        // 6. 设置当前页面
        viewPager2.setCurrentItem(1, false) // 无动画
        // viewPager2.setCurrentItem(1, true) // 有动画
        
        // 7. 获取当前页面
        val currentItem = viewPager2.currentItem
    }
    
    private fun setupTabLayout() {
        val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
        
        // 使用TabLayoutMediator关联TabLayout和ViewPager2
        TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
            tab.text = "页面 ${position + 1}"
        }.attach()
        
        // 自定义Tab
        tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                // Tab被选中
                tab?.customView = createCustomTabView(true)
            }
            
            override fun onTabUnselected(tab: TabLayout.Tab?) {
                // Tab取消选中
                tab?.customView = createCustomTabView(false)
            }
            
            override fun onTabReselected(tab: TabLayout.Tab?) {
                // Tab重新选中(再次点击)
            }
        })
    }
    
    private fun createCustomTabView(isSelected: Boolean): View {
        val view = LayoutInflater.from(this).inflate(R.layout.custom_tab, null)
        val textView = view.findViewById<TextView>(R.id.tabTextView)
        val indicator = view.findViewById<View>(R.id.tabIndicator)
        
        textView.text = if (isSelected) "选中" else "未选"
        indicator.visibility = if (isSelected) View.VISIBLE else View.INVISIBLE
        
        return view
    }
    
    private fun setupPageTransformer() {
        // 1. 深度变换
        viewPager2.setPageTransformer(DepthPageTransformer())
        
        // 2. 缩放变换
        // viewPager2.setPageTransformer(ScaleInTransformer())
        
        // 3. 组合变换
        viewPager2.setPageTransformer(CompositePageTransformer().apply {
            addTransformer(DepthPageTransformer())
            addTransformer(MarginPageTransformer(resources.getDimensionPixelSize(R.dimen.page_margin)))
        })
    }
    
    // 图片适配器
    inner class ImageAdapter : RecyclerView.Adapter<ImageAdapter.ViewHolder>() {
        
        private val images = listOf(
            R.drawable.image1,
            R.drawable.image2,
            R.drawable.image3,
            R.drawable.image4
        )
        
        inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val imageView: ImageView = view.findViewById(R.id.imageView)
        }
        
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_image, parent, false)
            return ViewHolder(view)
        }
        
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.imageView.setImageResource(images[position])
            
            // 添加点击事件
            holder.itemView.setOnClickListener {
                Toast.makeText(
                    this@BasicViewPager2Activity,
                    "点击了图片 ${position + 1}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
        
        override fun getItemCount(): Int = images.size
    }
    
    // 深度变换器
    class DepthPageTransformer : ViewPager2.PageTransformer {
        private val minScale = 0.75f
        
        override fun transformPage(page: View, position: Float) {
            when {
                position <= 0 -> { // [-Infinity,0]
                    // 左边的页面或当前页面
                    page.translationX = 0f
                    page.scaleX = 1f
                    page.scaleY = 1f
                    page.alpha = 1f
                }
                position <= 1 -> { // (0,1]
                    // 右边的页面
                    page.translationX = -page.width * position
                    val scaleFactor = minScale + (1 - minScale) * (1 - abs(position))
                    page.scaleX = scaleFactor
                    page.scaleY = scaleFactor
                    page.alpha = 1 - position
                }
                else -> { // (1,+Infinity]
                    // 更远的右边页面
                    page.alpha = 0f
                }
            }
        }
    }
}

4.2 ViewPager2与Fragment

class FragmentViewPager2Activity : AppCompatActivity() {
    
    private lateinit var viewPager2: ViewPager2
    private lateinit var adapter: FragmentStateAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fragment_viewpager)
        
        setupFragmentViewPager()
        setupFragmentCommunication()
    }
    
    private fun setupFragmentViewPager() {
        viewPager2 = findViewById(R.id.viewPager2)
        
        // 创建FragmentStateAdapter
        adapter = object : FragmentStateAdapter(this) {
            
            private val fragments = listOf(
                PageFragment.newInstance("页面 1"),
                PageFragment.newInstance("页面 2"),
                PageFragment.newInstance("页面 3"),
                PageFragment.newInstance("页面 4")
            )
            
            override fun getItemCount(): Int = fragments.size
            
            override fun createFragment(position: Int): Fragment {
                return fragments[position]
            }
        }
        
        viewPager2.adapter = adapter
        
        // 设置页面预加载
        viewPager2.offscreenPageLimit = 1
        
        // 使用FragmentStateAdapter时,确保FragmentManager生命周期一致
        adapter.addUpdateCallback {
            // 数据集变化时的回调
        }
    }
    
    private fun setupFragmentCommunication() {
        // Fragment与Activity通信
        supportFragmentManager.setFragmentResultListener(
            "requestKey",
            this
        ) { requestKey, bundle ->
            // 处理来自Fragment的结果
            val result = bundle.getString("data")
            Log.d("FragmentCommunication", "收到结果: $result")
        }
        
        // Activity向Fragment发送数据
        val bundle = Bundle().apply {
            putString("message", "来自Activity的消息")
        }
        supportFragmentManager.setFragmentResult("messageKey", bundle)
    }
    
    // 懒加载Fragment
    class LazyFragment : Fragment() {
        
        private var isLoaded = false
        
        override fun onResume() {
            super.onResume()
            
            // 检查是否在ViewPager2中
            val isInViewPager = parentFragment != null
            
            if (!isLoaded && userVisibleHint) {
                loadData()
                isLoaded = true
            }
        }
        
        override fun setUserVisibleHint(isVisibleToUser: Boolean) {
            super.setUserVisibleHint(isVisibleToUser)
            
            if (isVisibleToUser && isResumed && !isLoaded) {
                loadData()
                isLoaded = true
            }
        }
        
        private fun loadData() {
            // 加载数据
            Log.d("LazyFragment", "懒加载数据")
        }
    }
}

// 页面Fragment
class PageFragment : Fragment() {
    
    companion object {
        private const val ARG_TITLE = "title"
        
        fun newInstance(title: String): PageFragment {
            val fragment = PageFragment()
            val args = Bundle()
            args.putString(ARG_TITLE, title)
            fragment.arguments = args
            return fragment
        }
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_page, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        val title = arguments?.getString(ARG_TITLE) ?: "默认标题"
        view.findViewById<TextView>(R.id.titleTextView).text = title
        
        // 发送数据回Activity
        view.findViewById<Button>(R.id.sendButton).setOnClickListener {
            val result = Bundle().apply {
                putString("data", "来自Fragment的数据")
            }
            parentFragmentManager.setFragmentResult("requestKey", result)
        }
    }
}

4.3 ViewPager2嵌套与性能优化

class NestedViewPager2Activity : AppCompatActivity() {
    
    fun demonstrateNestedViewPager() {
        // 1. 外层ViewPager2(水平)
        val outerViewPager = findViewById<ViewPager2>(R.id.outerViewPager)
        
        // 2. 内层ViewPager2(垂直)
        val innerViewPager = findViewById<ViewPager2>(R.id.innerViewPager)
        
        // 3. 处理滑动冲突
        handleScrollConflict(outerViewPager, innerViewPager)
        
        // 4. 优化性能
        optimizeNestedPerformance()
    }
    
    private fun handleScrollConflict(
        outerViewPager: ViewPager2,
        innerViewPager: ViewPager2
    ) {
        // 方案1:禁用内层ViewPager的滑动
        // innerViewPager.isUserInputEnabled = false
        
        // 方案2:使用自定义触摸事件处理
        innerViewPager.setOnTouchListener { v, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    // 记录触摸位置
                    v.parent.requestDisallowInterceptTouchEvent(true)
                }
                MotionEvent.ACTION_MOVE -> {
                    // 判断滑动方向
                    val deltaX = abs(event.x - event.historicalX)
                    val deltaY = abs(event.y - event.historicalY)
                    
                    if (deltaY > deltaX) {
                        // 垂直滑动,内层处理
                        v.parent.requestDisallowInterceptTouchEvent(true)
                    } else {
                        // 水平滑动,外层处理
                        v.parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
                MotionEvent.ACTION_UP,
                MotionEvent.ACTION_CANCEL -> {
                    v.parent.requestDisallowInterceptTouchEvent(false)
                }
            }
            v.onTouchEvent(event)
        }
    }
    
    private fun optimizeNestedPerformance() {
        val viewPager = findViewById<ViewPager2>(R.id.viewPager)
        
        // 1. 减少页面预加载
        viewPager.offscreenPageLimit = 1
        
        // 2. 使用FragmentStateAdapter替代FragmentPagerAdapter
        // FragmentStateAdapter只保留当前页面的状态
        
        // 3. 优化Fragment生命周期
        setupFragmentLifecycleOptimization()
        
        // 4. 使用ViewPager2的保存状态功能
        viewPager.saveEnabled = true
        
        // 5. 懒加载数据
        setupLazyLoading()
    }
    
    private fun setupFragmentLifecycleOptimization() {
        // 在Fragment中:
        // - onViewCreated()中初始化视图
        // - onResume()中开始加载数据
        // - onPause()中暂停耗时操作
        // - onDestroyView()中释放资源
    }
    
    private fun setupLazyLoading() {
        // 实现懒加载:
        // 1. 使用setUserVisibleHint()(已过时)
        // 2. 使用Fragment的onResume()结合ViewPager2的位置判断
        // 3. 使用LifecycleOwner和LiveData
    }
}

五、面试常见问题

5.1 View与ViewGroup基础

  1. View的绘制流程是怎样的?各个阶段的作用是什么?

    • 测量(Measure):确定View的尺寸,调用onMeasure()
    • 布局(Layout):确定View的位置,调用onLayout()
    • 绘制(Draw):绘制View的内容,调用onDraw()
  2. MeasureSpec是什么?有哪几种模式?

    • MeasureSpec包含测量模式和大小
    • EXACTLY:精确值,对应match_parent或具体数值
    • AT_MOST:最大值,对应wrap_content
    • UNSPECIFIED:未指定,用于ScrollView等
  3. invalidate()和requestLayout()有什么区别?

    • invalidate():请求重绘,只触发onDraw()
    • requestLayout():请求重新测量和布局,触发onMeasure() -> onLayout() -> onDraw()

5.2 自定义View

  1. 自定义View需要注意哪些问题?

    • 正确处理padding和margin
    • 支持wrap_content和match_parent
    • 在onDraw()中避免创建对象
    • 处理触摸事件冲突
    • 考虑性能优化
  2. 如何优化自定义View的性能?

    • 使用Canvas.clipRect()减少绘制区域
    • 启用硬件加速
    • 避免在onDraw()中分配内存
    • 使用invalidate(Rect)只重绘脏区域

5.3 ConstraintLayout

  1. ConstraintLayout相比其他布局有什么优势?

    • 扁平化布局,减少嵌套层级
    • 性能更好,测量次数更少
    • 更灵活的约束关系
    • 支持屏障、引导线、链等高级特性
  2. ConstraintLayout中的Chain是什么?有哪几种类型?

    • Chain是将多个View连接在一起的约束链
    • CHAIN_SPREAD:均匀分布(默认)
    • CHAIN_SPREAD_INSIDE:均匀分布,但两端不留空间
    • CHAIN_PACKED:打包在一起
  3. Guideline和Barrier有什么区别?

    • Guideline:虚拟的参考线,用于百分比布局
    • Barrier:根据多个View的边界创建屏障,用于动态布局

5.4 RecyclerView

  1. RecyclerView的缓存机制是怎样的?

    • 四级缓存:Scrap、Cache、ViewCacheExtension、RecycledViewPool
    • 支持ViewHolder复用,提高性能
  2. RecyclerView的Adapter和ViewHolder模式有什么优势?

    • 减少findViewById调用
    • 提高滚动性能
    • 支持多种View类型
  3. DiffUtil的作用是什么?

    • 高效计算数据集差异
    • 只更新变化的Item
    • 支持动画效果
    • 减少不必要的绑定
  4. 如何实现RecyclerView的多类型Item?

    • 在getItemViewType()中返回不同的类型
    • 在onCreateViewHolder()中创建对应的ViewHolder
    • 在onBindViewHolder()中绑定数据

5.5 ViewPager2

  1. ViewPager2和ViewPager有什么区别?

    • ViewPager2基于RecyclerView实现
    • 支持垂直滑动
    • 支持RTL布局
    • 更好的性能
    • 内置DiffUtil支持
  2. FragmentStateAdapter和FragmentPagerAdapter有什么区别?

    • FragmentStateAdapter:只保留当前Fragment状态,适合大量页面
    • FragmentPagerAdapter:保留所有Fragment实例,适合少量固定页面
  3. 如何处理ViewPager2的预加载和懒加载?

    • 设置offscreenPageLimit控制预加载数量
    • 在Fragment中使用setUserVisibleHint或Lifecycle实现懒加载

5.6 性能优化

  1. 如何优化RecyclerView的滚动性能?

    • 使用DiffUtil更新数据
    • 优化onBindViewHolder中的代码
    • 设置合适的缓存大小
    • 使用setHasFixedSize(true)
    • 避免在滚动时执行耗时操作
  2. ConstraintLayout如何帮助提升性能?

    • 减少布局层级
    • 减少测量次数
    • 使用约束链避免嵌套
  3. View的过度绘制是什么?如何避免?

    • 过度绘制:同一像素被绘制多次
    • 避免方法:减少背景重叠、使用clipRect、移除不必要的背景

5.7 事件处理

  1. View的事件分发机制是怎样的?

    • 顺序:Activity -> Window -> DecorView -> ViewGroup -> View
    • 三个关键方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()
  2. 如何处理滑动冲突?

    • 外部拦截法:在父容器的onInterceptTouchEvent()中处理
    • 内部拦截法:在子View的dispatchTouchEvent()中配合requestDisallowInterceptTouchEvent()

5.8 实际应用场景

  1. 你在项目中如何选择不同的布局?

    • 简单布局:LinearLayout或RelativeLayout
    • 复杂布局:ConstraintLayout
    • 列表显示:RecyclerView
    • 分页显示:ViewPager2
  2. 如何实现一个复杂的聊天界面?

    • 使用RecyclerView显示消息列表
    • 使用多类型Adapter处理文本、图片、语音等消息
    • 使用DiffUtil高效更新
    • 实现下拉刷新和加载更多
  3. 如何实现一个图片浏览器?

    • 使用ViewPager2显示图片
    • 配合PhotoView实现缩放
    • 使用Glide或Coil加载图片
    • 实现图片缓存和预加载

5.9 兼容性与最佳实践

  1. Android不同版本对View系统有哪些重要变化?

    • Android 5.0:Material Design引入
    • Android 8.0:自适应图标
    • Android 10:深色主题
    • Android 12:Material You设计语言
  2. 什么是ViewBinding和DataBinding?有什么区别?

    • ViewBinding:生成视图绑定类,类型安全访问视图
    • DataBinding:数据绑定框架,支持双向绑定和表达式
    • ViewBinding更轻量,DataBinding功能更强大

传统View系统是Android开发的基石,掌握View的工作原理、熟练使用关键控件和布局,对于开发高质量应用至关重要。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐