Butterknife comes with a warning after the recent Android Studio upgrade:

Resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them as annotation attributes

Check the official website to find:

Butterknife has been deprecated and a View binding is recommended instead.

What is a View binding

Official Introduction:

View binding makes it easier to write code that interacts with views. Once view binding is enabled in a module, a binding class is generated for each XML layout file in that module. An instance of a bound class contains a direct reference to all views that have ids in the corresponding layout.

In most cases, view binding replaces findViewById.

Setup instructions

  1. Android Studio must be 3.6 or higher.
  2. Com. Android. Tools. Build: gradle need 3.6.0 and higher.
  3. Enable this function in build.gradle and set it separately for different modules.
android {
    ...
    viewBinding {
    	enabled = true
    }
        
    buildFeatures {
        viewBinding = true}}Copy the code

Two, the basic usage

The Activity is used in

Perform the following steps in the Activity’s onCreate() method:

  1. Call the static inflate() method contained in the generated binding class.
  2. Get a reference to the root view by calling the getRoot() method.
  3. Pass the root view to setContentView() to make it the active view on the screen.
    private ResultProfileBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ResultProfileBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
    }
Copy the code

You can now use an instance of this binding class to reference any view:

binding.getName().setText(viewModel.getName());
binding.button.setOnClickListener(new View.OnClickListener() {
	viewModel.userClicked()
});
Copy the code

Fragments used in

Perform the following steps in the onCreateView() method of the Fragment:

  1. Call the static inflate() method contained in the generated binding class.
  2. Get a reference to the root view by calling the getRoot() method.
  3. Return the root view from the onCreateView() method, making it the active view on the screen.
  4. Destroy the bound class in onDestroyView().
    private ResultProfileBinding binding;

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

    @Override
    public void onDestroyView(a) {
        super.onDestroyView();
        binding = null;
    }
    
Copy the code

Adapter is used in

public class TestAdapter extends BaseAdapter {

  private List<String> datas;
  private final LayoutInflater mInflater;

  public TestAdapter(List<String> datas, Context context) {
    this.datas = datas;
    this.mInflater = LayoutInflater.from(context);
  }

  @Override
  public int getCount(a) {
    return datas.size();
  }

  @Override
  public Object getItem(int i) {
    return i;
  }

  @Override
  public long getItemId(int i) {
    return i;
  }

  @Override
  public View getView(int i, View view, ViewGroup viewGroup) {
    ViewHolder viewHolder;
    if (view == null) {
      AdapterTestBinding binding = AdapterTestBinding.inflate(mInflater, viewGroup, false);
      viewHolder = new ViewHolder(binding);
      view = binding.getRoot();
      view.setTag(viewHolder);
    } else {
      viewHolder = (ViewHolder) view.getTag();
    }

    viewHolder.binding.tvContent.setText(datas.get(i));
    return view;
  }

  static class ViewHolder {

    private final AdapterTestBinding binding;

    public ViewHolder(AdapterTestBinding binding) {
      this.binding = binding; }}}Copy the code

Three, understand the source code implementation

For example, activity_main. XML will generate an activityMainBinding. Java file with the following path:

App \ build \ generated \ data_binding_base_class_source_out \ debug \ out \ \ databinding package name

The code is as simple as loading the layout and initializing the control:

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;
  @NonNull
  public final WebView wv;

  private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull WebView wv) {
    this.rootView = rootView;
    this.wv = wv;
  }

  @Override
  @NonNull
  public LinearLayout getRoot(a) {
    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) {
    // 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.
    String missingId;
    missingId: {
      WebView wv = rootView.findViewById(R.id.wv);
      if (wv == null) {
        missingId = "wv";
        break missingId;
      }
      return new ActivityMainBinding((LinearLayout) rootView, wv);
    }
    throw new NullPointerException("Missing required view with ID: ".concat(missingId)); }}Copy the code

ViewBinding source code:

public interface ViewBinding {
    /**
     * Returns the outermost {@link View} in the associated layout file. If this binding is for a
     * {@code <merge>} layout, this will return the first view inside of the merge tag.
     */
    @NonNull
    View getRoot(a);
}
Copy the code

