Android ViewBinding和DataBinding的几个使用方式

举报
芝麻粒儿 发表于 2022/06/26 15:17:10 2022/06/26
【摘要】 👉关于作者众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)欢迎关注公众号【空名先生】获取更多资源和交流! 👉前提这是小空坚持写的Android新手向系列,欢迎...

👉关于作者

众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!

专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)

欢迎关注公众号【空名先生】获取更多资源和交流!

👉前提

这是小空坚持写的Android新手向系列,欢迎品尝。

新手(√√√)

大佬(√)

👉实践过程

😜查找控件的几种方式

说这个之前我们先谈论下有几种查找控件的方式:

  1. findViewById,没错。最原始古老也是最基本的拿到控件的方法,刚学Android的同学一般最先会的就是这个,兼容性好,适用于所有的场景,当你刚学Android或不知道其他方案的时候,使用findViewById绝对没错。除了代码冗余一点,书写麻烦一点(不过也可以在Android Studio插件市场找到自动写findViewById的插件)。
  2. ButterKnife,这是在官方findViewById之后最先出来的第三方框架,需要进行依赖,简化代码,提升效率,增加了代码的可读性,不过也并没有解决和findViewById一样的缺点,而且作者已经三四年没更新,明确说明已经废弃。只不过不妨碍使用,尤其是老程序员以及老项目,不用没关系,看到了一定要知道这是啥。https://github.com/JakeWharton/butterknife
  3. kotlin-android-extensions,不仅要依赖三方库,而且只限于kotlin语言中使用,布局中写好控件,代码中可以直接引用,但是同样官方已经准备废弃了,老项目见到要知道是啥就行。原理还是findViewById。
  4. viewbinding和databinding,这是为了优化findViewById提出的另一个思路。通过视图绑定功能,就可以很轻松地实现与视图交互的代码。当启动后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。我们今天这篇文主要就将这俩。

😜ViewBinding

开始实操前,我们先了解她的优缺点
优点

  • 避免了控件空指针的错误,findViewById中就可能造成空指针或类型转换错误。
  • 代码可读性高
  • 相比DataBinding,更加轻量级

缺点

  • 会生成冗余的很多binding文件,虽然你不需要管理。
  • 灵活性不高,如果需要动态切换布局,则多个布局必须根节点必须一致。
  • 在 Activity、Fragment、Dialog、Adapter 中 ViewBinding 和 DataBinding 初始化方式有些不同
  • 需要单独处理 include 带 merge 标签的布局,和不带 merge 标签的布局等等

配置说明

Android Studio 3.6 版本开始,就内置在 Gradle 插件中了,不需要添加任何额外的库来使用它们,但是在 Android Studio 3.6 和 Android Studio 4.0 中使用方式不一样。
Android Studio3.6以上

android {
    viewBinding {
        enabled = true
    }
}

Android Studio4.0以上

android {
    buildFeatures {
        viewBinding = true
    }
}

如果不想要某个布局文件生成binding,需要添加tools:viewBindingIgnore="true"属性

<RelativeLayout
    ...
    tools:viewBindingIgnore="true" >
  	...
</RelativeLayout>

开启Bingding后,系统会利用驼峰命名法生成对应的绑定类,一Binding为后缀。
例如:布局activity_main.xml,生成的为ActivityMainBinding类

Activity中使用

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
     tools:context=".MainActivity">
    <TextView
       android:id="@+id/testName"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content" />
</LinearLayout>

Java中

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        binding.testName.setText("博客:芝麻粒儿");
    }
}

Kotlin中

上面Java代码中ActivityMainBinding我们没有放到onCreate外面当作全局变量,而事实上,一个控件我们通常在多个地方使用,所以需要全局变量。Java没什么用特别的,在Kotlin中声明的变量都必须在声明的同时对其进行初始化。而这里我们显然无法在声明全局binding变量的同时对它进行初始化,所以这里又使用了lateinit关键字对binding变量进行了延迟初始化。

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.testName.text = "博客:芝麻粒儿"
    }
}

Fragment中使用

需要注意的是为了有效保证binding变量的有效生命周期是在onCreateView()函数和onDestroyView()函数之间,除了在onCreateView创建,也要在onDestroyView及时置空。
Java中

public class MainFragment extends Fragment {

    private FragmentMainBinding binding;

    @Override
    public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        binding = FragmentMainBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}

Kotlin中

class MainFragment : Fragment() {

    private var _binding: FragmentMainBinding? = null

    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}

Adapter中使用

创建一个名为【adapter_item.xml】的布局文件
Kotlin中

/**
 * Created by akitaka on 2022-06-25.
 * @author akitaka
 * @filename MyAdapter
 * @describe
 * @email 960576866@qq.com
 */
class MyAdapter (val params: List<MyModel>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    inner class ViewHolder(binding: AdapterItemBinding) : RecyclerView.ViewHolder(binding.root) {
        val tvName: TextView = binding.fruitName
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = AdapterItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val paramsTemp = params[position]
        holder.tvName.text = paramsTemp.name
    }

    override fun getItemCount() = MyModel.size

}

Java中

