笔记,记录一下ViewModel,LiveData,DataBinding的使用小案例。
利用ViewMode点击一个按钮把数字加一
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".jetpack.viewmodel.ViewModelActivity">
<RelativeLayout
android:id="@+id/rl_top"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:textSize="24sp"
android:text="0"/>
<Button
android:id="@+id/bt_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:layout_centerHorizontal="true"
android:layout_below="@id/tv_number"/>
</RelativeLayout>
</RelativeLayout>
然后在activity中编写
public class ViewModelActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mTvNumber;
private Button mBtPlus;
private MyViewModel myViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
mTvNumber = findViewById(R.id.tv_number);
myViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
mTvNumber.setText(myViewModel.number+"");
mBtPlus = findViewById(R.id.bt_plus);
mBtPlus.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if(id == R.id.bt_plus){
mTvNumber.setText((++myViewModel.number)+"");
}
}
}
我们的数据也就存入到了MyViewModel中,代码如下:
public class MyViewModel extends ViewModel {
public int number;
}
利用ViewModel 存储的数据,在手机发生旋转的时候不会被重新还原,我们都知道,如果没有在清单文件设置activity的android:configChanges是会导致重新走生命周期的,数据也就会还原了。
android:configChanges="keyboardHidden|orientation|screenSize"
ViewModel+LiveData使两个fragment数据共享
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context=".jetpack.livedata.LiveDataActivity">
<fragment
android:id="@+id/first"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.example.myapplication.jetpack.livedata.FirstFragment"/>
<fragment
android:id="@+id/second"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.example.myapplication.jetpack.livedata.SecondFragment"/>
</LinearLayout>
布局很简单,上下一分为二设置两个fragment
然后准备我们的activity
public class LiveDataActivity extends AppCompatActivity {
private MyViewModel myViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_data);
}
public MyViewModel getViewModel(){
if(myViewModel == null){
myViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
}
return myViewModel;
}
}
ok,可以写我们的fragment的界面了,因为两个fragment界面代码和逻辑代码都一样,所以只贴一份出来。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".jetpack.livedata.FirstFragment">
<SeekBar
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:min="0"
android:max="100"/>
</FrameLayout>
public class FirstFragment extends Fragment {
private SeekBar seekBar;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
seekBar = view.findViewById(R.id.seekbar);
final MyViewModel myViewModel = ((LiveDataActivity)getActivity()).getViewModel();
myViewModel.getProgress().observe(getActivity(), new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
Log.d("yanjin","progress = integer "+integer);
seekBar.setProgress(integer);
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
myViewModel.getProgress().setValue(progress);
Log.d("yanjin","progress = "+progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
ok,这就能实现一边拖动seekbar另外一边的seekbar联动的效果了。
注意一点哦,ViewModel的setValue是在主线程执行,如果需要在子线程更新数据,得用postValue。
DataBinding的使用
一个简单的案例了解一下DataBinding
我们看布局
<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="userBean"
type="com.example.myapplication.jetpack.databinding.UserBean" />
<variable
name="onClick"
type="com.example.myapplication.jetpack.databinding.DataBindingActivity" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".jetpack.databinding.DataBindingActivity">
<ImageView
android:id="@+id/iv_head"
android:layout_width="100dp"
android:layout_height="100dp"
app:image="@{userBean.head}"
android:onClick="@{onClick::onClickView}"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:src="@drawable/ic_launcher_background"/>
<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_centerHorizontal="true"
android:layout_below="@id/iv_head"
android:layout_marginTop="80dp"
android:text="@{String.valueOf(userBean.age)}"
tools:text="年龄"/>
<include layout="@layout/user_age"
app:userBean="@{userBean}"/>
</RelativeLayout>
</layout>
然后include里面的代码如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="userBean"
type="com.example.myapplication.jetpack.databinding.UserBean" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_centerHorizontal="true"
android:layout_marginTop="180dp"
android:text="@{userBean.userName}"
tools:text="姓名"/>
</RelativeLayout>
</layout>
那么怎么把普通的布局转成DataBinding专用的呢?
首先在gradle中加入
dataBinding{
enabled = true
}
然后选中普通布局的最外成layout,alt+回车就能选择切换成databinding的布局了
在代码中
<variable
name="userBean"
type="com.example.myapplication.jetpack.databinding.UserBean" />
是将数据源传入到布局中。然后绑定数据,比如简单的text数据绑定
android:text="@{String.valueOf(userBean.age)}"
如果要绑定网络图片怎么办?那就要用到BindingAdapter注解了,我们写一个工具类,以后想要绑定网络图片都可以用这个工具类
public class ImageBindingAdapter {
@BindingAdapter("image")
public static void setImage(ImageView imageView,String path){
RequestOptions options = new RequestOptions();
options.placeholder(R.drawable.ic_launcher_background);
options.error(R.drawable.ic_launcher_background);
Glide.with(imageView.getContext()).load(path).apply(options).into(imageView);
}
}
然后在布局的imageview中使用
app:image="@{userBean.head}"
这里为啥用image呢?因为要和BindingAdapter注解中的参数字段统一。
关于点击事件的绑定,网络上的解法五花八门,这里就介绍一种,直接将要需要处理点击事件的类传进来,写上方法即可
<variable
name="onClick"
type="com.example.myapplication.jetpack.databinding.DataBindingActivity" />
这里我们传入activity了,那么在布局中添加点击事件
android:onClick="@{onClick::onClickView}"
最后传入的DataBindingActivity实现onClickView方法,整体activity代码如下:
public class DataBindingActivity extends AppCompatActivity {
private UserBean userBean;
private ActivityDataBindingBinding mViewDataBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding);
initData();
mViewDataBinding.setUserBean(userBean);
mViewDataBinding.setOnClick(this);
}
private void initData() {
userBean = new UserBean("张山",18,"https://www.baidu.com/img/flexible/logo/pc/result.png");
}
public void onClickView(View view){
if(view.getId() == R.id.iv_head){
Toast.makeText(view.getContext(),"哪里不会点哪里",Toast.LENGTH_LONG).show();
}
}
}
记住,布局里面需要的数据或者事件类,都要传入。
mViewDataBinding.setUserBean(userBean);
mViewDataBinding.setOnClick(this);
onClickView这个方法的话,用法就和平时实现view的onClick方法一样了,可以根据view的id判断,并做出相应处理。
当然,除了variable导入类和数据,还有import也可以不如我们在布局中导入一个工具类
<import type="com.example.l.gjj_databinding_demo.MyStringUtils"/>
然后在布局中就能使用了
android:text="@{MyStringUtils.capitalize(user.firstName)}"
还有,如果include中的布局也需要传入数据的话,就用
app:userBean="@{userBean}"
app:userBean这个和上面variable中的定义要统一。
RecycleView中使用DataBinding
老样子,布局登场
<?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>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".jetpack.databinding.recycle.DataBindingRecycleViewActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
</layout>
activty代码入场
public class DataBindingRecycleViewActivity extends AppCompatActivity {
private ActivityDataBindingRecycleViewBinding mViewBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding_recycle_view);
mViewBinding.recycleView.setLayoutManager(new LinearLayoutManager(this));
MyAdapter myAdapter = new MyAdapter(initData());
mViewBinding.recycleView.setAdapter(myAdapter);
}
private List<String> initData() {
List<String> list = new ArrayList<>();
for(int x = 0;x<100;x++){
list.add(x+"");
}
return list;
}
}
重中之重的adapter代码
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> list;
public MyAdapter(List<String> list){
this.list = list;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.test_item_layout, parent, false);
TestItemLayoutBinding bind = DataBindingUtil.bind(view);
return new ViewHolder(bind);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.binding.setNumber(list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
TestItemLayoutBinding binding;
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
public ViewHolder(TestItemLayoutBinding bind) {
super(bind.getRoot());
binding = bind;
}
}
}
有木有发现,我们不用再用itemView去findViewById了真好
最后的条目布局登场
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="number"
type="String" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="45dp">
<TextView
android:id="@+id/tv_number"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{number}"
android:gravity="center" />
</RelativeLayout>
</layout>
ViewModel+LiveData+DataBinding案例
来个综合点的案例,有两个分数,一个是A队分数,一个是B队分数,点击+3,分别为A或B队加3分,重置是把分数清零,撤销上一步操作是返回上一次分数。
直接上代码!!!
<?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="viewModel"
type="com.example.myapplication.jetpack.databinding.dlv.MyViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".jetpack.databinding.dlv.DLVActivity">
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_number1"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="@{String.valueOf(viewModel.teamAScore)}"
android:textSize="18sp"
tools:text="0"
android:textColor="@android:color/white"
android:layout_centerHorizontal="true"
android:gravity="center"
android:background="@color/colorPrimary"/>
<Button
android:id="@+id/btn_plus1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="+3"
android:onClick="@{viewModel.addTeamScore}"
android:layout_below="@id/tv_number1"
android:textSize="14sp"/>
<Button
android:id="@+id/btn_reset_last"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="撤销上一步操作"
android:onClick="@{viewModel.resetLast}"
android:layout_below="@id/btn_plus1"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_number2"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="@{String.valueOf(viewModel.teamBScore)}"
android:textSize="18sp"
tools:text="0"
android:textColor="@android:color/white"
android:layout_centerHorizontal="true"
android:background="@color/colorPrimaryDark"
android:gravity="center"/>
<Button
android:id="@+id/btn_plus2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="+3"
android:onClick="@{viewModel.addTeamScore}"
android:layout_below="@id/tv_number2"
android:textSize="14sp"/>
<Button
android:id="@+id/btn_reset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="重置"
android:onClick="@{viewModel.reset}"
android:layout_below="@id/btn_plus2"/>
</RelativeLayout>
</LinearLayout>
</layout>
public class DLVActivity extends AppCompatActivity {
private ActivityDLVBinding mBinding;
private MyViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_d_l_v);
mViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
mBinding.setViewModel(mViewModel);
mViewModel.getTeamAScore().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
mBinding.setViewModel(mViewModel);
}
});
mViewModel.getTeamBScore().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
mBinding.setViewModel(mViewModel);
}
});
}
}
public class MyViewModel extends ViewModel {
private MutableLiveData<Integer> teamAScore;
public MutableLiveData<Integer> getTeamAScore() {
if(teamAScore == null){
teamAScore = new MutableLiveData<>();
teamAScore.setValue(0);
}
return teamAScore;
}
private MutableLiveData<Integer> teamBScore;
public MutableLiveData<Integer> getTeamBScore() {
if(teamBScore == null){
teamBScore = new MutableLiveData<>();
teamBScore.setValue(0);
}
return teamBScore;
}
private int lastATeamScore = 0;
private int lastBTeamScore = 0;
public void addTeamScore(View view){
int id = view.getId();
if(id == R.id.btn_plus1){
Integer value = teamAScore.getValue();
lastATeamScore = value;
value = value + 3;
teamAScore.setValue(value);
}else if(id == R.id.btn_plus2){
Integer value = teamBScore.getValue();
lastBTeamScore = value;
value = value + 3;
teamBScore.setValue(value);
}
}
public void reset(View view){
int id = view.getId();
if(id == R.id.btn_reset){
teamAScore.setValue(0);
teamBScore.setValue(0);
}
}
public void resetLast(View view){
int id = view.getId();
if(id == R.id.btn_reset_last){
teamAScore.setValue(lastATeamScore);
teamBScore.setValue(lastBTeamScore);
}
}
}
有木有发现,现在的Activity变得特别的清晰明了了,哈哈哈哈。