Android MVVM框架搭建Navigation + Fragment + BottomNavigationView

Posted 初学者-Study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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