Jetpack - ViewModel、LiveData、DataBinding(数据绑定、双向数据绑定)

一、ViewModel

1、基本介绍
  • ViewModel 属于 Android Jetpack 架构组件的一部分,ViewModel 被设计用来存储和管理与 UI 相关的数据,这些数据在配置更改(例如,屏幕旋转)时能够幸存下来,ViewModel 的生命周期与 Activity 或 Fragment 的生命周期紧密相关,但比它们更长,这意味着即使 Activity 或 Fragment 被重新创建,ViewModel 中的数据也会保留下来,它有如下特点
  1. 配置更改时数据保留:当屏幕旋转或发生其他配置更改时,ViewModel 中的数据不会丢失

  2. 生命周期管理:ViewModel 在关联的 Activity 或 Fragment 被销毁时也会被清理,但它在配置更改时会保留下来

  3. 数据共享:可以在多个 Fragment 或 Activity 之间共享同一个 ViewModel 实例,以共享数据

  4. 与 LiveData 配合使用:ViewModel 通常与 LiveData 一起使用,以便在数据发生变化时通知 UI 更新

2、不使用 ViewModel
(1)Activity Layout
  • activity_view_model_no_use.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=".ViewModelNoUseActivity"><Buttonandroid:id="@+id/btn_add"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="add"android:text="add"android:textSize="20sp"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" /><TextViewandroid:id="@+id/tv_num"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.35000002" /></androidx.constraintlayout.widget.ConstraintLayout>
(2)Activity Code
  • ViewModelNoUseActivity.java
package com.my.viewmodel;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.view.View;
import android.widget.TextView;public class ViewModelNoUseActivity extends AppCompatActivity {private TextView tvNum;private int num;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_view_model_no_use);tvNum = findViewById(R.id.tv_num);}public void add(View view) {tvNum.setText(String.valueOf(++num));}
}
3、使用 ViewModel
(1)ViewModel
  • MyViewModel.java
package com.my.jetpackdemo.viewmodel;import androidx.lifecycle.ViewModel;public class MyViewModel extends ViewModel {public int num;
}
(2)Activity Layout
  • activity_view_model_no_use.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=".ViewModelNoUseActivity"><Buttonandroid:id="@+id/btn_add"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="add"android:text="add"android:textSize="20sp"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" /><TextViewandroid:id="@+id/tv_num"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.35000002" /></androidx.constraintlayout.widget.ConstraintLayout>
(3)Activity Code
  • ViewModelNoUseActivity.java
package com.my.viewmodel;import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;import android.os.Bundle;
import android.view.View;
import android.widget.TextView;import com.my.viewmodel.viewmodel.MyViewModel;public class ViewModelActivity extends AppCompatActivity {private TextView tvNum;private MyViewModel myViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_view_model);tvNum = findViewById(R.id.tv_num);myViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);tvNum.setText(String.valueOf(myViewModel.num));}public void add(View view) {tvNum.setText(String.valueOf(++myViewModel.num));}
}

二、LiveData

1、基本介绍
  • LiveData 是 Android Jetpack 架构组件中的一个类,它用于在数据发生变化时通知观察者(通常是 UI 组件),LiveData 是一个可观察的数据持有者,它与生命周期感知组件(例如,Activity、Fragment)紧密集成,这意味着它只会在这些组件处于活跃状态时更新观察者,它有如下特点
  1. 生命周期感知:LiveData 只在有活跃的观察者时才会分发更新,这有助于避免在组件(例如,Activity、Fragment)不再可见时发生不必要的更新

  2. 粘性事件:LiveData 可以选择性地保留最后一个值,并在新的观察者开始观察时立即发送该值(粘性事件的行为),这对于如登录状态、配置变化等需要即时知道当前状态的情况很有用

  3. 线程安全:可以在任何线程上修改 LiveData 的值,而观察者会在主线程上接收到这些更新,从而避免了直接操作 UI 组件的线程安全问题

  4. 数据一致性:LiveData 保证观察者会收到最新的数据,即使它们是在数据变化之后开始观察的

