AndroidMVVM架構
- Android MVVM架構,這一篇就夠
- 相關技術
- Activity模式的準備作業
- 一些控制元件的系結
- 1.TextView
- 2. 系結點擊事件
- 3. 系結其他控制元件,就拿ImageView舉例(任何控制元件都可以如此系結)
- 4. 系結串列等有配接器的控制元件,ListView為例
- Fragment模式的準備作業
- 1.給MainActivity加上VM
- 2.Fragment的vm我們還種方式系結
- 針對配接器物體類的系結補充
Android MVVM架構,這一篇就夠
這篇文章主要是分享我學習安卓前沿技術架構Jetpack的MVVM 主要分為兩個部分,一是Activity中使用AndroidViewModel名另外一個是Fragment里使用ViewModel,
MVVM優越性不許多說,內容不周之處歡迎指正,
本文所用到的技術倉庫在此:
https://gitee.com/jimonik/my-news/
相關技術
如果您對此了解可以跳過閱讀
- DataBinding ,資料系結,主要是把XML布局實體化成為一個bind類,在原始碼撰寫的時候就生成.class用以和另外一個類實體(ViewModel)進行系結,系結操作在Activity里;
- XML寫法變動主要是把工具包參考放入標簽,其內增加標簽對和布局,布局可以直接使用data中引入的class,ViewModel中也可以通過@BindAdapter對布局中的控制元件進行系結注解達到初始化的目的;
- VM也就是ViewModel,里面放雙向系結的資料(我更喜歡用MuteableLiveData)、靜態系結事件等,老師說的MVC的M貌似可以和它完全沒影響的一起作業(我的猜測);
Activity模式的準備作業
-
Android Studio創建一個空模板,大概是這個鬼樣子:
2. 創建一個MainVM類,繼承AndroidViewModel,大概是這樣

