View Binding的基础使用

10.1 View Binding基础

10.1.1 findViewById 的演进史

传统开发方式的演进

时代 方式 优点 缺点
Android 早期 findViewById 简单直接 类型不安全、运行时错误
ButterKnife 注解绑定 减少样板代码 编译时注解、停止维护
Kotlin Synthetics 插件绑定 简洁 停止维护、作用域问题
Data Binding 数据绑定 功能强大 构建时间长、学习成本高
View Binding 视图绑定 类型安全、轻量 不支持数据绑定

View Binding 的定位

View Binding 是 Android 团队推出的轻量级视图绑定方案,它提供了类型安全的视图访问,同时避免了 Data Binding 的复杂性和性能开销。


10.2 View Binding vs Data Binding vs Kotlin Synthetics

10.2.1 三种方式的对比

详细对比表

特性 View Binding Data Binding Kotlin Synthetics
类型安全 ✅ 是 ✅ 是 ✅ 是
编译时检查 ✅ 是 ✅ 是 ❌ 否
空安全 ✅ 是 ✅ 是 ✅ 是
数据绑定 ❌ 否 ✅ 是 ❌ 否
双向绑定 ❌ 否 ✅ 是 ❌ 否
绑定表达式 ❌ 否 ✅ 是 ❌ 否
Binding Adapters ❌ 否 ✅ 是 ❌ 否
构建时间 ⚡ 快 🐢 慢 ⚡ 快
学习成本 🟢 低 🟡 中 🟢 低
文件大小 🟢 小 🟡 中 🟢 小
维护状态 ✅ 推荐 ✅ 推荐 ❌ 已废弃
性能开销 🟢 低 🟡 中 🟢 低

10.2.2 何时使用 View Binding

使用 View Binding 的场景

场景 是否推荐 原因
简单 UI 交互 ✅ 是 只需要访问 View,无需数据绑定
性能敏感项目 ✅ 是 构建时间快,运行时开销低
团队快速开发 ✅ 是 学习成本低,上手快
现有项目迁移 ✅ 是 迁移成本低,无需大幅修改代码
复杂表单验证 ❌ 否 建议使用 Data Binding
响应式 UI ❌ 否 建议使用 Data Binding

选择建议

  • 只需要访问 View → View Binding
  • 需要数据绑定/响应式 UI → Data Binding
  • 学习新技术 → View Binding(更容易上手)
  • 现有项目 → View Binding(迁移成本低)

10.3 View Binding 快速入门

10.3.1 启用 View Binding

在 build.gradle 中启用

gradle 复制代码
android {
    // ...
    
    buildFeatures {
        viewBinding true
    }
}

模块级别配置

gradle 复制代码
// 为所有模块启用
subprojects {
    afterEvaluate {
        if (it.hasProperty('android')) {
            android {
                buildFeatures {
                    viewBinding true
                }
            }
        }
    }
}

10.3.2 自动生成的绑定类

绑定类命名规则

XML 文件名 生成的绑定类名
activity_main.xml ActivityMainBinding
fragment_home.xml FragmentHomeBinding
item_product.xml ItemProductBinding
dialog_settings.xml DialogSettingsBinding

绑定类的结构

kotlin 复制代码
// 自动生成的 ActivityMainBinding
public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;
  
  @NonNull
  public final TextView tvTitle;
  
  @NonNull
  public final Button btnSubmit;
  
  @NonNull
  public final EditText etName;
  
  private ActivityMainBinding(@NonNull ConstraintLayout rootView, 
                              @NonNull TextView tvTitle, 
                              @NonNull Button btnSubmit, 
                              @NonNull EditText etName) {
    this.rootView = rootView;
    this.tvTitle = tvTitle;
    this.btnSubmit = btnSubmit;
    this.etName = etName;
  }
  
  @Override
  @NonNull
  public ConstraintLayout getRoot() {
    return rootView;
  }
  
  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }
  
  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
                                            @Nullable ViewGroup parent,
                                            boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }
  
  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    // 绑定逻辑
  }
}

10.3.3 在 Activity 中使用

kotlin 复制代码
/**
 * 主 Activity
 */
class MainActivity : AppCompatActivity() {
    
    // 方式1:使用 lateinit var
    private lateinit var binding: ActivityMainBinding
    