2、LiveData 方法
方法说明
setValue用于设置 LiveData 对象的新值,当调用此方法时,所有活动的观察者都会收到这个新值
如果 LiveData 对象当前没有活动的观察者,setValue 方法调用不会做任何事情
但是,一旦有观察者,它将立即收到最近设置的值
注:setValue 方法应该在主线程上调用,因为观察者会在主线程上接收更新
getValue用于获取 LiveData 对象当前持有的值,如果没有设置值,则返回 null
postValue另一个用于设置 LiveData 值的方法,但它可以在任何线程上调用
从非主线程更新 LiveData 时,应该使用 postValue 方法而不是 setValue 方法
postValue 方法会将更新操作安排到主线程上执行,从而确保 UI 的更新是在主线程上进行的
3、演示
(1)ViewModel
  • MyLiveData.java
package com.my.livedata.viewmodel;import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;public class MyLiveData extends ViewModel {private MutableLiveData<Integer> currentSecond;public MutableLiveData<Integer> getCurrentSecond() {if (currentSecond == null) {currentSecond = new MutableLiveData<>();currentSecond.setValue(0);}return currentSecond;}
}
  • 上述代码使用了 MutableLiveData 来存储和分发一个整数值,它允许修改存储的值并通知观察者
(2)Activity Layout
  • activity_live_data.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=".LiveDataActivity"><TextViewandroid:id="@+id/tv_current_second"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(3)Activity Code
  • LiveDataActivity.java
package com.my.livedata;import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;import android.os.Bundle;
import android.widget.TextView;import com.my.livedata.viewmodel.MyLiveData;import java.util.Timer;
import java.util.TimerTask;public class LiveDataActivity extends AppCompatActivity {private TextView tvCurrentSecond;private MyLiveData myLiveData;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_live_data);tvCurrentSecond = findViewById(R.id.tv_current_second);myLiveData = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyLiveData.class);myLiveData.getCurrentSecond().observe(this, new Observer<Integer>() {@Overridepublic void onChanged(Integer integer) {tvCurrentSecond.setText(String.valueOf(integer));}});startTimer();}private void startTimer() {new Timer().schedule(new TimerTask() {@Overridepublic void run() {myLiveData.getCurrentSecond().postValue(myLiveData.getCurrentSecond().getValue() + 1);}}, 1000, 1000);}
}
  1. observe 方法用来观察 LiveDataViewModel 中的 currentSecond,当 currentSecond 的值改变时,onChanged 方法会被调用

  2. postValue 方法用来更新 LiveDataViewModel 中的 currentSecond 的值

4、LiveData 优化
(1)ViewModel
  • MyAnotherLiveData.java
package com.my.livedata.viewmodel;import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;public class MyAnotherLiveData extends ViewModel {private MutableLiveData<Integer> currentSecond = new MutableLiveData<>(0);public LiveData<Integer> getCurrentSecond() {return currentSecond;}public Integer getValue() {return currentSecond.getValue();}public void setValue(Integer integer) {currentSecond.setValue(integer);}public void postValue(Integer integer) {currentSecond.postValue(integer);}
}
(2)Activity Layout
  • activity_another_live_data.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=".AnotherLiveDataActivity"><TextViewandroid:id="@+id/tv_current_second"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(3)Activity Code
  • AnotherLiveDataActivity.java
package com.my.livedata;import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;import android.os.Bundle;
import android.widget.TextView;import com.my.livedata.viewmodel.MyAnotherLiveData;import java.util.Timer;
import java.util.TimerTask;public class AnotherLiveDataActivity extends AppCompatActivity {private TextView tvCurrentSecond;private MyAnotherLiveData myAnotherLiveData;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_another_live_data);tvCurrentSecond = findViewById(R.id.tv_current_second);myAnotherLiveData = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyAnotherLiveData.class);myAnotherLiveData.getCurrentSecond().observe(this, (integer) -> {tvCurrentSecond.setText(String.valueOf(integer));});startTimer();}private void startTimer() {new Timer().schedule(new TimerTask() {@Overridepublic void run() {myAnotherLiveData.postValue(myAnotherLiveData.getValue() + 1);}}, 1000, 1000);}
}
5、Fragment 通信
(1)ViewModel
  • SeekBarViewModel.java
