绑定控件的几种方式--ButterKnife/ViewBinding/DataBinding

绑定控件的几种方式

1. findViewById( 常规 )

大家都很熟悉的,就不多介绍了

2.ButterKnife ( 黄油刀 ,该库已弃用,仅了解)

image-20220704114756164

一款快速绑定Android视图中字段和方法的注解框架,也是Android开发中曾经比较常用的一款快速注解框架,通过ButterKnife的合理运用,我们可以避免重复地书写findViewById,在各种场合下快速地绑定view中的多种事件,大大提高开发的效率,它在Java编译时注解处理器,在编译时自动生成findViewById的代码,虽然现在已被弃用,但既然它曾经这么受欢迎,我们也可以自己对比一下它和其他方式的优势和劣势。

image-20220704113518000

简单的视图绑定例子:

1. 安装插件

File -> Settings -> Plugins -> 搜索ButterKnife , 找到Android ButterKnife Zelezny后点击Install,再重启AS

2. 添加依赖

    implementation 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'  
    // 如果是kotlin,把annotationProcessor替换成kapt即可

3. XML代码

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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=".MainActivity">

    <TextView
        android:id="@+id/tv_HW"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_example"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_HW" />

    <Button
        android:id="@+id/btn_click_me"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Click Me"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_example" />

    <Button
        android:id="@+id/btn_click_me_too"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Click Me TOO"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_click_me" />

    <EditText
        android:id="@+id/edit_query"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/btn_click_me_too" />


</androidx.constraintlayout.widget.ConstraintLayout>

4. Java代码

import androidx.appcompat.app.AppCompatActivity;

// 导入必要的库(使用ButterKnife过程中貌似不会帮你自动导入,若报错,检查是否导入相应的库)
import android.os.Bundle;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Button;
import android.widget.Toast;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

import com.example.mvcdemo.R;

public class MainActivity extends AppCompatActivity {
    
    

    // 控件注解,并且不能在控件前添加private或static
    @BindView(R.id.tv_HW)
    TextView tvHelloWorld;
    @BindView(R.id.tv_example)
    TextView tvExample;
    @BindView(R.id.edit_query)
    EditText editText;
    @BindView(R.id.btn_click_me)
    Button btnClickMe;
    @BindView(R.id.btn_click_me_too)
    Button btnClickMeToo;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // setContentView() 后面添加 ButterKnife.bind(this);
        ButterKnife.bind(this);
        // 可直接使用控件,不需要findViewById()
        tvExample.setText("修改后的TextView");
        editText.setText("123");
    }

    // 点击事件直接使用注解@OnClick(R.id.XXXX),无需使用匿名内部类
    @OnClick(R.id.tv_HW)
    public void changeText(TextView tvHw){
    
    
        tvHw.setText("Hello Android (change the World)");
    }

    // 共同处理两个按钮,使两个按钮点击后调用同一个事件
    @OnClick({
    
    R.id.btn_click_me, R.id.btn_click_me_too})
    public void mulitButtonClick(){
    
     // 函数名自定义
       Toast.makeText(this, "U click Me", Toast.LENGTH_SHORT).show();
    }

}

我们这里仅以我们最常规的例子示范,其他绑定具体可见:https://blog.csdn.net/qq_29924041/article/details/80538360

ButterKnife官网:https://jakewharton-github-io.translate.goog/butterknife/?_x_tr_sl=en&_x_tr_tl=zh-CN&_x_tr_hl=zh-CN&_x_tr_pto=op,sc

原理分析:https://blog.csdn.net/hai_qing_xu_kong/article/details/78831979

3.自定义注解绑定(了解)

https://blog.csdn.net/yangzhaomuma/article/details/51183110

4. **ViewBinding(推荐使用, 重点掌握)

ViewBinding是Google在2019年I/O大会上公布的一款Android视图绑定工具,也是Google强烈推荐我们使用的。

原理:Google在Android gradle插件中增加了新功能,当某个module开启ViewBind功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类,findViewById操作就是在这个自动生成的类里面完成的。

配置准备

确保你的Android Studio是3.6或更高的版本,在对应的项目工程模块app目录下的 build.gradle加入以下配置:

android {
    
    
    ……
	// 添加的配置为以下三行
    buildFeatures{
    
    
        viewBinding = true
    }
}

点击右上角的Sync Now,系统会自动生成viewBinding类,例如MainActivity会生成名为ActivityMainBinding的类,LaunchActivity会生成名为ActivityLaunchBinding的类,以此类推;

以上viewBinding类会生成在如下路径文件中(若想了解其中的原理,可以查看这些文件的代码)

image-20220704142710559

查看文件后,我们可以看出,ViewBinding最终使用的仍然是findViewById,和ButterKnife异曲同工,不同的是ButterKnife通过编译时注解生成ViewBinding类,而ViewBinding是通过编译时扫描layout文件生成ViewBinding类。

使用步骤

  1. 在类中定义全局的viewBinding类对象

  2. 给binding赋值,对应值为viewBinding类对象映射的视图,使变量和视图进行绑定

public class MainActivity2 extends AppCompatActivity {
    
    
	
    private ActivityMain2Binding binding;

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


    }
}
  1. 控件引用
