Android MVVM框架搭建Navigation + Fragment + BottomNavigationView
Posted 初学者-Study
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android MVVM框架搭建Navigation + Fragment + BottomNavigationView相关的知识,希望对你有一定的参考价值。
android MVVM框架搭建(五)Navigation + Fragment + BottomNavigationView
前言
MVVM框架的模式在这几篇文章中相比你已经熟悉很多了,具体的架构模式如下图所示:
上层的Activity/Fragment表示为View层,通过ViewModel去操作数据,然后由Repository去控制数据的来源,可以是本地数据库也可以是网络数据。这个模式在文章和代码中都有体现,算是比较的完整了。
本文效果图如下:
正文
MVVM框架的搭建按理来说就已经完成了,但是我们既然要弄一个实用的框架,就不能只停留于框架搭建的阶段,还要有实用的场景,我喜欢我的框架可以满足绝大部分开发中的使用。现在我们的框架虽然有了Activity,但是还没有使用过Fragment,通常Fragment是在什么时候使用呢?例如主页面五个子模块Fragment,分别表示五个功能,这样是不是会很好呢,这样就完美的将Fragment融入了进去,同时我们还可以与实际的开发模式相结合起来。嗯,不错,开始行动吧。
一、添加依赖
使用Navigation需要添加依赖,在app的build.gradle中的dependencies闭包中添加如下依赖:
// navigation依赖 ui 和 fragment
implementation 'androidx.navigation:navigation-fragment:2.3.2'
implementation 'androidx.navigation:navigation-ui:2.3.2'
然后Sync Now同步依赖项目。
二、Fragment创建
创建Fragment可以通过快捷的方式,自带了ViewModel的,如下图所示:
这里创建两个Fragment,NewsFragment和VideoFragment,对应的布局文件是news_fragment.xml和video_fragment.xml,ViewModel是NewsViewModel和VideoViewModel。
下面对项目的包分一下,我把Activity、Fragment、Adapter都看为ui,那么我在com.llw.mvvm包下新建一个ui包,包下新建一个fragment包,然后将NewsFragment和VideoFragment放入fragment包,然后把adapter包也移到ui包下,同时在ui包下新建一个activity包,包下将项目中所有的Activity移入,最后将NewsViewModel和VideoViewModel放到viewmodels包下。目录结构如下图所示:
下面依次修改一下news_fragment.xml和video_fragment.xml中的内容:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</layout>
两个xml里面的内容都是上面的这个代码,复制粘贴即可,这两个Fragment中的内容我们待会儿再写。
三、BaseActivity创建
因为我们的Activity比较多,而可能有些Activity中的方法有重合的,或者通用的,这种情况下我们可以将一些方法放入一个基础类里面,例如BaseActivity中,下面进行创建,在activity包下新建一个BaseActivity类,代码如下:
/**
* 基础Activity
*
* @author llw
*/
public class BaseActivity extends AppCompatActivity
protected AppCompatActivity context;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
this.context = this;
protected void showMsg(CharSequence msg)
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
protected void showLongMsg(CharSequence msg)
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
/**
* 跳转页面
* @param clazz 目标页面
*/
protected void jumpActivity(final Class<?> clazz)
startActivity(new Intent(context, clazz));
/**
* 跳转页面并关闭当前页面
* @param clazz 目标页面
*/
protected void jumpActivityFinish(final Class<?> clazz)
startActivity(new Intent(context, clazz));
finish();
/**
* 状态栏文字图标颜色
* @param dark 深色 false 为浅色
*/
protected void setStatusBar(boolean dark)
View decor = getWindow().getDecorView();
if (dark)
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
else
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
里面也是一些简单的方法,后面在开发中有新的需要可以一直加进去,根据实际情况来,不要什么都加进去,其实没必要的。
四、启动页
我们的这个MVVM-Demo虽然只是一个Demo,但是我们要给自己一个高一点的标准,所以我打算给一个启动页,一个简单的动画,然后进入我们的登录页,虽然我们是一个假登录,但是意思已经到位了。然后我们在登录页面上记录程序是否登录过,如果登录过下次进入程序就不再进入登录页面,而是直接进入主页面了,这样的逻辑很简单,下面来实现一下吧。
在activity包下新建一个SplashActivity,对应的布局是activity_splash.xml,xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".ui.activity.SplashActivity">
<RelativeLayout
android:layout_width="160dp"
android:layout_height="160dp">
<ImageView
android:layout_width="160dp"
android:layout_height="160dp"
android:src="@mipmap/ic_splash_logo" />
<TextView
android:visibility="invisible"
android:id="@+id/tv_mvvm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="46dp"
android:text="MVVM"
android:textColor="@color/white"
android:textSize="28sp"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Model View ViewModel"
android:textColor="@color/black"
android:textSize="24sp" />
<TextView
android:id="@+id/tv_translate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:text="Model View ViewModel"
android:textColor="@color/white"
android:textSize="24sp" />
</RelativeLayout>
</LinearLayout>
</layout>
这里面有一个图标ic_splash_logo.png,我这里贴一下,不过你最好到我的源码去找,这样不会有水印,而且图片格式也是对的。
针对于启动页我特别弄了一个主题样式,在themes.xml下增加如下代码样式:
<style name="SplashTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:statusBarColor" tools:targetApi="lollipop">#00FFFFFF</item><!--设置状态栏的颜色-->
</style>
然后我们修改AndroidManifest.xml中的代码,因为之前的启动Activity是LoginActivity,需要改一下。如下图所示:
下面我们增加一个动画的帮助工具类,在utils包下新建一个EasyAnimation类,里面的代码如下:
public class EasyAnimation
/**
* 开始眨眼动画
*
* @param view 需要设置动画的View
*/
public static void startBlink(View view)
AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
alphaAnimation.setDuration(500);
alphaAnimation.setStartOffset(20);
alphaAnimation.setRepeatMode(Animation.REVERSE);
alphaAnimation.setRepeatCount(Animation.INFINITE);
view.startAnimation(alphaAnimation);
/**
* 开始眨眼动画
*
* @param view 需要设置动画的View
* @param alphaAnimation 透明度动画(自行配置)
*/
public static void startBlink(View view, AlphaAnimation alphaAnimation)
view.startAnimation(alphaAnimation);
/**
* 停止眨眼动画
*
* @param view 需要清除动画的View
*/
public static void stopBlink(View view)
if (view != null)
view.clearAnimation();
/**
* 移动指定View的宽度
*
* @param view
*/
public static void moveViewWidth(View view, TranslateCallback callback)
view.post(() ->
//通过post拿到的tvTranslate.getWidth()不会为0。
TranslateAnimation translateAnimation = new TranslateAnimation(0, view.getWidth(), 0, 0);
translateAnimation.setDuration(1000);
translateAnimation.setFillAfter(true);
view.startAnimation(translateAnimation);
//动画监听
translateAnimation.setAnimationListener(new Animation.AnimationListener()
@Override
public void onAnimationStart(Animation animation)
@Override
public void onAnimationEnd(Animation animation)
//检查Android版本
callback.animationEnd();
@Override
public void onAnimationRepeat(Animation animation)
);
);
/**
* 移动指定View的宽度
*
* @param view 需要位移的View
* @param callback 位移动画回调
* @param translateAnimation 位移动画 (自行配置)
*/
public static void moveViewWidth(View view, TranslateCallback callback, TranslateAnimation translateAnimation)
view.post(() ->
//通过post拿到的tvTranslate.getWidth()不会为0。
view.startAnimation(translateAnimation);
//动画监听
translateAnimation.setAnimationListener(new Animation.AnimationListener()
@Override
public void onAnimationStart(Animation animation)
@Override
public void onAnimationEnd(Animation animation)
//检查Android版本
callback.animationEnd();
@Override
public void onAnimationRepeat(Animation animation)
);
);
public interface TranslateCallback
//动画结束
void animationEnd();
因为在启动页需要知道程序有没有登录,因此在Constant中增加一个常量,如下所示:
/**
* 是否登录过
*/
public static final String IS_LOGIN = "isLogin";
下面我们修改一下SplashActivity的代码,使用这个常量来判断需要跳转到那个页面,代码如下:
public class SplashActivity extends BaseActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
ActivitySplashBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_splash);
setStatusBar(true);
EasyAnimation.moveViewWidth(binding.tvTranslate, () ->
binding.tvMvvm.setVisibility(View.VISIBLE);
jumpActivity(MVUtils.getBoolean(Constant.IS_LOGIN) ? MainActivity.class : LoginActivity.class);
);
这里我继承了BaseActivity,然后设置了状态栏深色模式,因为我们的页面是白色的,如果状态栏也是白色就看不出来了,后面就是在动画结束的时候跳转页面,很简单的代码。这个页面的代码就写完了,下面我们修改LoginActivity中的代码,首先是修改继承的Activity为BaseActivity。里面的代码如下:
public class LoginActivity extends BaseActivity
private ActivityLoginBinding dataBinding;
private LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
//数据绑定视图
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);
loginViewModel = new LoginViewModel();
//Model → View
User user = new User("admin", "123456");
loginViewModel.getUser().setValue(user);
//获取观察对象
MutableLiveData<User> user1 = loginViewModel.getUser();
user1.observe(this, user2 -> dataBinding.setViewModel(loginViewModel));
dataBinding.btnLogin.setOnClickListener(v ->
if (loginViewModel.user.getValue().getAccount().isEmpty())
showMsg("请输入账号");
return;
if (loginViewModel.user.getValue().getPwd().isEmpty())
showMsg("请输入密码");
return;
//记录已经登录过
MVUtils.put(Constant.IS_LOGIN,true);
showMsg("登录成功");
jumpActivity(MainActivity.class);
);
这里就没啥好说的,就是使用了BaseActivity中的方法。同时我修改了一下布局中的代码,我将这两个TextView隐藏了
同时我们修改一下图片显示之前的占位图或者说是默认背景图。两个图片如下:
然后一个加载图片出错时显示的图片:
首先是MainActivity中,显示必应图片的位置,修改一下activity_main.xml
然后打开CustomImageView,增加如下代码:
private static final RequestOptions OPTIONS = new RequestOptions()
.placeholder(R.drawable.wallpaper_bg)//图片加载出来前,显示的图片
.fallback(R.drawable.wallpaper_bg) //url为空的时候,显示的图片
.error(R.mipmap.ic_loading_failed);//图片加载失败后,显示的图片
将这个值配置进去,如下图所示:
下面我们运行一下看是什么效果。
效果还可以的,下面进入主页面的代码编写。
五、主页面
当到了每日壁纸页面时,我们需要再提供一个入口可以进入下一个页面,现在的每日壁纸页面不能算是真正意义上的主页面,因此我们写一个入口,可以在MainActivity中增加一个浮动按钮,页面上下滑动时控制按钮的显示和消失。下面在activity_main.xml中增加如下布局代码:
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_home"
android:layout_width="wrap_content以上是关于Android MVVM框架搭建Navigation + Fragment + BottomNavigationView的主要内容,如果未能解决你的问题,请参考以下文章
Android MVVM框架搭建ViewModel + LiveData + DataBinding
Android MVVM框架搭建ViewModel + LiveData + DataBinding
Android MVVM框架搭建高德地图定位天气查询BottomSheetDialog
Android MVVM框架搭建HiltViewBindingActivity Result API
Android MVVM框架搭建HiltViewBindingActivity Result API
Android MVVM框架搭建Navigation + Fragment + BottomNavigationView