package com.my.jetpackdemo.viewmodel;import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;public class SeekBarViewModel extends ViewModel {private MutableLiveData<Integer> process;public MutableLiveData<Integer> getProcess() {if (process == null) {process = new MutableLiveData<>();process.setValue(0);}return process;}
}
(2)Fragment
  • fragment_first.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=".fragment.FirstFragment"><SeekBarandroid:id="@+id/seek_bar_first"android:layout_width="0dp"android:layout_height="wrap_content"android:max="100"android:min="0"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • FirstFragment.java
package com.my.livedata.fragment;import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;import com.my.livedata.R;
import com.my.livedata.viewmodel.SeekBarViewModel;public class FirstFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View root = inflater.inflate(R.layout.fragment_first, null, false);SeekBar seekBarFirst = root.findViewById(R.id.seek_bar_first);SeekBarViewModel seekBarViewModel = new ViewModelProvider(getActivity(),new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(SeekBarViewModel.class);seekBarViewModel.getProcess().observe(getActivity(), new Observer<Integer>() {@Overridepublic void onChanged(Integer integer) {seekBarFirst.setProgress(integer);}});seekBarFirst.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {seekBarViewModel.setValue(progress);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}});return root;}
}
  • fragment_second.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=".fragment.SecondFragment"><SeekBarandroid:id="@+id/seek_bar_second"android:layout_width="0dp"android:layout_height="wrap_content"android:max="100"android:min="0"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • SecondFragment.java
package com.my.livedata.fragment;import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;import com.my.livedata.R;
import com.my.livedata.viewmodel.SeekBarViewModel;public class SecondFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View root = inflater.inflate(R.layout.fragment_second, null, false);SeekBar seekBarSecond = root.findViewById(R.id.seek_bar_second);SeekBarViewModel seekBarViewModel = new ViewModelProvider(getActivity(),new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(SeekBarViewModel.class);seekBarViewModel.getProcess().observe(getActivity(), new Observer<Integer>() {@Overridepublic void onChanged(Integer integer) {seekBarSecond.setProgress(integer);}});seekBarSecond.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {seekBarViewModel.setValue(progress);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}});return root;}
}
(3)Activity Layout
  • activity_live_data_fragment.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=".LiveDataFragmentActivity"><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/gl"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"app:layout_constraintGuide_end="365dp" /><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/fcv1"android:name="com.my.livedata.fragment.FirstFragment"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toTopOf="@+id/gl"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/fcv2"android:name="com.my.livedata.fragment.SecondFragment"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="@+id/gl" />
</androidx.constraintlayout.widget.ConstraintLayout>
(4)Activity Code
  • LiveDataFragmentActivity.java
package com.my.livedata;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;public class LiveDataFragmentActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_live_data_fragment);}
}

三、数据绑定

1、基本介绍
  • Android DataBinding 是一种布局书写方式,它允许将数据直接绑定到布局的 XML 中,使得数据的变化能够直接反映到 View 上,这是 Android团队实现 MVVM 架构的一种方法,旨在减少 Android 开发中的大量模板代码(例如,findViewById()),增加代码及逻辑清晰度,提高开发效率和维护效率
2、演示
(1)Setting
  • 模块级 build.gradle
android {...defaultConfig {...dataBinding {enabled = true}}
}
(2)Entity
  • Idol.java
package com.my.jetpackdemo.entity;public class Idol {public String name;public String star;public Idol(String name, String star) {this.name = name;this.star = star;}
}
(3)Activity Layout
  • activity_data_binding.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><variablename="idol"type="com.my.databinding.entity.Idol" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".DataBindingActivity"><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/gl"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"app:layout_constraintGuide_percent="0.5" /><TextViewandroid:id="@+id/tv_idol_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ idol.name }"android:textSize="20sp"app:layout_constraintBottom_toTopOf="@+id/gl"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_idol_star"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ idol.star }"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@+id/gl" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • DataBindingActivity.java
