引言
作为一名安卓的开发人员,对各种架构应该是非常熟悉的:MVC,MVP,MVVM;包括现在留下的MVI,这些都是前人的智慧的结晶,都有各自的应用场景。 作为一名目前技术一般的开发人员,我们这里不去分析整个架构的流程,功能点等;直接贴一下自己使用的base类,需要的人可以直接CV获取。
PS:
这里为了不写findViewById,直接集成了DataBinding,需要加入如下依赖:
dataBinding {
enabled = true;
}
BaseRepository
public class BaseRepository {
}
- repository作为model层,负责与底层(相对于ui来说的)进行交互,如:网络请求,本地数据查询等。
- 我一般会在这里使用rxjava+retrofit进行网络请求,在数据请求返回时,通过接口或者LiveDataBus将数据回调到viewmodel中。
- 这里比较推荐构造一个工厂,来生产不同情况下需要的repository。
- 仓库工厂类如下:
public class RepositoryFactory {
private static RepositoryFactory sInstance;
private RepositoryFactory() {
}
@NonNull
public static RepositoryFactory getInstance() {
if (sInstance == null) {
sInstance = new RepositoryFactory();
}
return sInstance;
}
private HashMap<String, BaseRepository> mRepositoryMap = new HashMap<>();
/**
* 获取对应的repository对象 如果有就用之前的 没有就创一个新的
*/
public <T extends BaseRepository> T getRepository(@NonNull Class<T> repositoryClass) {
String canonicalName = repositoryClass.getCanonicalName();
if (mRepositoryMap.containsKey(canonicalName)) {
return (T) mRepositoryMap.get(canonicalName);
} else {
BaseRepository repository = create(repositoryClass);
mRepositoryMap.put(canonicalName, repository);
return (T) repository;
}
}
/**
* 使用默认构造创建一个新的
*/
@NonNull
private <T extends BaseRepository> T create(@NonNull Class<T> repositoryClass) {
try {
return repositoryClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + repositoryClass, e);
}
}
}
BaseViewModel
public abstract class BaseViewModel<K extends BaseRepository> extends ViewModel {
protected K mRepository;
public BaseViewModel() {
init();
}
protected void init() {
mRepository = requireRepository();
}
/**
* 返回对应的仓库实例 如: BaseRepository对象
* return RepositoryFactory.getInstance().getRepository(AccountRepository.class);
*/
protected abstract K requireRepository();
}
- viewModel用于数据处理:例如用户点击了界面的button用于登录,就需要在viewModel去封装对应的如用户名、密码等数据,并使用repository进行数据请求;又比如,请求歌单数据,后端返回了很复杂的数据,而我们的界面中,并不需要这么复杂的数据(比如返回数据有三十个字段,界面需要的可能就四五个),这样我们就可以在数据返回时,做一定的处理
- --当然,这种情况我还是使用的比较少的,一般都是直接返回到ui层...
- 这里的viewmodel,需要传入一个repository的泛型,与之绑定。
BaseActivity
public abstract class BaseActivity<T extends ViewDataBinding, V extends BaseViewModel> extends AppCompatActivity {
protected T mDataBinding;
protected V mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
mViewModel = getViewModel();
initView();
initListener();
initObserver();
}
/**
* 初始化界面组件
*/
protected abstract void initView();
/**
* 初始化监听之类
*/
protected abstract void initListener();
/**
* 初始化 观察者监听
*/
protected abstract void initObserver();
/**
* 使用view model provider 构造view model
*/
protected abstract V getViewModel();
/**
* 返回布局id
*/
protected abstract int getLayoutId();
- BaseActivity需要传入对应的dataBinding泛型和viewmodel的泛型,viewmodel泛型如前文的repository一样;而dataBinding的泛型,需要你在引入databinding的依赖后,在布局文件中,使用alt+enter,选择convertxxx的来转换成dataBinding对应的布局,这样就会生产对应的Binding类,Binding类的名字是根据你的布局的名字来的:如activity_main对应的是ActivityMainBinding。
- 除此以外,你还需要在你的getLayoutId()方法中,返回你自己的布局id,这样,当activity运行起来时,就会自动显示对应的布局了,不需要手动编写setContentView()方法和findViewById了。
- getViewModel()方法需要返回一个viewModel对象,这个返回的就是后面实际上操作的对象。
BaseFragment
public abstract class BaseFragment<T extends ViewDataBinding, V extends BaseViewModel> extends Fragment {
protected T mDataBinding;
protected V mViewModel;
protected Context mContext;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
mContext = requireContext();
mViewModel = getViewModel();
initView();
initListener();
initObserver();
mDataBinding.setLifecycleOwner(this);
return mDataBinding.getRoot();
}
/**
* 初始化界面组件
*/
protected abstract void initView();
/**
* 初始化监听之类
*/
protected abstract void initListener();
/**
* 初始化 观察者监听
*/
protected abstract void initObserver();
/**
* 使用view model provider 构造view model
*/
protected abstract V getViewModel();
/**
* 返回对应的布局id
*/
protected abstract int getLayoutId();
public void toFragment(@NonNull BaseFragment fragment) {
MainActivity activity = (MainActivity) requireActivity();
if (activity != null) activity.toFragment(fragment);
else LogUtils.e("require activity error...");
}
- 就我自己的开发过程中来说,写fragment还是比activity多的多,比较喜欢多一些fragment少activity的方式。
- 对应的需要进行修改的地方,与BaseActivity一致。
使用例子:
- 纸上得来终觉浅,绝知此事要躬行。
- 看了这么多,不如直接实操一下。阅读者可以直接手动粘贴上方的base类到项目中,接着就可以试一下下方的例子。 构建“仓库”:
- 我们有很多种方式从repository返回数据到viewModel:接口、eventbus等。这里我选择常见的接口进行数据返回,为了书写方便,减少重复代码,我选择修改一下base类:
public class BaseRepository<T extends BaseRepository.BaseCallback> {
protected T callback;
public void setCallback(T baseCallback) {
this.callback = baseCallback;
}
public interface BaseCallback {
}
}
构建仓库类:
public class MainRepository extends BaseRepository<MainRepository.OnRequestBaseCallback> {
/**
* 请求数据
*/
public void requestData() {
// 这里延时操作来模拟数据返回
new Thread(() -> {
// 这里模拟请求数据返回
List<String> data = new ArrayList<String>() {{
add("张三");
add("李四");
add("王五");
}};
if (callback != null) {
callback.onUserCallback(data);
}
}).start();
}
public interface OnRequestBaseCallback extends BaseCallback {
void onUserCallback(@NonNull List<String> data);
}
}
这里我们创建了一个MainRepository,请求数据,返回一个String类型的list,并当接受到返回的数据时,通过接口进行回调。
构建viewModel
```
public class MainViewModel extends BaseViewModel<MainRepository> {
private MutableLiveData<List<String>> userList = new MutableLiveData<>();
public MutableLiveData<List<String>> getUserList() {
return userList;
}
@Override
protected void init() {
super.init();
mRepository.setCallback(data -> {
userList.setValue(data);
});
}
/**
* 请求数据
*/
public void requestData(){
mRepository.requestData();
}
@Override
protected MainRepository requireRepository() {
return RepositoryFactory.getInstance().getRepository(MainRepository.class);
}
}
```
viewmodel中使用MutableLiveData+泛型的方式声明数据,当数据返回时,只需要将数据同步到MutableLiveData中即可。
构建fragment
public class MainFragment extends BaseFragment<LayoutFragmentMainBinding, MainViewModel> {
@SuppressLint("ClickableViewAccessibility")
@Override
protected void initView() {
}
@Override
protected void initListener() {
// 测试请求数据
mDataBinding.topBar.setOnClickListener(v -> mViewModel.requestData());
}
@Override
protected void initObserver() {
mViewModel.getUserList().observe(this, strings -> {
// todo 在这里接收到数据返回 更新ui
});
}
@Override
protected MainViewModel getViewModel() {
return new ViewModelProvider(requireActivity()).get(MainViewModel.class);
}
@Override
protected int getLayoutId() {
return R.layout.layout_fragment_main;
}
}
结束
上述就是一个完整的,我平时常用的一个可以快速构建的mvvm模板,希望对读者可以带来一定的帮助。
补充BaseView
- 相信读者都对自定义有自己独到的见解,诚然,你当然可以自己写一个view来继承view或者viewgroup,根据自己的逻辑重写其中的measure、layout、draw方法,来达到自己想要的效果。
- 但就我自己而言,有时候这样可能不够快。例如有这样一个场景,我需要做一个类似卡片的效果,它有自己需要的布局:背景是什么样的,距离左上角多少距离有尺寸多少的文字1,其他地方有文字2。这是一个很常见的场景。
- 于是我就想,要是可以直接写一个布局就好了。
BaseView
public abstract class BaseView<T extends ViewDataBinding> extends FrameLayout {
protected T mDataBinding;
protected Context mContext;
public BaseView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
mDataBinding = DataBindingUtil.inflate(LayoutInflater.from(context), getLayoutId(), this, false);
addView(mDataBinding.getRoot());
init(attrs);
}
public T getDataBinding() {
return mDataBinding;
}
protected abstract void init(AttributeSet attrs);
protected abstract int getLayoutId();
}
泛型的返回类型和前文的activity和fragment类似。
真·结语
- 上文只是我个人平时使用的模板,它不一定是最好的,也会有一些问题,但却是目前来说我自己使用的比较习惯的一套代码。发出来只是希望可以对读者带来一定的帮助。
- 毕竟,我也吃了很多前人种下来的瓜;要是可以让后人吃我种的瓜的时候,不吐槽这个太难吃,我就比较满意了。