/**
 * Created by akitaka on 2022-06-25.
 *
 * @author akitaka
 * @filename MyAdapter
 * @describe
 * @email 960576866@qq.com
 */
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private Context mContext;
    private ArrayList<String> mData;
    private final LayoutInflater inflater;

    public MyAdapter(Context context, ArrayList<String> data) {
        mContext = context;
        mData = data;
        inflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        AdapterItemBinding itemBinding = AdapterItemBinding.inflate(inflater, parent, false);
        return new ViewHolder(itemBinding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.textView.setText(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public ViewHolder(@NonNull AdapterItemBinding itemBinding) {
            super(itemBinding.getRoot());
            textView = itemBinding.textView;
        }
    }
}

自定义View中使用

自定义View的布局文件叫【layout_my_view.xml】

private fun initView() {
    //加载布局
    val binding = LayoutMyViewBinding.inflate(LayoutInflater.from(context), this, true)
    //binding.布局中的控件名即可
}

就这么简单。
在自定义View这有个需要特别提心的,比如横竖屏奇切换布局的时候
竖屏布局

<?xml version="1.0" encoding="utf-8"?>
<cn.zhima.OneView
xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/oneView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

横屏布局

<?xml version="1.0" encoding="utf-8"?>
<cn.zhima.TwoView
xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/twoView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

我们知道binding会根据文件名利用驼峰命名来实现bind类,上面的横竖屏布局文件文件名相同,再切换引用的时候就会出现根元素的 id不一致的报错信息。

Configurations for activity_main.xml must agree on the root element’s ID.

所以我们需要为这两个布局额外嵌套一层相同的ViewGroup,比如RelativeLayout或LinearLayout。之后就可以正常运行了。

Dialog中使用

Dialog dialog = new Dialog(getContext());
DialogTipBinding dialogTipBinding = DialogTipBinding.inflate(LayoutInflater.from(getContext()));
dialog.setContentView(dialogTipBinding.getRoot());
dialogTipBinding.tvTip.setText("弹框提示");
dialog.show();

include和merge标签的使用

布局中可以嵌套include标签,但是如果查找id的话,如果引入的这个布局根不是merge的话则必须include标签需要加上个id。
布局【include_title.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tvTesxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="芝麻粒儿" />
</RelativeLayout>

Activity布局:【activity_include.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".IncludeActivity">
    <include
        android:id="@+id/title"
        layout="@layout/include_title" />
</LinearLayout>

public class IncludeActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityIncludeBinding binding = ActivityIncludeBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.title.tvTesxt.setText("空名先生");
    }
}

merge和include最大的区别在于,使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常代表着更高的运行效率。
titlebar.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Title"
        android:textSize="20sp" />
</merge>

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <include
        layout="@layout/titlebar" />
</LinearLayout>

Kotlin的MainActivity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var titlebarBinding: TitlebarBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        titlebarBinding = TitlebarBinding.bind(binding.root)
        setContentView(binding.root)
        titlebarBinding.title.text = "芝麻粒儿"
    }
}

在onCreate()函数中可以看到,这里我们又定义了一个titlebarBinding变量。不用说大佬们也看出来了,这个TitlebarBinding就是Android Studio根据我们的titlebar.xml布局文件自动生成的Binding类。

😜DataBinding

优点

  • 支持 data 和 view 双向绑定
  • 避免了控件空指针的错误,findViewById中就可能造成空指针或类型转换错误。
  • 效率高,遍历一次就将所有的控件引用生成

缺点

  • 布局必须嵌套一层layout标签,改造成本大
  • 灵活性不高,如果需要动态切换布局,则多个布局必须根节点必须一致。
  • 在 Activity、Fragment、Dialog、Adapter 中 ViewBinding 和 DataBinding 初始化方式有些不同
  • 需要单独处理 include 带 merge 标签的布局,和不带 merge 标签的布局等等

Android Studio3.6以上

android {
    dataBinding{
        enabled = true
    }
}

Android Studio4.0以上

android {
    buildFeatures {
        dataBinding= true
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
	   <!--有个user的实体类-->
       <variable name="user" type="com.zhima.User"/>
   </data>
   
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.name}"/>
           
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.sex}"/>
   </LinearLayout>
</layout>

MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("空名先生", "未知");
binding.setUser(user);

实现了布局和数据的绑定,但也同时增加了对xml的侵入性

😜插件+委托

上面介绍了ViewBinding和DataBinding,需要写的模板代码很多,那么如何省略这些呢?有没有更“骚”的方式?
那就是利用插件

dependencies {
	//最新的到1.0.5版本  地址:https://github.com/hi-dhl/JDataBinding
    implementation 'com.hi-dhl:binding:1.0.5'
}
//Dialog
class AppDialog(context: Context) : Dialog(context, R.style.AppDialog) {
    val binding: DialogAppBinding by viewbind()
    //binding.你的控件名即可
}
//Activity
class MainActivity : AppCompatActivity() {
    // DataBinding
    val binding: ActivityMainBinding by databind(R.layout.activity_main)
    // ViewBinding
    val binding: ActivityMainBinding by viewbind()
    //binding.你的控件名即可
}
//Fragment
class MainFragment : Fragment(R.layout.fragment_main) {
   // DataBinding
   val binding: FragmentMainBinding by databind()
    // ViewBinding
   val binding: FragmentMainBinding by viewbind()
    //binding.你的控件名即可
}

😜总结

ButterKnife,kotlin-android-extensions,viewbinding和databinding,都是成熟方案你都可以用,而且可以同时使用,想用哪个用哪个(只要不嫌乱),但要注意时效性,说不准哪天就被废弃了。而findViewById,从Android诞生一直到现在仍然兼容性最高。

📢作者:小空和小芝中的小空

📢转载说明-务必注明来源:芝麻粒儿 的个人主页 - 专栏 - 掘金 (juejin.cn)

📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。