package com.my.databinding;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;import com.my.databinding.databinding.ActivityDataBindingBinding;
import com.my.databinding.entity.Idol;public class DataBindingActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityDataBindingBinding activityDataBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding);Idol idol = new Idol("张学友", "五星");activityDataBindingBinding.setIdol(idol);}
}
3、更多用法
(1)Entity
  • User.java
package com.my.databinding.entity;public class User {public String name;public int star;public User(String name, int star) {this.name = name;this.star = star;}
}
(2)Listener
  • EventHandleListener.java
package com.my.databinding.listener;import android.content.Context;
import android.view.View;
import android.widget.Toast;public class EventHandleListener {private Context context;public EventHandleListener(Context context) {this.context = context;}public void btnOnClick(View view) {Toast.makeText(context, "点赞", Toast.LENGTH_SHORT).show();}
}
(3)Util
  • UserUtil.java
package com.my.jetpackdemo.util;public class UserUtil {public static String getStar(int star) {switch (star) {case 1:return "一星";case 2:return "一星";case 3:return "三星";case 4:return "四星";case 5:return "五星";}return "零星";}
}
(4)Activity Layout
  • activity_user.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><variablename="user"type="com.my.databinding.entity.User" /><variablename="eventHandleListener"type="com.my.databinding.listener.EventHandleListener" /><import type="com.my.databinding.util.UserUtil" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".UserActivity"><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/guideline2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"app:layout_constraintGuide_end="365dp" /><TextViewandroid:id="@+id/tv_user_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ user.name }"android:textSize="20sp"app:layout_constraintBottom_toTopOf="@+id/guideline2"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_user_star"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ UserUtil.getStar(user.star) }"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@+id/guideline2" /><Buttonandroid:id="@+id/btn_like"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点赞"android:textSize="20sp"android:onClick="@{ eventHandleListener.btnOnClick }"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.498"app:layout_constraintStart_toStartOf="parent"app:layout_constraintVertical_bias="0.508" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(5)Activity Code
  • UserActivity.java
package com.my.databinding;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;import com.my.databinding.databinding.ActivityUserBinding;
import com.my.databinding.listener.EventHandleListener;
import com.my.databinding.entity.User;public class UserActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_user);ActivityUserBinding activityUserBinding = DataBindingUtil.setContentView(this, R.layout.activity_user);User user = new User("张三", 3);activityUserBinding.setUser(user);EventHandleListener eventHandleListener = new EventHandleListener(this);activityUserBinding.setEventHandleListener(eventHandleListener);}
}
4、二级页面数据绑定
(1)Entity
package com.my.databinding.entity;public class User {public String name;public int star;public User(String name, int star) {this.name = name;this.star = star;}
}
(2)Util
  • UserUtil.java
package com.my.jetpackdemo.util;public class UserUtil {public static String getStar(int star) {switch (star) {case 1:return "一星";case 2:return "一星";case 3:return "三星";case 4:return "四星";case 5:return "五星";}return "零星";}
}
(3)Activity Layout
  1. user_son1.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><variablename="user"type="com.my.databinding.entity.User" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/son_tv_user_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ user.name }"android:textColor="#E91E63"android:textSize="24sp"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>
  1. user_son2.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><variablename="user"type="com.my.databinding.entity.User" /><import type="com.my.databinding.util.UserUtil" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/son_tv_user_star"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ UserUtil.getStar(user.star) }"android:textColor="#E91E63"android:textSize="20sp"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>
  1. activity_user_father.java
<?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><variablename="user1"type="com.my.databinding.entity.User" /><variablename="user2"type="com.my.databinding.entity.User" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".UserFatherActivity"><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/gl"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"app:layout_constraintGuide_percent="0.5" /><includelayout="@layout/user_son1"app:user="@{ user1 }"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toTopOf="@+id/gl"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><includelayout="@layout/user_son2"app:user="@{ user2 }"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@+id/gl" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • UserFatherActivity.java
package com.my.databinding;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;import android.os.Bundle;import com.my.databinding.databinding.ActivityUserFatherBinding;
import com.my.databinding.entity.User;public class UserFatherActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityUserFatherBinding activityUserFatherBinding = DataBindingUtil.setContentView(this, R.layout.activity_user_father);User user1 = new User("张三", 3);User user2 = new User("李四", 4);activityUserFatherBinding.setUser1(user1);activityUserFatherBinding.setUser2(user2);}
}
5、RecyclerView 数据绑定
(1)Entity
  • Student.java
