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)
            }
        }
    }
}
相关推荐
Xzaveir1 天前
不要用一个状态表示“号码已认证”:企业号码身份的四域模型
android·人工智能
zhangjin11201 天前
AOSP下载
android
Mico181 天前
MySQL 8.0.35 GTID 主从复制搭建-基于GITD
android·mysql·adb
YXL1111YXL1 天前
LeakCanary 源码解析检测泄露工作机制(一)
android·leakcanary
且随疾风前行.1 天前
Android Binder 驱动 - 内核驱动层源码初探
android·网络·binder
额恩661 天前
AI 智能体从零搭建实战教程——扣子
android·rxjava·coze
帅次1 天前
Kotlin Flow 与 StateFlow:UI 单向数据流基础
开发语言·ui·kotlin·stateflow·onlifecycle
hunterandroid1 天前
前台服务适配与线上排查:通知权限、启动限制和任务保活
android·前端
帅次1 天前
Android 高级工程师面试:Flutter 渲染与性能 近1年高频追问 20 题
android·flutter·面试·渲染·性能
糖果店的幽灵1 天前
【langgraph 从入门到精通graphApi 篇】Command 与动态流程控制
android·java·数据库·人工智能·langgraph