美文网首页Android开发Android开发经验谈Android技术知识
Jetpack 全组件实战开发短视频应用App <一>

Jetpack 全组件实战开发短视频应用App <一>

作者: 程序员叁柒 | 来源:发表于2020-08-17 20:24 被阅读0次

    Jetpack 概述

    我们这里先介绍下Jetpack,这是它的官网 Jetpack官网.

    Jetpack 是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。

    Jetpack 包含与平台 API 解除捆绑的 androidx.* 软件包库。这意味着,它可以提供向后兼容性,且比 Android 平台的更新频率更高,以此确保您始终可以获取最新且最好的 Jetpack 组件版本。

    Jetpack主要有四部分组成,基础架构行为页面

    在这里插入图片描述

    Foundation(基础)

    • AppCompat(向后兼容)
    • Android KTX(编写更加简洁的Kotlin代码)
    • Multidex (多处理dex的问题)
    • Auto(Auto组件)
    • Test(测试)
    • TV(TV)
    • Wear OS by Google(穿戴设备)

    Architecture Compinents(架构组件)

    • Data Bingding(数据绑定)
    • Room(数据库)
    • WorkManager(后台任务管家)
    • Lifecycle(生命周期)
    • Navigation(导航)
    • Paging(分页)
    • LiveData(底层数据通知更改视图)
    • ViewModel(以注重生命周期的方式管理界面的相关数据)

    Behavior(行为)

    • Download manager(下载给管理器)
    • Media & playback(媒体和播放)
    • Notifications(通知)
    • Permissions(权限)
    • Preferences(偏好设置)
    • Sharing(共享)
    • Slices(切片)

    UI(视觉交互)

    • Animation & transitions(动画和过渡)
    • Emoji(标签)
    • Fragment(Fragment)
    • Layout(布局)
    • Palette(调色板)

    使用JetPack的好处

    • 1.消除大量重复样板式的代码。
    • 2.简化复杂的任务。
    • 3.提供了强健的向后兼容的能力。
    • 4.加速Android的开发进程。

    我们接下来就是使用Jetpack全套组件来开发一款短视频应用,期间也会穿插着介绍下某个组件的使用和原理

    这一篇我们将使用Navigation搭建我们App的基础架构,我们先看下效果

    在这里插入图片描述

    Navigation介绍

    官网地址
    快速入门

    导航组件由以下三个关键部分组成:

    • 导航图:在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
    • NavHost:显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现 (NavHostFragment),可显示 Fragment 目标。
    • NavController:在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。

    Navigation使用

    这里我们直接使用AS自带的模板创建一个Activity

    在这里插入图片描述

    创建完毕之后,我们看下都导入了哪些依赖

     implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'com.google.android.material:material:1.1.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
        //这俩是最主要的
        implementation 'androidx.navigation:navigation-fragment:2.2.2'
        implementation 'androidx.navigation:navigation-ui:2.2.2'
    
        implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    
    

    接着我们看下整体项目目录


    在这里插入图片描述

    一个Activity和三个Fragment,接着我们从Activity看起

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="?attr/actionBarSize">
    
        <com.google.android.material.bottomnavigation.BottomNavigationView
            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>
    
    

    看下它的布局,很简单就是两部分,底部的BottomNavigationView和上面的fragment,我们先看下上面的这个fragment,可以看见它是一个叫NavHostFragmentFragment,我们在上面介绍里面说过,这个是一个容器,我们简单看下它的源码

    public class NavHostFragment extends Fragment implements NavHost {
        ......
        @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = getContext();
        //实例化NavController,它才是真正处理导航的,并且把FragmentNavigator实例化添加到SimpleNavigatorProvider 
        mNavController = new NavController(context);
       mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
    }
    
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
       @Nullable Bundle savedInstanceState) {
       FrameLayout frameLayout = new FrameLayout(inflater.getContext());
       frameLayout.setId(getId());
       return frameLayout;
        }
    
    //onViewCreated传入当前的根布局以及NavController实例,并且给根布局设置一个tag,所以在每次findNavController的时候都会通过这个tag取唯一的实例,如果取不到会循环从父布局在去找。
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            if (!(view instanceof ViewGroup)) {
               throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
            }
            View rootView = view.getParent() != null ? (View) view.getParent() : view;
            Navigation.setViewNavController(rootView, mNavController);
    }
        public static void setViewNavController(@NonNull View view,
                @Nullable NavController controller) {
            view.setTag(R.id.nav_controller_view_tag, controller);
        }
        ......
    
    }
    
    

    通过上面源码我们知道NavHostFragment里面实例化一个NavController,他是处理导航的,主要就是通过navigate方法实现跳转的

        /**
         * @param resId {@link NavDestination#getAction(int) action}的ActionId或者destinationId
         * @param args arguments参数
         * @param navOptions 导航操作的选项
         */
    public void navigate(int resId, Bundle args, NavOptions navOptions) {
        //回退栈为null返回NavGraph
        //不为null返回回退栈中的最后一项
        NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.peekLast();
        if (currentNode == null) {
            throw new IllegalStateException("no current navigation node");
        }
        int destId = resId;
        //获取resId对应的NavAction
        final NavAction navAction = currentNode.getAction(resId);
        if (navAction != null) {
            if (navOptions == null) {
                navOptions = navAction.getNavOptions();
            }
            //通过NavAction获取目的地id
            destId = navAction.getDestinationId();
        }
        //若destId为0而navOptions又不为null则弹出到该navOptions的指定的页面
        if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != 0) {
            popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
            return;
        }
        //为0报错
        if (destId == 0) {
            throw new IllegalArgumentException("Destination id == 0 can only be used" + " in conjunction with navOptions.popUpTo != 0");
        }
    
        //找到准备前往的目的地
        NavDestination node = findDestination(destId);
        if (node == null) {
            final String dest = NavDestination.getDisplayName(mContext, destId);
            throw new IllegalArgumentException("navigation destination " + dest
                    + (navAction != null
                    ? " referenced from action " + NavDestination.getDisplayName(mContext, resId)
                    : "")
                    + " is unknown to this NavController");
        }
        if (navOptions != null) {
            //是否清除回退栈
            if (navOptions.shouldClearTask()) {
                popBackStack(0, true);
                mBackStack.clear();
            } else if (navOptions.getPopUpTo() != 0) {
                //导航之前弹出栈到指定栈
                // 是否将该页面也弹出
                popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
            }
        }
        //进行导航
        node.navigate(args, navOptions);
    }
    
    

    这里我们还需要介绍两个类NavDestinationNavGraphNavGraphNavDestination的集合,NavDestination就相当于是节点,它的源码有一个这个方法

    在这里插入图片描述

    这个就是通过解析xml实例化自己的,那么这个xml是哪来的呢?其实就是我们activity_mainfragment节点里面有个navGraph

    在这里插入图片描述
    <?xml version="1.0" encoding="utf-8"?>
    <navigation 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:id="@+id/mobile_navigation"
        app:startDestination="@+id/navigation_home">
    
        <fragment
            android:id="@+id/navigation_home"
            android:name="com.hfs.navgationdemo.ui.home.HomeFragment"
            android:label="@string/title_home"
            tools:layout="@layout/fragment_home" />
    
        <fragment
            android:id="@+id/navigation_dashboard"
            android:name="com.hfs.navgationdemo.ui.dashboard.DashboardFragment"
            android:label="@string/title_dashboard"
            tools:layout="@layout/fragment_dashboard" />
    
        <fragment
            android:id="@+id/navigation_notifications"
            android:name="com.hfs.navgationdemo.ui.notifications.NotificationsFragment"
            android:label="@string/title_notifications"
            tools:layout="@layout/fragment_notifications" />
    </navigation>
    
    

    接下来说说我们activity_main中的另一个布局BottomNavigationView

    <com.google.android.material.bottomnavigation.BottomNavigationView
            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" />
    
    

    用它的话你需要添加依赖

    implementation 'com.google.android.material:material:1.1.0'
    
    

    我们需要给他配置一个menu,告诉它我们需要几个tab,比如我们这个例子中配置了3个

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item
            android:id="@+id/navigation_home"
            android:icon="@drawable/ic_home_black_24dp"
            android:title="@string/title_home" />
    
        <item
            android:id="@+id/navigation_dashboard"
            android:icon="@drawable/ic_dashboard_black_24dp"
            android:title="@string/title_dashboard" />
    
        <item
            android:id="@+id/navigation_notifications"
            android:icon="@drawable/ic_notifications_black_24dp"
            android:title="@string/title_notifications" />
    
    </menu>
    
    

    我们回到MainActivity中看看它们是怎么使用的

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            BottomNavigationView navView = findViewById(R.id.nav_view);
            // Passing each menu ID as a set of Ids because each
            // menu should be considered as top level destinations.
            AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                    R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                    .build();
            NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
            NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
            NavigationUI.setupWithNavController(navView, navController);
        }
    
    }
    
    

    主要是在NavigationUI.setupWithNavController(navView, navController);上,我们点进去看下

     public static void setupWithNavController(
                @NonNull final BottomNavigationView bottomNavigationView,
                @NonNull final NavController navController) {
            bottomNavigationView.setOnNavigationItemSelectedListener(
                    new BottomNavigationView.OnNavigationItemSelectedListener() {
                        @Override
                        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                            return onNavDestinationSelected(item, navController);
                        }
                    });
            final WeakReference<BottomNavigationView> weakReference =
                    new WeakReference<>(bottomNavigationView);
            navController.addOnDestinationChangedListener(
                    new NavController.OnDestinationChangedListener() {
                        @Override
                        public void onDestinationChanged(@NonNull NavController controller,
                                @NonNull NavDestination destination, @Nullable Bundle arguments) {
                            BottomNavigationView view = weakReference.get();
                            if (view == null) {
                                navController.removeOnDestinationChangedListener(this);
                                return;
                            }
                            Menu menu = view.getMenu();
                            for (int h = 0, size = menu.size(); h < size; h++) {
                                MenuItem item = menu.getItem(h);
                                if (matchDestination(destination, item.getItemId())) {
                                    item.setChecked(true);
                                }
                            }
                        }
                    });
        }
    
    

    这里分别监听了BottomNavigationViewNavControllerNavController我们在上面说了下,这里我们简单看下BottomNavigationView

        public static boolean onNavDestinationSelected(@NonNull MenuItem item,
                @NonNull NavController navController) {
            NavOptions.Builder builder = new NavOptions.Builder()
                    .setLaunchSingleTop(true)
                    .setEnterAnim(R.anim.nav_default_enter_anim)
                    .setExitAnim(R.anim.nav_default_exit_anim)
                    .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                    .setPopExitAnim(R.anim.nav_default_pop_exit_anim);
            if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
                builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
            }
            NavOptions options = builder.build();
            try {
                //TODO provide proper API instead of using Exceptions as Control-Flow.
                navController.navigate(item.getItemId(), null, options);
                return true;
            } catch (IllegalArgumentException e) {
                return false;
            }
        }
    
    

    最终还是会调用navController.navigate(item.getItemId(), null, options);
    OK,我们运行下看看效果

    在这里插入图片描述

    这里跟我们要实现的效果很像,但是还有些问题,一个是我们想动态添加底部,不在xml写死;我们在点击底部item的时候不要这个放大缩小动画;切换的时候Fragment不反复重建,源码使用的是replace方式,我们需要改进;这些问题我们将在下一篇解决

    相关文章

      网友评论

        本文标题:Jetpack 全组件实战开发短视频应用App <一>

        本文链接:https://www.haomeiwen.com/subject/fsuqjktx.html