<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] # 1. 說明 > 本篇博客參考[Data Binding in Android (google.cn)](https://developer.android.google.cn/codelabs/android-databinding?hl=en#0) 和 [數據綁定庫](https://developer.android.google.cn/topic/libraries/data-binding) 數據綁定可以使用聲明性格式(而非程序化地)將布局中的界面組件綁定到應用中的數據源。其實有點類似于`MVVM`框架,數據和顯示的部分動態綁定,**當數據發生改變對應的視圖也隨之改變**。如果您使用數據綁定的主要目的是取代`findViewById()`調用,請考慮改用視圖綁定。其模式示意圖: ![](https://img.kancloud.cn/86/d4/86d47eb3ea048e29ca22273bf39c8f41_624x193.png) 和視圖綁定類似,對于`Android Studio`的版本也有要求: > Android Studio 3.4 or greater # 2. 使用 ## 2.1 環境準備 類似的,直接在配置文件中添加: ~~~ android { ... dataBinding { enabled = true } } ~~~ 將之前的`layout`布局文件修改為`DataBinding layout`。直接右擊根布局的標簽元素,然后選擇**Show Context Actions**: ![](https://img.kancloud.cn/ce/09/ce09916a8288d3c055aeb7b6203ca711_615x86.png) 然后就可以看見提供了直接轉換到`data binding`布局的選項: ![](https://img.kancloud.cn/9e/a3/9ea3e4b166cf90a478c55a18f5769ba1_712x184.png) 比如我這里轉換后的`xml`布局文件為: ~~~xml <?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=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="內容" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.194" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ~~~ 在`data`標簽中的內容,也就是定義的變量。比如可以定義如下的兩個變量: ~~~ <data> <import type="android.view.View"/> <variable name="name" type="String"/> <variable name="message" type="String"/> </data> ~~~ 因為后續需要使用`View`,所以這里需要導入包。對應的,可以使用自定義的類,然后導入對應的包即可。 ## 2.2 根據name長度顯示Message案例 將定義的變量和布局文件中的控件關聯,也就是使用變量。在`Android Jetpack`中定義的使用方式為`@{ expression }`的格式,也就是可以如下使用: ~~~ <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{ message }" android:visibility="@{ (name.length > 3) ? View.VISIBLE : View.GONE }" ... /> ~~~ 然后就是在代碼中設置在`xml`中申明的兩個變量的值。和`viewbinding`類似在`databinding`中也需要在`onCreate`方法替換: ~~~ setContentView(R.layout.plain_activity) ~~~ 這里替換為: ~~~ binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main ) ~~~ 所獲得的`binding`對象也就是和布局文件相關聯的類,即:`ActivityMainBinding`。通過`binding`這個實例,就能夠直接操作在`xml`中聲明的變量: ~~~ binding.name = "testDemo" binding.message = "Hello data binding." ~~~ 完整代碼: ~~~ class MainActivity2 : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. setContentView(R.layout.plain_activity) replace with below: // data binding binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main ) // 2. set the variable values binding.name = "testDemo" binding.message = "Hello data binding." } } ~~~ 結果: ![](https://img.kancloud.cn/68/1b/681b41970ea27899281cd685e404219e_185x61.png) ## 2.3 響應點擊事件 通常我們可以直接在`xml`中直接設置點擊函數,比如: ~~~ android:onClick="onButtonClick" ~~~ 然后在`Activity`中定義方法`onButtonClick`。或者直接通過這個按鈕的實例對象來注冊監聽,進行事件處理。這里也是類似,可以在`xml`中采用`Lambda`表達式的方式來注冊函數。首先定義一個`SimpleViewModel`類,繼承自`ViewModel`類,如下: ~~~kotlin /** * @author 夢否 on 2022/3/29 * @blog https://mengfou.blog.csdn.net/ */ class SimpleViewModel: ViewModel() { // 定義消息 var message = "Hello data binding." get() { return if(clickNumber % 2 == 0) "偶數" else "奇數" } private set // 阻止外部修改,只支持內部修改 val name = "testDemo" var clickNumber = 0 private set // 定義點擊函數 fun onTextViewClick(){ clickNumber++ Log.e("TAG", "onTextViewClick: ${clickNumber}" ) } } ~~~ 但是,很不幸,點擊`TextView`之后,在`TextView`中顯示的文本并沒有觀測到數據的變化。觀察日志: ![](https://img.kancloud.cn/49/1f/491fbabca94357f6588cffc8b645416d_1010x96.png) 其實,這是因為我們設置的數據并不可觀測。我們需要讓數據可以**observable**才行。為了讓字段可觀測,可以使用`observable`類或者`LiveData`。關于可觀察的數據對象在`Google`中有詳細說明:[使用可觀察的數據對象](https://developer.android.google.cn/topic/libraries/data-binding/observability)。 ## 2.4 可觀察數據類型 可觀察類有三種不同類型:對象、字段和集合。 ### 2.4.1 可觀測對象 實現`Observable`接口的類允許注冊監聽器,以便它們接收有關可觀察對象的屬性更改的通知。為便于開發,數據綁定庫提供了用于實現監聽器注冊機制的`BaseObservable`類。實現`BaseObservable`的數據類負責在屬性更改時發出通知。具體操作過程是向 getter 分配`Bindable`注釋,然后在 setter 中調用`notifyPropertyChanged()`方法。 ### 2.4.2 可觀測字段 ~~~ ObservableField<String>() ~~~ 以及基本的: ![](https://img.kancloud.cn/75/cc/75cc705955da208b287836fb9af730b0_275x408.png) ### 2.4.3 可觀察集合 `ObservableArrayMap`、`ObservableArrayList`等。 ## 2.5 設置數據可觀察 因為這里所使用的為基本類型,比如`message`和`name`。由于這里我只需要`message`可觀測,所以這里對其應用可觀測字段即可。如下: ~~~kotlin /** * @author 夢否 on 2022/3/29 * @blog https://mengfou.blog.csdn.net/ */ class SimpleViewModel : ViewModel() { // 定義可觀測的字段,使用ObservableField var message = ObservableField<String>("Hello data binding.") private set // 阻止外部修改,只支持內部修改 val name = "testDemo" var clickNumber = 0 private set // 定義點擊函數 fun onTextViewClick() { clickNumber++ if (clickNumber.rem(2) == 0) message.set("is Even") else message.set("is odd") Log.e("TAG", "onTextViewClick: ${clickNumber}") } } ~~~ 對應的修改在`xml`中的`<data>`標簽中的變量聲明: ~~~xml <data> <import type="android.view.View" /> <variable name="viewModel" type="com.weizu.jetpackdemo.SimpleViewModel" /> </data> ~~~ 對應的`MainActivity`文件: ~~~kotlin class MainActivity2 : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. setContentView(R.layout.plain_activity) replace with below: // data binding binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main ) // set data binding.viewModel = SimpleViewModel() } } ~~~ 然后就可以看見點擊后奇數點擊和偶數點擊的切換顯示文本效果。 ## 2.6 數據雙向綁定 在這個案例中需要達到的效果為:對應定義的可觀測字段`Field`內容的改變可以通知到對應的控件,而控件的內容變化也可以通知到`Field`。所以這里可以使用控件`EditText`。 ### 2.6.1 方式一:繼承自BaseObservable 在布局文件中定義一個`EditText`和`TextView`,如下: ~~~xml <?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 name="myViewModel" type="com.weizu.jetpackdemo.databinding.MyViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".databinding.MainActivity"> <EditText android:id="@+id/editText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" android:text="@={ myViewModel.userInput }" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{ myViewModel.userInput }" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.675" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ~~~ 值得注意的是,在`EditText`中設置為: ```xml android:text="@={ myViewModel.userInput }" ``` 而在`TextView`中為: ```xml android:text="@{ myViewModel.userInput }" ``` 因為在`EditText`中我們需要完成雙向綁定,即用戶輸入可以通知到`LiveData`,而在`TextView`中只要加載變化后的數據即可。 那么,在自定義`ViewModel`中為: ~~~kotlin /** * @author 夢否 on 2022/4/20 * @blog https://mengfou.blog.csdn.net/ */ class MyViewModel :BaseObservable(){ // 設置為LiveData,便于布局文件中TextView內容的自動更新 private val userInput = MutableLiveData<String>("Tom") // 這里一定不要忘記添加注解@Bindable,否則雙向綁定不會生效 @Bindable @JvmName("getUserInput") fun getUserInput(): String{ return userInput.value.toString() } // 用于更新TextView fun get(): LiveData<String> { return this.userInput } @JvmName("setUserInput") fun setUserInput(str: String){ if(!str.equals(userInput)) { this.userInput.value = str } Log.e("TAG", "setValue: ${str}" ) // 通知數據發生了改變 notifyPropertyChanged(BR.myViewModel) // build后會自動生成一個BR類,對應在xml中聲明的變量 } } ~~~ 這里為了完成雙向綁定,繼承自`BaseObservable`,且在`get`方法上使用了`@Bindable`注解來表示綁定。至于`get()`方法僅是為了返回`LiveData`對象,然后在`Activity`中設置觀察,更新`TextView`控件內容: ~~~kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMain2Binding>( this, R.layout.activity_main2 ) // 這里直接使用new一個對象 // 因為這里的自定義ViewModel繼承的是BaseObservable類,不是ViewModel類 val myViewModel = MyViewModel() binding.myViewModel = myViewModel // 設置觀察,以更新TextView文本 myViewModel.get().observe(this) { binding.textView2.text = myViewModel.get().value } } } ~~~ 效果: ![](https://img.kancloud.cn/a8/ab/a8ab56259e30c35cfedbf17c4b0b69d4_274x158.png) ### 2.6.2 方式二:繼承自ObservableField 布局文件還是保持不變: ~~~xml <?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 name="myViewModel" type="com.weizu.jetpackdemo.databinding.MyViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".databinding.MainActivity"> <EditText android:id="@+id/editText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" android:text="@={ myViewModel.userInput }" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{ myViewModel.userInput }" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.675" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ~~~ 對于`ViewModel`進行刪減: ~~~kotlin class MyViewModel { // 設置為可觀察類型 val userInput = ObservableField<String>("Tom") } ~~~ 最后在`Activity`中進行設置數據: ~~~kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMain2Binding>( this, R.layout.activity_main2 ) // 這里直接使用new一個對象 // 因為這里的自定義ViewModel繼承的是BaseObservable類,不是ViewModel類 val myViewModel = MyViewModel() binding.myViewModel = myViewModel // 在xml文件:@=操作符進行雙向綁定 } } ~~~ 達到的效果和上小節一樣。 ## 2.7 RecyclerView+dataBinding 可以使用`databinding`來設置每個`item`的內容。比如在主布局文件: ~~~xml <?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"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".recycleview.MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ~~~ 因為使用了`RecyclerView`,所以這里還是定義對應的適配器: ~~~kotlin /** * @author 夢否 on 2022/4/20 * @blog https://mengfou.blog.csdn.net/ */ class MyRecycleViewAdapter(var context: Context, var datas: List<User>) : RecyclerView.Adapter<MyRecycleViewAdapter.MyViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val inflater = LayoutInflater.from(context) val binding = DataBindingUtil.inflate<RecyclerviewItemBinding>( inflater, R.layout.recyclerview_item, parent, false ) val myViewHolder = MyViewHolder(binding.root) myViewHolder.binding = binding return myViewHolder } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.binding?.user = datas[position] Log.e("TAG", "onBindViewHolder: ${position} + ${ datas[position].name }") } override fun getItemCount(): Int { return datas.size } inner class MyViewHolder(var root: View) : RecyclerView.ViewHolder(root) { var binding: RecyclerviewItemBinding? = null } } ~~~ 同樣的在`R.layout.recyclerview_item`布局文件中設置`databinding`: ~~~xml <?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 name="user" type="com.weizu.jetpackdemo.recycleview.User" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{ user.name }" app:layout_constraintBottom_toTopOf="@+id/textView4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="93dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="20dp" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{ String.valueOf(user.age) }" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/imageView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="1dp" android:imageSrc="@{ user.image }" app:layout_constraintEnd_toStartOf="@+id/textView4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ~~~ 對于`User`類比較簡單: ~~~kotlin class User(var age: Int, var name: String) { var image = "https://i1.hdslb.com/bfs/face/7e72c58637ff26df68fb30939de078d2bbbfcdbe.jpg" } ~~~ 在主`Activity`中配置: ~~~kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMain3Binding>( this, R.layout.activity_main3 ) val datas = listOf<User>( User(12, "Jack"), User(10, "Tom"), User(23, "Joe") ) // 必須設置布局管理器,否則不會顯示RecyclerView binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.adapter = MyRecycleViewAdapter(this, datas) } } ~~~ 運行即可看見效果。 # 3. 自定義BindingAdapter 參考視頻地址:[https://www.bilibili.com/video/BV1Ry4y1t7Tj?p=12](https://www.bilibili.com/video/BV1Ry4y1t7Tj?p=12) 這個案例感覺比較典型,達到的效果為可以使用`databinding`的方式傳入一個圖片的鏈接地址,然后可以通過注解的方式來**直接**定義屬性字段。然后可以完成加載。比如下面的案例: 布局文件: ~~~xml <?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 name="src" type="String" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".BindingAdapterActivity"> <ImageView android:id="@+id/imageView" android:layout_width="300dp" android:layout_height="300dp" app:imageSrc="@{ src }" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ~~~ 注意到,在`ImageView`標簽中直接設置了自定義的字段: ```xml app:imageSrc="@{ src }" ``` 而這個字段以前我們是需要使用`tool:`并定義對應的`styleable`樣式。這里并不需要,僅需要使用注解來申明: ~~~kotlin class ImageViewCus { // 需要注意的是,這里需要使用靜態方法 companion object{ @JvmStatic @BindingAdapter("app:imageSrc") fun loadImage(imageView: ImageView, str: String){ Glide.with(imageView.context) .load(str) .placeholder(R.drawable.ic_launcher_background) .into(imageView) } } } ~~~ 最后是在`Activity`中使用: ~~~kotlin class MyBindingAdapterActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityBindingAdapterBinding>( this, R.layout.activity_binding_adapter ) binding.src = "https://img-blog.csdnimg.cn/5690c131d90e460fa4c96bf86b1ae634.png" } } ~~~ 傳入`databinding`中聲明的字符串即可,就可以達到預期的效果。整體的使用流程感覺和`SpringBoot`中的類似,但是這里比較好奇的是難道這里也會掃描所有包/類中的注解?應該是的,等儲備知識夠了再深入。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看