binding.buttonExample.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View v) {
    
    
        Toast.makeText(MainActivity2.this, "使用ViewBinding绑定了按钮", Toast.LENGTH_SHORT).show();
    }
});

5. DataBinding(提前了解)

DataBindingLiveDataViewModel都是JetPack框架里面的,学习这些也有助于我们学习后面的MVVM架构。

DataBinding的特性就是可以将数据直接绑定到界面中.

下面我们看看DataBinding的具体使用:

添加配置

android {
    
    
    compileSdkVersion 31
    buildToolsVersion '30.0.3'

    defaultConfig {
    
    
        ……
        // 在此位置添加下面代码
        dataBinding {
    
    
            enabled = true
        }
    }
}

具体使用

第一步

在布局中选中根布局,按下Alt + Enter,出现以下选项,点击Convert to data binding layout

image-20220704175538162

然后布局修改后如下:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity3">

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

第二步

在布局中填入数据和控件

单向绑定、方法绑定
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <!--  使用variable声明变量,后面的xml代码中我们就可以用@{
    
    }调用Java代码中的变量和函数      -->
        <variable
            name="user"
            type="com.example.bingwaydemo.bean.User" />

        <variable
            name="click"
            type="com.example.bingwaydemo.MainActivity3.Listener" />


    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity3">


        <EditText
            android:id="@+id/et_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="@{user.userName}"
            android:hint="请输入用户名"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/et_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="@{user.password}"
            android:hint="请输入密码"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_username" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="Login"
            android:onClick="@{()->click.changeUSerName()}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et_password" />

        <TextView
            android:id="@+id/tv_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="@{user.userName}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button" />

        <TextView
            android:id="@+id/tv_password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="@{user.password}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_username" />

        <ImageView
            android:id="@+id/image_head"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:src="@{user.headImage}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_password"
            tools:srcCompat="@tools:sample/avatars" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Activity和实体类

这里的User类使用了LiveData,让user对象数据发生变化的时候,Editext中的数据会自动更新

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.library.baseAdapters.BR;

/**
 * @ProjectName : bingWayDemo
 * @Author : Victor Scott
 * @Time : 2022/7/4 17:29
 * @Description : 描述
 */
// 1. 继承自BaseObservable
public class User extends BaseObservable {
    
    
    // 2.使用 @Bindable 注解属性,这里要注意,如果属性是public 的,需要在声明的时候注解
    //   如果属性是 private 的,需要在 get 方法上注解
    @Bindable
    public String userName;
    private String password;
    private String headImage;

    public String getHeadImage() {
    
    
        return headImage;
    }

    public void setHeadImage(String headImage) {
    
    
        this.headImage = headImage;
    }

    public String getUserName() {
    
    
        return userName;
    }

    public void setUserName(String userName) {
    
    
        this.userName = userName;
        // 3. 在public 属性上注解就可以了,但是在private 属性上仅仅注解是不管用的,还需要在属性值发生改变之后
        //    通知出去,这里的 BR 是系统生成的
        notifyPropertyChanged(BR.userName);
        //    与上面的 notifyPropertyChanged 用法相似,区别是 上面的方法只通知相关的控件刷新内容,
        //    下面这个方法则是通知所有控件刷新内容
        //    notifyChange();
    }

    @Bindable
    public String getPassword() {
    
    
        return password;
    }

    public void setPassword(String password) {
    
    
        this.password = password;
    }

    @Override
    public String toString() {
    
    
        return "User{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", headImage=" + headImage +
                '}';
    }
}

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;
import android.util.Log;

import com.example.bingwaydemo.bean.User;
import com.example.bingwaydemo.databinding.ActivityMain3Binding;

public class MainActivity3 extends AppCompatActivity {
    
    

    ActivityMain3Binding binding;
    User user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main3);
        /**    通过DataBindingUtil.setContentView 将布局文件和activity关联,替代了原来setContentView()方法
         *     这样省去了findViewById,只需要binding.button/binding.etUsername/bindind.etPassword直接获取控件
         *     binding.getRoot()用于获取顶层的布局,在Fragment中需要使用这个方法,返回view
         */

        user = new User();
        user.setUserName("username");
        user.setPassword("password");
        user.setHeadImage("https://img2.baidu.com/it/u=2860188096,638334621&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500");

        binding.setUser(user);
        binding.setClick(new Listener());

    }


    public class Listener{
    
    
        public void changeUSerName(){
    
    
            user.setUserName("更改了用户名");
            Log.e("TAG", user.toString() );
        }
    }

}

我们会发现几个好处:

  • 不用再写findViewById()这样的样板式代码
  • 创建的user对象中的数据自动填充到了对应的位置,不用再setText(),若要修改只需要改变类中的变量即可

这里的单相绑定是指数据实体发生改变的时候,会及时的通知到布局文件,改变显示的内容。

但是此时你发现,修改EditView是,对应的user对象中的userName并不会发生对应的变化,如何能让他们实现双向绑定呢?

只需要 将**@{}** 改成**@={}**, 中间加上=即可

这个时候我们再去运行demo,向 EditText中输入内容的时候会发现,下面的TextView中的内容同时也在发生变化,点击LOGIN按钮,user从日志中也可以看出,user对象也确确实实发生了改变。至此,双向绑定也就基本实现了。

猜你喜欢

转载自blog.csdn.net/victorrrrt/article/details/125610695