package com.my.databinding.entity;public class Student {public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}public String getAgeStr() {return age + "";}
}
(2)Activity Layout
  1. recycler_view_binding_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"><data><variablename="student"type="com.my.databinding.entity.Student" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#03A9F4"android:paddingTop="25dp"android:paddingBottom="25dp"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ student.name }"android:textSize="24sp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_age"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="25dp"android:text="@{ student.ageStr }"android:textSize="20sp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_name" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  1. activity_recycler_view_binding.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.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.my.databinding.RecyclerViewBindingActivity"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv"android:layout_width="match_parent"android:layout_height="match_parent"tools:ignore="MissingConstraints"tools:layout_editor_absoluteX="1dp"tools:layout_editor_absoluteY="1dp" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(3)Adapter
  • RecyclerViewBindingAdapter.java
package com.my.databinding.adapter;import android.view.LayoutInflater;
import android.view.ViewGroup;import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;import com.my.databinding.R;
import com.my.databinding.databinding.RecyclerViewBindingItemBinding;
import com.my.databinding.entity.Student;import java.util.List;public class RecyclerViewBindingAdapter extends RecyclerView.Adapter<RecyclerViewBindingAdapter.MyViewHolder> {List<Student> studentList;public RecyclerViewBindingAdapter(List<Student> studentList) {this.studentList = studentList;}@NonNull@Overridepublic MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {RecyclerViewBindingItemBinding recyclerViewBindingItemBinding =DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),R.layout.recycler_view_binding_item, parent, false);return new MyViewHolder(recyclerViewBindingItemBinding);}@Overridepublic void onBindViewHolder(@NonNull MyViewHolder holder, int position) {Student student = studentList.get(position);holder.recyclerViewBindingItemBinding.setStudent(student);}@Overridepublic int getItemCount() {return studentList.size();}static class MyViewHolder extends RecyclerView.ViewHolder {private RecyclerViewBindingItemBinding recyclerViewBindingItemBinding;public MyViewHolder(RecyclerViewBindingItemBinding recyclerViewBindingItemBinding) {super(recyclerViewBindingItemBinding.getRoot());this.recyclerViewBindingItemBinding = recyclerViewBindingItemBinding;}}
}
(4)Activity Code
  • RecyclerViewBindingActivity.java
package com.my.databinding;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;import android.os.Bundle;import com.my.databinding.adapter.RecyclerViewBindingAdapter;
import com.my.databinding.databinding.ActivityRecyclerViewBindingBinding;
import com.my.databinding.entity.Student;import java.util.ArrayList;
import java.util.List;public class RecyclerViewBindingActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityRecyclerViewBindingBinding activityRecyclerViewBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view_binding);LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);activityRecyclerViewBindingBinding.rv.setLayoutManager(linearLayoutManager);List<Student> studentList = new ArrayList<>();studentList.add(new Student("jack", 18));studentList.add(new Student("smith", 19));studentList.add(new Student("tom", 20));RecyclerViewBindingAdapter recyclerViewBindingAdapter = new RecyclerViewBindingAdapter(studentList);activityRecyclerViewBindingBinding.rv.setAdapter(recyclerViewBindingAdapter);}
}

四、双向数据绑定

2、演示
(1)Entity
  • Info.java
package com.my.databinding.entity;public class Info {public String username;public String password;public Info(String username, String password) {this.username = username;this.password = password;}
}
(2)viewModel
  • InfoViewModel.java