    // 方式2:使用 lazy 延迟初始化
    // private val binding: ActivityMainBinding by lazy { 
    //     ActivityMainBinding.inflate(layoutInflater) 
    // }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 使用 View Binding
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        setupViews()
        setupListeners()
    }
    
    /**
     * 设置视图
     */
    private fun setupViews() {
        binding.tvTitle.text = "View Binding 示例"
        binding.etName.hint = "请输入姓名"
    }
    
    /**
     * 设置监听器
     */
    private fun setupListeners() {
        binding.btnSubmit.setOnClickListener {
            val name = binding.etName.text.toString()
            if (name.isNotEmpty()) {
                Toast.makeText(this, "你好,$name!", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "请输入姓名", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

10.3.4 在 Fragment 中使用

kotlin 复制代码
/**
 * 首页 Fragment
 */
class HomeFragment : Fragment() {
    
    // 使用 lazy 延迟初始化
    private var _binding: FragmentHomeBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        setupViews()
        setupListeners()
    }
    
    /**
     * 设置视图
     */
    private fun setupViews() {
        binding.tvTitle.text = "Home Fragment"
        binding.tvDescription.text = "这是一个使用 View Binding 的 Fragment"
    }
    
    /**
     * 设置监听器
     */
    private fun setupListeners() {
        binding.btnNavigate.setOnClickListener {
            val action = HomeFragmentDirections.actionHomeFragmentToDetailFragment()
            findNavController().navigate(action)
        }
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null // 避免内存泄漏
    }
}

10.4 View Binding 高级用法

10.4.1 在 RecyclerView Adapter 中使用

kotlin 复制代码
/**
 * 商品 ViewHolder
 */
class ProductViewHolder(
    private val binding: ItemProductBinding
) : RecyclerView.ViewHolder(binding.root) {
    
    fun bind(product: Product, onProductClick: (Product) -> Unit) {
        // 设置数据
        binding.tvName.text = product.name
        binding.tvPrice.text = "¥${product.price}"
        binding.tvStock.text = if (product.inStock) "有货" else "缺货"
        
        // 加载图片
        Glide.with(binding.ivProduct.context)
            .load(product.imageUrl)
            .placeholder(R.drawable.placeholder)
            .into(binding.ivProduct)
        
        // 设置点击事件
        binding.root.setOnClickListener {
            onProductClick(product)
        }
        
        // 设置库存状态颜色
        binding.tvStock.setTextColor(
            ContextCompat.getColor(
                binding.root.context,
                if (product.inStock) 
                    android.R.color.holo_green_dark 
                else 
                    android.R.color.holo_red_dark
            )
        )
    }
}
kotlin 复制代码
/**
 * 商品 Adapter
 */
class ProductAdapter(
    private val onProductClick: (Product) -> Unit
) : RecyclerView.Adapter<ProductViewHolder>() {
    
    private val products = mutableListOf<Product>()
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
        val binding = ItemProductBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return ProductViewHolder(binding)
    }
    
    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
        holder.bind(products[position], onProductClick)
    }
    
    override fun getItemCount(): Int = products.size
    
    fun submitList(newProducts: List<Product>) {
        products.clear()
        products.addAll(newProducts)
        notifyDataSetChanged()
    }
}
kotlin 复制代码
/**
 * 商品列表 Fragment
 */
class ProductListFragment : Fragment() {
    
    private var _binding: FragmentProductListBinding? = null
    private val binding get() = _binding!!
    
    private lateinit var adapter: ProductAdapter
    private val viewModel: ProductListViewModel by viewModels()
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentProductListBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        setupRecyclerView()
        observeViewModel()
        viewModel.loadProducts()
    }
    
    /**
     * 设置 RecyclerView
     */
    private fun setupRecyclerView() {
        adapter = ProductAdapter { product ->
            viewModel.onProductClick(product)
        }
        
        binding.rvProducts.apply {
            adapter = this@ProductListFragment.adapter
            layoutManager = LinearLayoutManager(requireContext())
            addItemDecoration(
                DividerItemDecoration(
                    requireContext(),
                    DividerItemDecoration.VERTICAL
                )
            )
        }
    }
    
    /**
     * 观察 ViewModel
     */
    private fun observeViewModel() {
        viewModel.products.observe(viewLifecycleOwner) { products ->
            adapter.submitList(products)
            
            // 显示/隐藏空状态
            binding.emptyView.isVisible = products.isEmpty()
            binding.rvProducts.isVisible = products.isNotEmpty()
        }
        
        viewModel.navigateToProductDetail.observe(viewLifecycleOwner) { event ->
            event.getContentIfNotHandled()?.let { product ->
                val action = ProductListFragmentDirections
                    .actionProductListToProductDetail(product.id)
                findNavController().navigate(action)
            }
        }
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

10.4.2 在 Dialog 中使用

kotlin 复制代码
/**
 * 确认对话框
 */
class ConfirmDialog(
    private val title: String,
    private val message: String,
    private val onConfirm: () -> Unit
) : DialogFragment() {
    
    private var _binding: DialogConfirmBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        _binding = DialogConfirmBinding.inflate(layoutInflater)
        
        // 设置内容
        binding.tvTitle.text = title
        binding.tvMessage.text = message
        
        // 设置监听器
        binding.btnCancel.setOnClickListener {
            dismiss()
        }
        
        binding.btnConfirm.setOnClickListener {
            onConfirm()
            dismiss()
        }
        
        // 创建对话框
        return AlertDialog.Builder(requireContext())
            .setView(binding.root)
            .create()
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

10.4.3 在 BottomSheetDialogFragment 中使用

kotlin 复制代码
/**
 * 底部菜单对话框
 */
class BottomSheetMenuFragment : BottomSheetDialogFragment() {
    
    private var _binding: FragmentBottomSheetMenuBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentBottomSheetMenuBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        setupListeners()
    }
    
    /**
     * 设置监听器
     */
    private fun setupListeners() {
        binding.menuProfile.setOnClickListener {
            navigateToProfile()
            dismiss()
        }
        
        binding.menuSettings.setOnClickListener {
            navigateToSettings()
            dismiss()
        }
        
        binding.menuLogout.setOnClickListener {
            logout()
            dismiss()
        }
    }
    
    private fun navigateToProfile() {
        val action = BottomSheetMenuFragmentDirections.actionBottomSheetMenuToProfile()
        findNavController().navigate(action)
    }
    
    private fun navigateToSettings() {
        val action = BottomSheetMenuFragmentDirections.actionBottomSheetMenuToSettings()
        findNavController().navigate(action)
    }
    
    private fun logout() {
        viewModel.logout()
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

10.4.4 在 RecyclerView Item 点击中获取 Binding

kotlin 复制代码
/**
 * 商品 ViewHolder
 */
class ProductViewHolder(
    private val binding: ItemProductBinding
) : RecyclerView.ViewHolder(binding.root) {
    
    fun bind(product: Product, onProductClick: (Product) -> Unit) {
        binding.apply {
            tvName.text = product.name
            tvPrice.text = "¥${product.price}"
            
            Glide.with(ivProduct.context)
                .load(product.imageUrl)
                .into(ivProduct)
            
            root.setOnClickListener {
                onProductClick(product)
            }
        }
    }
}
相关推荐
always_TT几秒前
【Python 字符串格式化:format() 方法】
android·开发语言·python
恋猫de小郭8 分钟前
AI 时代,一个优化 Flutter 的重复代码工具 Deslop
android·前端·flutter
RisunJan22 分钟前
Android 面试知识点整理
android·面试·职场和发展
脚踏实地,坚持不懈!1 小时前
Android ANR 内核底层全解析:从一次触摸到“应用无响应”
android·linux
xhBruce1 小时前
强制使用桌面模式 - SECONDARY_HOME -android-15.0.0_r23
android·launcher3
_祝你今天愉快12 小时前
从驱动来分析服务的添加过程
android
Dovis(誓平步青云)15 小时前
折叠屏悬停看视频,上半屏和下半屏应该各做什么
android·java·服务器·开发语言·安全·音视频
alexhilton16 小时前
让架构边界变成可执行的测试
android·kotlin·android jetpack
hai_android17 小时前
SendChannel 与 ReceiveChannel 通信机制
android
数据治理自习室19 小时前
AI 应用评测体系(GraphRAG)
android·大数据·人工智能·kotlin