The code can be slightly modified to reduce Activity, Fragment, Adapter in the repeated code.

BaseActivity

public abstract class BaseActivity<T extends ViewBinding> extends AppCompatActivity {

  protected T binding;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = getBinding();
    setContentView(binding.getRoot());

  }
  protected abstract T getBinding(a);

}
Copy the code
public class TestActivity extends BaseActivity<ActivityMainBinding>{

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    binding.wv.loadUrl("https://www.baidu.com");
  }

  @Override
  protected ActivityMainBinding getBinding(a) {
    returnActivityMainBinding.inflate(getLayoutInflater()); }}Copy the code

BaseFragment

public abstract class BaseFragment<T extends ViewBinding> extends Fragment {

  protected Context context;

  protected T binding;

  @Nullable
  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState) {
    binding = getBinding(inflater, container);
    return binding.getRoot();
  }

  protected abstract T getBinding(LayoutInflater inflater, ViewGroup container);

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

  @Override
  public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    this.context = context;
  }

  @Override
  public void onDetach(a) {
    super.onDetach();
    this.context = null; }}Copy the code
public class TestFragment extends BaseFragment<FragmentTestBinding>{

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    binding.tvContent.setText("this is test");
  }

  @Override
  protected FragmentTestBinding getBinding(LayoutInflater inflater, ViewGroup container) {
    return FragmentTestBinding.inflate(inflater, container, false); }}Copy the code

BaseAdapter

public abstract class MyAdapter<T extends ViewBinding> extends BaseAdapter {

  private final LayoutInflater inflater;
  public MyAdapter(Context context) {
    inflater = LayoutInflater.from(context);
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (null == convertView) {
      T binding = getBinding(inflater, parent);
      holder = new ViewHolder(binding);
      convertView = binding.getRoot();
      convertView.setTag(holder);
    } else {
      holder = (ViewHolder) convertView.getTag();
    }

    handleData(position, holder.binding);
    return convertView;
  }

  protected abstract void handleData(int position, T binding);

  protected abstract T getBinding(LayoutInflater inflater, ViewGroup parent);

  class ViewHolder {

    private final T binding;
    public ViewHolder(T binding) {
      this.binding = binding; }}}Copy the code
public class TestAdapter extends MyAdapter<AdapterTestBinding> {

  private List<String> datas;

  public TestAdapter(List<String> datas, Context context) {
    super(context);
    this.datas = datas;
  }

  @Override
  public int getCount(a) {
    return datas.size();
  }

  @Override
  public Object getItem(int i) {
    return i;
  }

  @Override
  public long getItemId(int i) {
    return i;
  }

  @Override
  protected void handleData(int position, AdapterTestBinding binding) {
    binding.tvContent.setText(datas.get(position));
  }

  @Override
  protected AdapterTestBinding getBinding(LayoutInflater inflater, ViewGroup parent) {
    return AdapterTestBinding.inflate(inflater, parent, false); }}Copy the code

Four, other

If the layout uses

tags, you need to set an ID for

to obtain the elements in the composite control.

<! -- A simple title bar layout -->

      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal" android:layout_width="match_parent"
  android:layout_height="wrap_content">
  <ImageView
    android:id="@+id/iv_back"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@mipmap/ic_launcher"/>

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/tv_title"
    android:text="this is title"/>
</LinearLayout>
Copy the code

      
<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"
    android:orientation="vertical">
    <! -- Using composite controls -->
    <include layout="@layout/view_title"
      android:id="@+id/view_title"/>

    <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:id="@+id/tv_content"
      android:text="test"/>
</LinearLayout>
Copy the code
public class TestFragment extends BaseFragment<FragmentTestBinding>{

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    binding.tvContent.setText("this is test");
    // Use the include id to find the corresponding control
    binding.viewTitle.tvTitle.setText("this is title");
  }

  @Override
  protected FragmentTestBinding getBinding(LayoutInflater inflater, ViewGroup container) {
    return FragmentTestBinding.inflate(inflater, container, false); }}Copy the code