package com.my.databinding.viewmodel;import android.util.Log;import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;import com.my.databinding.BR;
import com.my.databinding.entity.Info;public class InfoViewModel extends BaseObservable {private Info info;public InfoViewModel(Info info) {this.info = info;}@Bindablepublic String getUsername() {return info.username;}public void setUsername(String username) {if (username != null && !username.equals(info.username)) {info.username = username;Log.d("my ==========", "setUsername: " + info.username);notifyPropertyChanged(BR.username);}}@Bindablepublic String getPassword() {return info.password;}public void setPassword(String password) {if (password != null && !password.equals(info.password)) {info.password = password;Log.d("my ==========", "setPassword: " + info.password);notifyPropertyChanged(BR.password);}}
}
(3)Activity Layout
  • activity_data_binding_two.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><variablename="infoViewModel"type="com.my.databinding.viewmodel.InfoViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".DataBindingTwoActivity"><EditTextandroid:id="@+id/et_username"android:layout_width="wrap_content"android:layout_height="wrap_content"android:ems="10"android:inputType="textPersonName"android:text="@={ infoViewModel.username }"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.1" /><EditTextandroid:id="@+id/et_password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:ems="10"android:inputType="textPersonName"android:text="@={ infoViewModel.password }"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/et_username"app:layout_constraintVertical_bias="0.2" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • DataBindingTwoActivity.java
