在build.gradle(Module)中添加viewBinding元素后,Android会自动给模块中的每个XML布局文件生成一个相应的Binding类,该Binding类名称为XML布局文件驼峰式大写+Binding后缀。以如下所示的activity_welcome.xml文件为例,对应的ActivityWelcomeBinding.java的源代码如下所示。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/welcome_container" android:orientation="vertical" android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Please choose an item" android:textSize="30dp" android:gravity="center"/> <LinearLayout android:id="@+id/button_container" android:layout_marginTop="30dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="GEOGRAPHY" android:id="@+id/btn_geo"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="MATHEMATICS" android:id="@+id/btn_math"/> </LinearLayout> </LinearLayout>activity_welcome.xml
// Generated by view binder compiler. Do not edit! package com.larissa.android.quiz.databinding; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.viewbinding.ViewBinding; import androidx.viewbinding.ViewBindings; import com.larissa.android.quiz.R; import java.lang.NullPointerException; import java.lang.Override; import java.lang.String; // ActivityWelcomeBinding是final类,实现ViewBinding接口 public final class ActivityWelcomeBinding implements ViewBinding { // XML布局文件中的根元素被定义为一个私有的常量字段 @NonNull private final LinearLayout rootView; // XML布局文件中有id的控件被定义为公有的常量字段 @NonNull public final Button btnGeo; @NonNull public final Button btnMath; @NonNull public final LinearLayout buttonContainer; @NonNull public final LinearLayout welcomeContainer; // ActivityWelcomeBinding的构造函数为私有的,即ActivityWelcomeBinding不能在类的外部通过new实例化 // 构造函数的参数为上方所定义的常量字段 private ActivityWelcomeBinding(@NonNull LinearLayout rootView, @NonNull Button btnGeo, @NonNull Button btnMath, @NonNull LinearLayout buttonContainer, @NonNull LinearLayout welcomeContainer) { this.rootView = rootView; this.btnGeo = btnGeo; this.btnMath = btnMath; this.buttonContainer = buttonContainer; this.welcomeContainer = welcomeContainer; } // 实现ViewBinding接口中定义的getRoot()方法,其实这就是rootView字段的getter方法 @Override @NonNull public LinearLayout getRoot() { return rootView; } // 返回ActivityWelcomeBinding实例的两个inflate()方法。可见,inflate(LayoutInflater)方法 // 其实是调用了inflate(LayoutInflater, ViewGroup, boolean)方法 @NonNull public static ActivityWelcomeBinding inflate(@NonNull LayoutInflater inflater) { return inflate(inflater, null, false); } @NonNull public static ActivityWelcomeBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) { // 调用LayoutInflater.inflate()方法,返回解析XML布局文件得到的View对象/View树 View root = inflater.inflate(R.layout.activity_welcome, parent, false); // 如果attachToParent为true,则将View对象贴parent上 if (attachToParent) { parent.addView(root); } return bind(root); } // bind()方法其实是获取ActivityWelcomeBinding实例的最终方法 @NonNull public static ActivityWelcomeBinding bind(@NonNull View rootView) { // The body of this method is generated in a way you would not otherwise write. // This is done to optimize the compiled bytecode for size and performance. int id; missingId: { id = R.id.btn_geo; // 从rootView树中查找指定id的控件 Button btnGeo = ViewBindings.findChildViewById(rootView, id); // 如果从rootView树中没有找到指定id的控件,则跳出missingId label if (btnGeo == null) { break missingId; } id = R.id.btn_math; Button btnMath = ViewBindings.findChildViewById(rootView, id); if (btnMath == null) { break missingId; } id = R.id.button_container; LinearLayout buttonContainer = ViewBindings.findChildViewById(rootView, id); if (buttonContainer == null) { break missingId; } LinearLayout welcomeContainer = (LinearLayout) rootView; // 所有的控件都找到了,则实例化ActivityWelcomeBinding对象 return new ActivityWelcomeBinding((LinearLayout) rootView, btnGeo, btnMath, buttonContainer, welcomeContainer); } // 有控件没有找到的话,则抛出错误 String missingId = rootView.getResources().getResourceName(id); throw new NullPointerException("Missing required view with ID: ".concat(missingId)); } }ActivityWelcomeBinding.java
可见,在ActivityWelcomeBinding中用到了LayoutInflater类。LayoutInflater类中也定义了inflate()方法,该方法的主要作用就是将XML布局文件解析为相应的View对象或者称为View树,可实现动态换肤、视图转换、属性转换等需求。activity_welcome.xml对应的View树如下图所示。
LayoutInflater是一个抽象类,在Activity中获得与其相关联的LayoutInflater实例一般是通过调用Activity.getLayoutInflater()方法。关于Activity.getLayoutInflater()方法是如何得到与Activity相关联的LayoutInflater的实例请参见:https://www.jianshu.com/p/a4dd4892c84e
LayoutInflater类中定义了4个inflate()方法:
1.public View inflate (int resource, ViewGroup root) 2.public View inflate(XmlPullParser parser, ViewGroup root) 3.public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 4.public View inflate(int resource, ViewGroup root, boolean attachToRoot)
通过源代码可以发现,这些inflate()其实最终都调用了第3个inflate()方法。本文并不打算对LayoutInflater.inflate()方法的业务逻辑进行详细分析,只是通过源代码其实可以发现,在inflate()中其实会调用到LayoutInflater.createView()方法,而在createView()方法中其实是用反射reflection来动态地生成View对象的。
另外,值得一提的是,在第3个和第4个inflate()方法中,当attachToRoot为true时,返回的是ViewGroup root对象;当attachToRoot为false时,返回的是解析resource得到的View对象。这里可以看个例子:
public class WelcomeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_welcome); View rootView=getLayoutInflater().inflate(R.layout.activity_welcome,null,false); getAllViews(rootView); } private void getAllViews(View view){ if(view instanceof ViewGroup){ Log.d(TAG,"ViewGroup:".concat(view.getClass().getTypeName())); int count=((ViewGroup) view).getChildCount(); for(int i=0;i<count;i++){ getAllViews(((ViewGroup) view).getChildAt(i)); } } else{ Log.d(TAG,"View:".concat(view.getClass().getTypeName())); } } }
当ViewGroup为null,且attachToRoot为false时,日志输出为:
而当指定了一个FrameLayout作为ViewGroup,且attachToRoot为true时,日志输出为:
public class WelcomeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_welcome); FrameLayout viewGroup=new FrameLayout(this); View rootView=getLayoutInflater().inflate(R.layout.activity_welcome,viewGroup,true); getAllViews(rootView); } private void getAllViews(View view){ if(view instanceof ViewGroup){ Log.d(TAG,"ViewGroup:".concat(view.getClass().getTypeName())); int count=((ViewGroup) view).getChildCount(); for(int i=0;i<count;i++){ getAllViews(((ViewGroup) view).getChildAt(i)); } } else{ Log.d(TAG,"View:".concat(view.getClass().getTypeName())); } } }
故而可以发现,ViewBinding.inflate()和LayoutInflater.inflate()是不同的。在ViewBinding.inflate()中,即便指定了ViewGroup且attachToParent为true,但是返回的仍然是解析XML布局文件得到的View树。只不过当attachToParent为true时,解析XML布局文件得到的View树被贴在了ViewGroup parent(形参)上,那么对应的实参也会被修改。
通过分析ActivityWelcomeBinding源代码可知,在WelcomeActivity中使用ViewBinding首先是调用inflate()方法得到ActivityWelcomeBinding的实例,然后调用ActivityWelcomeBinding.getRoot()方法得到rootView,并通过setContentView()注入rootView。进而在WelcomeActivity中就可以通过ActivityWelcomeBinding的实例.属性名的方式得到相应的控件并使用控件。如下方代码所示:
public class WelcomeActivity extends AppCompatActivity { public static final String CLASS="class"; public static final int GEOGRAPHY=0; public static final int MATHEMATICS=1; private final String TAG="Welcome"; private ActivityWelcomeBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportActionBar().setTitle("Welcome to Quiz"); binding=ActivityWelcomeBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // btnGeo是ActivityWelcomeBinding中定义的公有字段 binding.btnGeo.setOnClickListener(v->btnClick(GEOGRAPHY)); binding.btnMath.setOnClickListener(v->btnClick(MATHEMATICS)); } private void btnClick(int flag){ Intent intent= new Intent(this,MainActivity.class); intent.putExtra(CLASS,flag); startActivity(intent); } }
那么如果代码改成下方这样,WelcomeActivity的屏幕则如下图所示。ActivityWelcomeBinding.inflate()返回的仍然是ActivityWelcomeBinding的实例,但是改变了FrameLayout parent这个实参。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportActionBar().setTitle("Welcome to Quiz"); FrameLayout parent=new FrameLayout(this); TextView msg=new TextView(this); msg.setText("FrameLayout is the parent"); msg.setTextSize(30); parent.addView(msg); binding=ActivityWelcomeBinding.inflate(getLayoutInflater(),parent,true); setContentView(parent); // btnGeo是ActivityWelcomeBinding中定义的公有字段 binding.btnGeo.setOnClickListener(v->btnClick(GEOGRAPHY)); binding.btnMath.setOnClickListener(v->btnClick(MATHEMATICS)); }标签:ViewGroup,LayoutInflater,ViewBinding,ActivityWelcomeBinding,rootView,public,Andr From: https://www.cnblogs.com/larissa-0464/p/17288866.html