3.在App的build.gradle里寫dataBinding.enabled=true打開資料系結,并同步,
4.布局改稱這樣就完成:
其中vm就是起的類名字, -
系結布局和VM的操作放在MainActivity里面:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//獲取布局系結實體
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//獲取VM實體
MainVM vm = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MainVM.class);
//把他們邦在一起
binding.setVm(vm);
//設定VM所使用的生命周期
binding.setLifecycleOwner(this);
/**
* 以下是我的習慣:
*
* 因為在VM中我們可能要用到MainActivity彈Toast什么的,因此需要傳一個this
* 因為若涉及到配接器串列等控制元件、以及資料系結則需要在VM中用到binding
* 因此VM中的setBinding,我把他當作初始化函式使用更方便!
*/
vm.setBinding(binding,this);
}
}
6此時的VM:
public class MainVM extends AndroidViewModel {
private static ActivityMainBinding binding;
@SuppressLint("StaticFieldLeak")
private static MainActivity mainActivity;
public MainVM(@NonNull Application application) { super(application); }
public void setBinding(ActivityMainBinding binding, MainActivity mainActivity) {
//把binding和mainActivity都賦值給MainVM作為靜態變數備用,因為很多系結的控制元件都只能用靜態方法
MainVM.binding =binding;
MainVM.mainActivity =mainActivity;
}
}
一些控制元件的系結
1.TextView
在MainVM中使用MutableLiveData 其中MutableLiveData的泛型是要監聽系結資料的型別,
public class MainVM extends AndroidViewModel {
private static ActivityMainBinding binding;
@SuppressLint("StaticFieldLeak")
private static MainActivity mainActivity;
public static MutableLiveData<String> text=new MutableLiveData<>();
public MainVM(@NonNull Application application) {
super(application);
text.setValue("這是初始值");
}
public void setBinding(ActivityMainBinding binding, MainActivity mainActivity) {
//把binding和mainActivity都賦值給MainVM作為靜態變數備用,因為很多系結的控制元件都只能用靜態方法
MainVM.binding =binding;
MainVM.mainActivity =mainActivity;
}
}
同時在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="vm" type="com.demo.MainVM"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{vm.text}"/>
</LinearLayout>
</layout>
這樣,每當text.setValue()就會實時更新界面上的資料,
若是EditText,則將 @{vm.text} 改成 @={vm.text}就可以實作修改編輯框,在vm中的text.getValue()時可以實施獲取資料,即資料雙向系結,
2. 系結點擊事件
一般都是在onClick中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="vm" type="com.demo.MainVM"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:onClick="@{vm::click}"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
然后在VM中加個普通點擊事件(必須是靜態)
public class MainVM extends AndroidViewModel {
private static ActivityMainBinding binding;
@SuppressLint("StaticFieldLeak")
private static MainActivity mainActivity;
public MainVM(@NonNull Application application) { super(application); }
public void setBinding(ActivityMainBinding binding, MainActivity mainActivity) {
//把binding和mainActivity都賦值給MainVM作為靜態變數備用,因為很多系結的控制元件都只能用靜態方法
MainVM.binding =binding;
MainVM.mainActivity =mainActivity;
}
public static void click(View view){
Toast.makeText(mainActivity, "你點擊了按鈕", Toast.LENGTH_SHORT).show();
}
}
3. 系結其他控制元件,就拿ImageView舉例(任何控制元件都可以如此系結)
<ImageView
android:layout_width="100dp"
app:bindImage="@{vm.imgdir}"
android:layout_height="100dp"
tools:ignore="ContentDescription" />
這一句
app:bindImage="@{vm.imgdir}"
的bindImage是隨便起的一個名字,而
vm.imgdir
則是在VM中定義的
public static MutableLiveData<String> imgdir=new MutableLiveData<>();
注意,如果app報錯則需在根節點加入參考:
xmlns:app="http://schemas.android.com/apk/res-auto"
而在VM中則
public static MutableLiveData<String> imgdir=new MutableLiveData<>();
@BindingAdapter("bindImage")
public static void bindImage(ImageView imageView,MutableLiveData<String> imgdir){
if (!imgdir.getValue().equals("")){
imageView.setImageBitmap(BitmapFactory.decodeFile(imgdir.getValue()));
}
}
其中imgdir需要在構造器中初始化, @BindingAdapter(“bindImage”)的bindImage正是xml中我起的名字的,這個方法名字可以隨意起,我這里和BindingAdapter里面的保持一樣(習慣),
引數一:就是要系結的控制元件實體,可以用來初始化,設定點擊事件什么的,
引數二:是xml中傳入的資料,也就是在VM中定義的imgdir,
作業順序是一旦imgdir.setValue()或者imgdir.postValue()更新了資料,那么bindImage方法便會執行,就實作了資料的監聽,在bindImage方法中設定布局內容就相當于更新后的資料同步布局,達到高度解耦效果,
4. 系結串列等有配接器的控制元件,ListView為例
首先先準備配接器需要的幾個檔案
1.Adapter
public class Adapter extends BaseAdapter {
Context context;
public List<Bean> data;
public Adapter(Context context, List<Bean> objects){
super();
this.context=context;
data=objects;
}
@Override
public int getCount() {
return Objects.requireNonNull(data).size();
}
@Override
public Object getItem(int position) {
return Objects.requireNonNull(data).get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
@SuppressLint("ViewHolder") ViewDataBinding binding= DataBindingUtil.inflate(LayoutInflater.from(context),R.layout.item, parent, false);
binding.setVariable(BR.bean, Objects.requireNonNull(data).get(position));
return binding.getRoot();
}
}
可見,getView中的
binding.setVariable(BR.bean, Objects.requireNonNull(data).get(position));
正是使物體類bean(相當于Activity的VM)和item系結在一起的方法,經此一句,再不復寫其他代碼,
- Bean物體類
public class Bean {
public String text;
public Bean(String text){
this.text=text;
}
}
建議寫成public,不要寫任何setter和getter
- item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="bean" type="com.demo.Bean" />
</data>
<LinearLayout
android:padding="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="@{bean.text}"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</LinearLayout>
</layout>
細品,和activity的布局、vm一毛一樣,vm和bean,就換了名字
- 在VM設定adapter和串列點擊長按事件,(在setBinding中)
public class MainVM extends AndroidViewModel {
@SuppressLint("StaticFieldLeak")
private static ActivityMainBinding binding;
@SuppressLint("StaticFieldLeak")
private static MainActivity mainActivity;
//初始化好
private final List<Bean> data=new ArrayList<>();
public MainVM(@NonNull Application application) { super(application); }
public void setBinding(ActivityMainBinding binding, MainActivity mainActivity) {
//把binding和mainActivity都賦值給MainVM作為靜態變數備用,因為很多系結的控制元件都只能用靜態方法
MainVM.binding =binding;
MainVM.mainActivity =mainActivity;
//設定配接器方式和以往不同
binding.setAdp(new Adapter(mainActivity,data));
//通過binding來設定點擊長按事件
binding.list.setOnItemClickListener(null);
binding.list.setOnItemLongClickListener(null);
//往串列里添加資料
data.add(new Bean("emmmmm"));
//更新串列
binding.getAdp().notifyDataSetChanged();
//不在主線陳更新
mainActivity.runOnUiThread(() -> binding.getAdp().notifyDataSetChanged());
}
}
Fragment模式的準備作業
以官方自帶的boot navigation activity為例,他創建好三這樣的:
他把一個activity分成了三個fragment,每一個fragment分配了一個ViewModel,其中一個是:
public class DashboardFragment extends Fragment {
private DashboardViewModel dashboardViewModel;
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
dashboardViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
final TextView textView = root.findViewById(R.id.text_dashboard);
dashboardViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
textView.setText(s);
}
});
return root;
}
}
不難發現,fragment的VM實體寫好后,通過observe觀察資料的方式在fragment中監聽資料,大部分代碼還是寫在了fragment中,不符合最優解耦方式,這里我們稍微 改變一下:
1.給MainActivity加上VM
在build.gradle中:dataBinding.enabled=true
MainVM.java
public class MainVM extends AndroidViewModel {
@SuppressLint("StaticFieldLeak")
private static MainActivity mainActivity;
private static ActivityMainBinding binding;
public MainVM(@NonNull Application application) {
super(application);
}
@BindingAdapter("bindNav")
public static void bindNav(BottomNavigationView bottomNavigationView,MainVM mainVM){
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications).build();
NavController navController = Navigation.findNavController(mainActivity, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(mainActivity, navController, appBarConfiguration);
NavigationUI.setupWithNavController(bottomNavigationView, navController);
}
public void setBinding(ActivityMainBinding binding, MainActivity mainActivity) {
MainVM.binding =binding;
MainVM.mainActivity =mainActivity;
}
}
把底部的導航欄通過系結的方法監聽其點擊,
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
MainVM vm = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MainVM.class);
binding.setVm(vm);
binding.setLifecycleOwner(this);
vm.setBinding(binding,this);
}
}
這是常規的系結方法,
activity_main.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">
<data>
<variable
name="vm"
type="com.demo.MainVM" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
app:bindNav="@{vm}"
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
也就增加了個 app:bindNav="@{vm}"
2.Fragment的vm我們還種方式系結
以dashboard為例
DashboardFragment.java改成
public class DashboardFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
FragmentDashboardBinding binding = DataBindingUtil.bind(root);
DashboardViewModel vm= new ViewModelProvider(this).get(DashboardViewModel.class);
binding.setVm(vm);
binding.setLifecycleOwner(this);
vm.setBinding(binding,requireActivity());
return root;
}
}
DashboardViewModel.java改成
public class DashboardViewModel extends ViewModel {
public static MutableLiveData<String> text=new MutableLiveData<>();
public DashboardViewModel() {
text.setValue("初始直");
}
public void setBinding(FragmentDashboardBinding binding, FragmentActivity requireActivity) {
}
}
fragment_dashboard.xml改成
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="vm"
type="com.demo.ui.dashboard.DashboardViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@{vm.text}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
就這樣,fragment的資料系結形式改成了和Activity一樣的方式,系結的方式也和Activity一樣,
針對配接器物體類的系結補充
有時,在串列物體類中可能需要寫@BindAdapter方法進行耗時操作,
我們同樣可以傳入activity讓他回到主執行緒
例如
//這是個Bean物體類
public class CollectionBean {
public String newsId,userId,title,id;
public CollectionBean(String id,String newsId, String userId){
this.id=id;
this.newsId=newsId;
this.userId=userId;
}
@BindingAdapter(value = {"bindTitle","activity"}, requireAll = false)
public static void getTitle(TextView textView, String newsId, Activity activity){
BmobApi.get("https://api2.bmob.cn/1/classes/news/"+newsId, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
activity.runOnUiThread(() -> textView.setText("未知標題"));
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
activity.runOnUiThread(() -> {
//這里是主執行緒
});
}
});
}
}
總結完畢
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/282320.html
標籤:其他