package com.my.databinding;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;import com.my.databinding.databinding.ActivityDataBindingTwoBinding;
import com.my.databinding.viewmodel.InfoViewModel;
import com.my.databinding.entity.Info;public class DataBindingTwoActivity extends AppCompatActivity {private InfoViewModel infoViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityDataBindingTwoBinding activityDataBindingTwoBinding =DataBindingUtil.setContentView(this, R.layout.activity_data_binding_two);Info info = new Info("123", "456");infoViewModel = new InfoViewModel(info);activityDataBindingTwoBinding.setInfoViewModel(infoViewModel);test();}public void test() {new Thread(() -> {try {Thread.sleep(5000);infoViewModel.setUsername("112233");infoViewModel.setPassword("445566");} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
3、另一种实现演示
(1)Entity
  • Info.java
package com.my.databinding.entity;public class Info {public String username;public String password;public Info(String username, String password) {this.username = username;this.password = password;}
}
(2)viewModel
package com.my.databinding.viewmodel;import android.util.Log;import androidx.databinding.ObservableField;import com.my.databinding.entity.Info;public class AnotherInfoViewModel {public ObservableField<String> username;public ObservableField<String> password;public AnotherInfoViewModel(Info info) {username = new ObservableField<>(info.username);password = new ObservableField<>(info.password);}public String getUsername_() {return username.get();}public void setUsername_(String username) {Log.d("my ==========", "setUsername: " + username);this.username.set(username);}public String getPassword_() {return password.get();}public void setPassword_(String password) {Log.d("my ==========", "setPassword: " + password);this.password.set(password);}
}
(3)Activity Layout
  • activity_data_binding_two_another.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><variablename="anotherInfoViewModel"type="com.my.databinding.viewmodel.AnotherInfoViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.my.databinding.DataBindingTwoAnotherActivity"><EditTextandroid:id="@+id/et_username"android:layout_width="wrap_content"android:layout_height="wrap_content"android:ems="10"android:inputType="textPersonName"android:text="@={ anotherInfoViewModel.username }"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.1" /><EditTextandroid:id="@+id/et_password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:ems="10"android:inputType="textPersonName"android:text="@={ anotherInfoViewModel.password }"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/et_username"app:layout_constraintVertical_bias="0.2" /><Buttonandroid:id="@+id/btn_change"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Change"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
(4)Activity Code
  • DataBindingTwoAnotherActivity.java
package com.my.databinding;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;import android.os.Bundle;import com.my.databinding.databinding.ActivityDataBindingTwoAnotherBinding;
import com.my.databinding.entity.Info;
import com.my.databinding.viewmodel.AnotherInfoViewModel;public class DataBindingTwoAnotherActivity extends AppCompatActivity {private AnotherInfoViewModel anotherInfoViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityDataBindingTwoAnotherBinding activityDataBindingTwoAnotherBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding_two_another);Info info = new Info("123", "456");anotherInfoViewModel = new AnotherInfoViewModel(info);activityDataBindingTwoAnotherBinding.setAnotherInfoViewModel(anotherInfoViewModel);findViewById(R.id.btn_change).setOnClickListener((v) -> {anotherInfoViewModel.setUsername_("112233");anotherInfoViewModel.setPassword_("445566");});test();}public void test() {new Thread(() -> {try {Thread.sleep(5000);anotherInfoViewModel.setUsername_("112233");anotherInfoViewModel.setPassword_("445566");} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915365.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go并发聊天室:从零构建实战

大家好&#xff0c;今天我将分享一个使用Go语言从零开始构建的控制台并发聊天室项目。这个项目虽然简单&#xff0c;但它麻雀虽小五脏俱全&#xff0c;非常适合用来学习和实践Go语言强大的并发特性&#xff0c;尤其是 goroutine 和 channel 的使用。 一、项目亮点与功能特性 …

疯狂星期四第13天运营日报

网站运营第13天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 昨日访问量 昨天大概60个ip, 同比上个星期是高点的&#xff0c;但是与星期四差别还是太大了。&#x1f602; 昨日搜索引擎收录情况 百度依旧0收录 …

吴恩达《AI for everyone》第二周课程笔记

机器学习项目工作流程以Echo/Alexa&#xff08;语音识别AI&#xff09;作为例子解释&#xff1a; 1. collect data 收集数据——人为找很多人说 Alexa&#xff0c;并录制音频&#xff1b;并且还会让一群人说其他词语&#xff0c;比如hello 2. train model 训练模型——用机器学…

uniapp props、$ref、$emit、$parent、$child、$on

1. uniapp props、ref、ref、ref、emit、parent、parent、parent、child、$on 1.1. 父组件和子组件 propsPage.vue导入props-son-view.vue组件的时候,我们就称index.vue为父组件依次类推,在vue中只要能获取到组件的实例,那么就可以调用组件的属性或是方法进行操作 1.2. pr…

4、ubuntu | dify创建知识库 | 上市公司个股研报知识库

1、创建知识库步骤 创建一个知识库并上传相关文档主要涉及以下五个关键步骤&#xff1a; 创建知识库&#xff1a;首先&#xff0c;需要创建一个新的知识库。这可以通过上传本地文件、从在线资源导入数据或者直接创建一个空的知识库来实现。 指定分段模式&#xff1a;接下来是…

Kubernetes中为Elasticsearch配置多节点共享存储

在Kubernetes中为Elasticsearch配置多节点共享存储(ReadWriteMany)需结合存储后端特性及Elasticsearch架构设计。 由于Elasticsearch默认要求每个节点独立存储数据(ReadWriteOnce),直接实现多节点共享存储需特殊处理。 ​​方案一:使用支持ReadWriteMany的存储后端(推荐…

SpringBoot热部署与配置技巧

配置文件SpringBoot 的热部署Spring为开发者提供了一个名为spring-boot-devtools的模块来使SpringBoot应用支持热部署&#xff0c;提高开发者的开发效率&#xff0c;无需手动重启SpringBoot应用相关依赖&#xff1a;<dependency> <groupId>org.springframework.boo…

Python与C#的三元运算符的写法区别

一、语法结构对比​​PyTorch示例​​dev torch.device("cuda:0" if torch.cuda.is_available() else "cpu")​​逻辑​​&#xff1a;若torch.cuda.is_available()为真&#xff0c;则返回"cuda:0"&#xff0c;否则返回"cpu"。​​作…

java 学习篇一

java知识点 一、windows不区分大小写&#xff0c;linux区分大小写 二、写java需要JDK&#xff0c;一般运行环境需要JRE 三、JDK安装一般是傻瓜是安装 四、java主要工具javac、java&#xff1b;其中javac用于编译.java -> .class&#xff1b;java用于执行.class文件执行时候不…

仙盟数据库应用-外贸标签打印系统 前端数据库-V8--毕业论文-—-—仙盟创梦IDE

基于 Excel 标签打印软件的外贸打印流程优化与实践摘要&#xff1a;在全球化外贸业务中&#xff0c;标签打印是货物流通、信息标识的关键环节。本文聚焦 “未来之窗云上打印技术” 的 Excel 标签打印软件&#xff0c;结合外贸平台实际场景&#xff0c;分析其在打印流程中的应用…

【Linux】权限详解 权限本质、权限属性、su、sudo提权、chmod\chown\chgrp、文件类别

文章目录一、权限的认识二、linux的权限本质三、linux的用户su指令sudo提权四、linux角色五、文件权限属性六、修改权限的指令操作chmod指令(权限只会验证一次)chown/chgrp指令修改文件权限的八进制方案七、文件类别详解一、权限的认识 什么是权限&#xff1f; 生活中处处都有权…

rman清理归档

1进入rman rman target / 2&#xff1a;列出所有归档日志的路径 LIST ARCHIVELOG ALL; 3.然后在执行 crosscheck archivelog all;&#xff08;检查 RMAN 存储库中记录的归档日志是否在磁盘或备份存储中实际存在。 4.然后在执行 delete noprompt expired archivelog all;&…

Selenium 处理动态网页与等待机制详解

在使用 Selenium 进行网页自动化操作时&#xff0c;动态网页往往是开发者遇到的第一个 “拦路虎”。想象一下&#xff1a;你明明在代码中写好了元素定位逻辑&#xff0c;运行时却频繁报错 “元素不存在”&#xff0c;但手动打开网页时元素明明就在眼前 —— 这很可能是因为网页…

Salesforce 与外部系统实时集成:基于事件驱动的异步集成架构

在 Salesforce 与外部系统&#xff08;如 ERP、财务系统、物流系统等&#xff09;的实时集成中&#xff0c;“稳定性” 是核心挑战 —— 既要保证数据同步的及时性&#xff0c;又要应对网络波动、系统故障、并发冲突等不可控因素。以下从问题本质、技术瓶颈、解决方案细节三个维…

React 的 `cache()` 函数

文章目录前言一、核心作用二、工作原理三、使用场景1. 避免重复数据请求2. 优化昂贵计算四、缓存规则详解五、与其它缓存方式对比六、服务端特殊行为七、最佳实践八、缓存失效策略九、使用限制十、与数据获取库集成总结&#xff1a;何时使用 cache()前言 React 的 cache() 函数…

大白编译——autotools与cmake

注意: 本文内容于 2025-07-20 01:58:56 创建,可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容,请访问原文地址:大白编译——autotools与cmake。感谢您的关注与支持! 之前记录了通过autotools编译rpm包与deb包的步骤。参考小白编译——rpm包与deb包 - …

react19+nextjs+antd切换主题颜色

在 React 19 Next.js Ant Design 项目中实现主题切换功能&#xff0c;可以通过以下步骤完成。这里将提供完整方案&#xff0c;包含静态主题切换和动态实时切换两种方式。一、基础配置&#xff08;Ant Design 主题支持&#xff09; 1. 安装必要依赖 npm install antd ant-desi…

Modbus Slave 使用教程:快速搭建模拟从站进行测试与开发

文章目录Modbus Slave 使用教程&#xff1a;快速搭建模拟从站进行测试与开发步骤详解&#xff1a;搭建 Modbus Slave1. 安装与启动2. 配置从站连接 (Connection Setup)连接3. 定义从站数据 (设置寄存器/线圈映射)4. 设置初始值与变化模式 (可选但重要)5. 连接 Master 进行测试高…

通俗易懂神经网络:从基础到实现

引言 神经网络是人工智能和深度学习的核心&#xff0c;它模仿人脑的工作方式&#xff0c;通过数据学习复杂的模式。本文将以通俗易懂的方式讲解神经网络的基础知识&#xff0c;包括单层神经网络、多层神经网络&#xff0c;最后用Python代码实现一个简单的神经网络模型。1. 神经…

【Linux】基本指令详解(三) 指令本质、三个查找指令、打包压缩、重要热键、linux体系结构、命令行解释器

文章目录date指令cal指令find指令(指令本质也是文件)which指令file指令whereis指令alias指令grep指令top指令打包和压缩指令zip/unzip指令关于rzsz(linux与windows互传 )tar指令linux机器之间互传bc指令uname指令(查看linux机器体系结构)几个重要的热键[Tab]按键[Ctrl]c按键[Ct…