[安卓] 深度掌握Android传统View系统:View、布局与关键控件全解析
在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基础
-
View的绘制流程是怎样的?各个阶段的作用是什么?
- 测量(Measure):确定View的尺寸,调用onMeasure()
- 布局(Layout):确定View的位置,调用onLayout()
- 绘制(Draw):绘制View的内容,调用onDraw()
-
MeasureSpec是什么?有哪几种模式?
- MeasureSpec包含测量模式和大小
- EXACTLY:精确值,对应match_parent或具体数值
- AT_MOST:最大值,对应wrap_content
- UNSPECIFIED:未指定,用于ScrollView等
-
invalidate()和requestLayout()有什么区别?
- invalidate():请求重绘,只触发onDraw()
- requestLayout():请求重新测量和布局,触发onMeasure() -> onLayout() -> onDraw()
5.2 自定义View
-
自定义View需要注意哪些问题?
- 正确处理padding和margin
- 支持wrap_content和match_parent
- 在onDraw()中避免创建对象
- 处理触摸事件冲突
- 考虑性能优化
-
如何优化自定义View的性能?
- 使用Canvas.clipRect()减少绘制区域
- 启用硬件加速
- 避免在onDraw()中分配内存
- 使用invalidate(Rect)只重绘脏区域
5.3 ConstraintLayout
-
ConstraintLayout相比其他布局有什么优势?
- 扁平化布局,减少嵌套层级
- 性能更好,测量次数更少
- 更灵活的约束关系
- 支持屏障、引导线、链等高级特性
-
ConstraintLayout中的Chain是什么?有哪几种类型?
- Chain是将多个View连接在一起的约束链
- CHAIN_SPREAD:均匀分布(默认)
- CHAIN_SPREAD_INSIDE:均匀分布,但两端不留空间
- CHAIN_PACKED:打包在一起
-
Guideline和Barrier有什么区别?
- Guideline:虚拟的参考线,用于百分比布局
- Barrier:根据多个View的边界创建屏障,用于动态布局
5.4 RecyclerView
-
RecyclerView的缓存机制是怎样的?
- 四级缓存:Scrap、Cache、ViewCacheExtension、RecycledViewPool
- 支持ViewHolder复用,提高性能
-
RecyclerView的Adapter和ViewHolder模式有什么优势?
- 减少findViewById调用
- 提高滚动性能
- 支持多种View类型
-
DiffUtil的作用是什么?
- 高效计算数据集差异
- 只更新变化的Item
- 支持动画效果
- 减少不必要的绑定
-
如何实现RecyclerView的多类型Item?
- 在getItemViewType()中返回不同的类型
- 在onCreateViewHolder()中创建对应的ViewHolder
- 在onBindViewHolder()中绑定数据
5.5 ViewPager2
-
ViewPager2和ViewPager有什么区别?
- ViewPager2基于RecyclerView实现
- 支持垂直滑动
- 支持RTL布局
- 更好的性能
- 内置DiffUtil支持
-
FragmentStateAdapter和FragmentPagerAdapter有什么区别?
- FragmentStateAdapter:只保留当前Fragment状态,适合大量页面
- FragmentPagerAdapter:保留所有Fragment实例,适合少量固定页面
-
如何处理ViewPager2的预加载和懒加载?
- 设置offscreenPageLimit控制预加载数量
- 在Fragment中使用setUserVisibleHint或Lifecycle实现懒加载
5.6 性能优化
-
如何优化RecyclerView的滚动性能?
- 使用DiffUtil更新数据
- 优化onBindViewHolder中的代码
- 设置合适的缓存大小
- 使用setHasFixedSize(true)
- 避免在滚动时执行耗时操作
-
ConstraintLayout如何帮助提升性能?
- 减少布局层级
- 减少测量次数
- 使用约束链避免嵌套
-
View的过度绘制是什么?如何避免?
- 过度绘制:同一像素被绘制多次
- 避免方法:减少背景重叠、使用clipRect、移除不必要的背景
5.7 事件处理
-
View的事件分发机制是怎样的?
- 顺序:Activity -> Window -> DecorView -> ViewGroup -> View
- 三个关键方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()
-
如何处理滑动冲突?
- 外部拦截法:在父容器的onInterceptTouchEvent()中处理
- 内部拦截法:在子View的dispatchTouchEvent()中配合requestDisallowInterceptTouchEvent()
5.8 实际应用场景
-
你在项目中如何选择不同的布局?
- 简单布局:LinearLayout或RelativeLayout
- 复杂布局:ConstraintLayout
- 列表显示:RecyclerView
- 分页显示:ViewPager2
-
如何实现一个复杂的聊天界面?
- 使用RecyclerView显示消息列表
- 使用多类型Adapter处理文本、图片、语音等消息
- 使用DiffUtil高效更新
- 实现下拉刷新和加载更多
-
如何实现一个图片浏览器?
- 使用ViewPager2显示图片
- 配合PhotoView实现缩放
- 使用Glide或Coil加载图片
- 实现图片缓存和预加载
5.9 兼容性与最佳实践
-
Android不同版本对View系统有哪些重要变化?
- Android 5.0:Material Design引入
- Android 8.0:自适应图标
- Android 10:深色主题
- Android 12:Material You设计语言
-
什么是ViewBinding和DataBinding?有什么区别?
- ViewBinding:生成视图绑定类,类型安全访问视图
- DataBinding:数据绑定框架,支持双向绑定和表达式
- ViewBinding更轻量,DataBinding功能更强大
传统View系统是Android开发的基石,掌握View的工作原理、熟练使用关键控件和布局,对于开发高质量应用至关重要。
更多推荐


所有评论(0)