Android Jetpack组件之Navigation使用-源码
1、前言
最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:
- Android Jetpack组件总览
- Android Jetpack 组件之 Lifecycle使用
- Android Jetpack 组件之 Lifecycle源码
- Android Jetpack组件之ViewModel使用
- Android Jetpack组件之 LiveData使用-源码
- Android Jetpack组件之 Paging使用-源码
- Android Jetpack组件之 Room使用-源码
- Android Jetpack组件之Navigation使用-源码
- Android Jetpack组件之WorkManger使用介绍
- Android Jetpack组件App Startup简析
- Android Jetpack组件之Hilt使用
本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。
2、Navigation简介
导航架构组件简化了Android应用程序中导航的实现,通过在xml中添加元素并指定导航的起始和目的地,从而在Fragment之间建立连接在Activity中调用xml中设置的导航action从而跳转界面到目的地,简单来说它和之前在活动中调用startActivity的区别就类似于代码布局和xml中layout布局一样,既简单又可视化,如下图就是一个navigaton的xml图:
Navigation多数作用于Fragment中,不过导航组件还支持:Fragment、Activity、导航图和子图、自定义目标。本文内容实现如下功能:
3、Navigation实战操作
在实战之前,我们先来了解一下Navigation
中最关键的三要素,他们是:
名词 | 解释 |
---|---|
Navigation Graph (New XML resource)
|
如我们的第一张图所示,这是一个新的资源文件,用户在可视化界面可以看出他能够到达的Destination (用户能够到达的屏幕界面),以及流程关系。
|
NavHostFragment (Layout XML view)
|
当前Fragment 的容器
|
NavController (Kotlin/Java object)
|
导航的控制者 |
可能我这么解释还是有点抽象,做一个不是那么恰当的比喻,我们可以将Navigation Graph
看作一个地图,NavHostFragment
看作一个车,以及把NavController
看作车中的方向盘,Navigation Graph
中可以看出各个地点(Destination)和通往各个地点的路径,NavHostFragment
可以到达地图中的各个目的地,但是决定到什么目的地还是方向盘NavController
,虽然它取决于开车人(用户)。
第一步 添加依赖
模块层的build.gradle
文件需要添加:
ext.navigationVersion = "2.0.0"
dependencies {//... implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"
}
如果你要使用SafeArgs
插件,还要在项目目录下的build.gradle
文件添加:
buildscript {ext.navigationVersion = "2.0.0"dependencies {classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"}
}
以及模块下面的build.gradle
文件添加:
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs'
第二步 创建navigation导航
- 创建基础目录:资源文件
res
目录下创建navigation
目录 -> 右击navigation
目录New一个Navigation resource file
- 创建一个
Destination
,如果说navigation
是我们的导航工具,Destination
是我们的目的地,在此之前,我已经写好了一个WelcomeFragment
、LoginFragment
和RegisterFragment
,添加Destination
的操作完成后如下所示:
除了可视化界面之外,我们仍然有必要看一下里面的内容组成,login_navigation.xml
:
<navigation...android:id="@+id/login_navigation"app:startDestination="@id/welcome"><fragmentandroid:id="@+id/login"android:name="com.joe.jetpackdemo.ui.fragment.login.LoginFragment"android:label="LoginFragment"tools:layout="@layout/fragment_login"/><fragmentandroid:id="@+id/welcome"android:name="com.joe.jetpackdemo.ui.fragment.login.WelcomeFragment"android:label="LoginFragment"tools:layout="@layout/fragment_welcome"><action.../><action.../></fragment><fragmentandroid:id="@+id/register"android:name="com.joe.jetpackdemo.ui.fragment.login.RegisterFragment"android:label="LoginFragment"tools:layout="@layout/fragment_register"><argument.../></fragment>
</navigation>
我在这里省略了一些不必要的代码。让我们看一下navigation标签
的属性:
属性 | 解释 |
---|---|
app:startDestination
|
默认的起始位置 |
第三步 建立NavHostFragment
我们创建一个新的LoginActivity
,在activity_login.xml
文件中:
<androidx.constraintlayout.widget.ConstraintLayout...><fragmentandroid:id="@+id/my_nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"app:navGraph="@navigation/login_navigation"app:defaultNavHost="true"android:layout_width="match_parent"android:layout_height="match_parent"/></androidx.constraintlayout.widget.ConstraintLayout>
有几个属性需要解释一下:
属性 | 解释 |
---|---|
android:name
|
值必须是androidx.navigation.fragment.NavHostFragment ,声明这是一个NavHostFragment
|
app:navGraph
|
存放的是第二步建好导航的资源文件,也就是确定了Navigation Graph
|
app:defaultNavHost="true"
|
与系统的返回按钮相关联 |
第四步 界面跳转、参数传递和动画
在WelcomeFragment
中,点击登录和注册按钮可以分别跳转到LoginFragment
和RegisterFragment
中。
这里我使用了两种方式实现:
- 方式一 利用ID导航
目标:WelcomeFragment
携带key
为name
的数据跳转到LoginFragment
,LoginFragment
接收后显示。
Have a account ? Login
按钮的点击事件如下:
btnLogin.setOnClickListener {// 设置动画参数val navOption = navOptions {anim {enter = R.anim.slide_in_rightexit = R.anim.slide_out_leftpopEnter = R.anim.slide_in_leftpopExit = R.anim.slide_out_right}}// 参数设置val bundle = Bundle()bundle.putString("name","TeaOf")findNavController().navigate(R.id.login, bundle,navOption)
}
后续LoginFragment
的接收代码比较简单,直接获取Fragment中的Bundle
即可,这里不再出示代码。最后的效果:
- 方式二 利用
Safe Args
目标:WelcomeFragment
通过Safe Args
将数据传到RegisterFragment
,RegisterFragment
接收后显示。
再看一下已经展示过的login_navigation.xml
:
<navigation...><fragment.../><fragmentandroid:id="@+id/welcome"><actionandroid:id="@+id/action_welcome_to_login"app:destination="@id/login"/><actionandroid:id="@+id/action_welcome_to_register"app:enterAnim="@anim/slide_in_right"app:exitAnim="@anim/slide_out_left"app:popEnterAnim="@anim/slide_in_left"app:popExitAnim="@anim/slide_out_right"app:destination="@id/register"/></fragment><fragmentandroid:id="@+id/register"...><argumentandroid:name="EMAIL"android:defaultValue="2005@qq.com"app:argType="string"/></fragment>
</navigation>
细心的同学可能已经观察到navigation
目录下的login_navigation.xml
资源文件中的action
标签和argument
标签,这里需要解释一下:
心的同学可能已经观察到navigation
目录下的login_navigation.xml
资源文件中的action
标签和argument
标签,这里需要解释一下:
action标签
属性 | 作用 |
---|---|
app:destination
|
跳转完成到达的fragment 的Id
|
app:popUpTo
|
将fragment 从栈 中弹出,直到某个Id的fragment
|
argument标签
属性 | 作用 |
---|---|
android:name
|
标签名字 |
app:argType
|
标签的类型 |
android:defaultValue
|
默认值 |
点击Android studio中的Make Project按钮,可以发现系统为我们生成了两个类:
WelcomeFragment
中的JOIN US
按钮点击事件:
btnRegister.setOnClickListener {val action = WelcomeFragmentDirections.actionWelcomeToRegister().setEMAIL("TeaOf1995@Gamil.com")findNavController().navigate(action)
}
RegisterFragment
中的接收:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// ...val safeArgs:RegisterFragmentArgs by navArgs()val email = safeArgs.emailmEmailEt.setText(email)
}
以及效果:
需要提及的是,如果不用Safe Args
,action
可以由Navigation.createNavigateOnClickListener(R.id.next_action, null)
方式生成,感兴趣的同学可以自行编写。
4、更多
Navigation
可以绑定menus
、drawers
和bottom navigation
,这里我们以bottom navigation
为例,我先在navigation
目录下新创建了main_navigation.xml
,接着新建了MainActivity
,下面则是activity_main.xml
:
<LinearLayout...><fragmentandroid:id="@+id/my_nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"app:navGraph="@navigation/main_navigation"app:defaultNavHost="true"android:layout_height="0dp"android:layout_weight="1"/><com.google.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/navigation_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/white"app:itemIconTint="@color/colorAccent"app:itemTextColor="@color/colorPrimary"app:menu="@menu/menu_main"/></LinearLayout>
MainActivity
中的处理也十分简单:
class MainActivity : AppCompatActivity() {lateinit var bottomNavigationView: BottomNavigationViewoverride fun onCreate(savedInstanceState: Bundle?) {//...val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragmentval navController = host.navControllerinitWidget()initBottomNavigationView(bottomNavigationView,navController)}private fun initBottomNavigationView(bottomNavigationView: BottomNavigationView, navController: NavController) {bottomNavigationView.setupWithNavController(navController)}private fun initWidget() {bottomNavigationView = findViewById(R.id.navigation_view)}
}
效果:
5、源码
5.1 NavHostFragment
官网上是这样介绍它的:NavHostFragment provides an area within your layout for self-contained navigation to occur. 大致意思就是NavHostFragment在布局中提供了一个区域,用于进行包含导航
接下来我们看一下它的源码:
public class NavHostFragment extends Fragment implements NavHost {@CallSuper@Overridepublic void onAttach(@NonNull Context context) {super.onAttach(context);if (mDefaultNavHost) {requireFragmentManager().beginTransaction().setPrimaryNavigationFragment(this).commit();}}
}
可以看到它就是一个Fragment
,在onAttach
生命周期开启事务将它自己设置成了PrimaryFragment了,当然通过defaultNavHost
条件判断的,这个布尔值看着眼熟吗?没错,就是我们在xml布局中设置的那一个。app:defaultNavHost="true"
接着看它的onCreate
生命周期
@CallSuper@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);final Context context = requireContext();mNavController = new NavController(context);mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());.......if (navState != null) {// Navigation controller state overrides argumentsmNavController.restoreState(navState);}if (mGraphId != 0) {// Set from onInflate()mNavController.setGraph(mGraphId);} else {// See if it was set by NavHostFragment.create()final Bundle args = getArguments();final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;final Bundle startDestinationArgs = args != null? args.getBundle(KEY_START_DESTINATION_ARGS): null;if (graphId != 0) {mNavController.setGraph(graphId, startDestinationArgs);}}}
我们看到在onCreate生命周期中创建了一个NavController,并且为这个NavController创建了一个_Navigator__添加了进去,_我们跟踪createFragmentNavigator,发现它创建了一个FragmentNavigator,这个类是做什么的呢?它继承了Navigator,查看注释我们知道它是为每个Navigation设置策略的,也就是说Fragment之间通过导航切换都是由它来操作的,下面会详细介绍的,这里先简单看下。
接下来我们看到为NavController设置了setGraph(),也就是我们xml里面定义的navGraph,导航布局里面的Fragment及action跳转等信息。
还有就是onCreateView、onViewCreated等生命周期方法,基本就是加载布局设置ID的方法了。
下面我们跟到NavController.setGraph()中看下是怎样将我们设计的fragment添加进去的?
5.2 NavController
/*** Sets the {@link NavGraph navigation graph} to the specified graph.* Any current navigation graph data (including back stack) will be replaced.** <p>The graph can be retrieved later via {@link #getGraph()}.</p>** @param graph graph to set* @see #setGraph(int, Bundle)* @see #getGraph*/@CallSuperpublic void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {if (mGraph != null) {// Pop everything from the old graph off the back stackpopBackStackInternal(mGraph.getId(), true);}mGraph = graph;onGraphCreated(startDestinationArgs);}
我们看如果设置的graph不为null,它执行了popBackStackInternal,看注释的意思为从之前的就的graph栈弹出所有的graph:
boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {..........boolean popped = false;for (Navigator navigator : popOperations) {if (navigator.popBackStack()) {mBackStack.removeLast();popped = true;} else {// The pop did not complete successfully, so stop immediatelybreak;}}return popped;}
果真remove掉了之前所有的naviagtor。而这个mBackStack是什么时候添加的navigator的呢?查看源码我们发现:
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {boolean popped = false;if (navOptions != null) {if (navOptions.getPopUpTo() != -1) {popped = popBackStackInternal(navOptions.getPopUpTo(),navOptions.isPopUpToInclusive());}}Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(node.getNavigatorName());Bundle finalArgs = node.addInDefaultArgs(args);NavDestination newDest = navigator.navigate(node, finalArgs,navOptions, navigatorExtras);if (newDest != null) {// 如果NavGraph不在栈内,先拿到父类NavgarphArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();NavGraph parent = newDest.getParent();while (parent != null) {hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs));parent = parent.getParent();}// 现在遍历后堆栈并查看哪些导航图已经在栈内Iterator<NavBackStackEntry> iterator = mBackStack.iterator();while (iterator.hasNext() && !hierarchy.isEmpty()) {NavDestination destination = iterator.next().getDestination();if (destination.equals(hierarchy.getFirst().getDestination())) {//destination 如果已经在栈顶,不需要再add了hierarchy.removeFirst();}}// Add all of the remaining parent NavGraphs that aren't// already on the back stackmBackStack.addAll(hierarchy);//添加新的 destinationNavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, finalArgs);mBackStack.add(newBackStackEntry);}if (popped || newDest != null) {dispatchOnDestinationChanged();}}
还记得这个方法吗?我们一般手动切换Fragment时可以调用这个方法,最后就是跟踪到这里。
findNavController().navigate(R.id.bottomNavSampleActivity)
同时,切换目标Fragment到栈顶。我们发现最后dispatchOnDestinationChanged()
这个方法,分发目标界面切换。有必要去跟一下,你可能会发现意想不到的东西:
/*** Dispatch changes to all OnDestinationChangedListeners.* <p>* If the back stack is empty, no events get dispatched.** @return If changes were dispatched.*/@SuppressWarnings("WeakerAccess") /* synthetic access */boolean dispatchOnDestinationChanged() {// We never want to leave NavGraphs on the top of the stack//noinspection StatementWithEmptyBodywhile (!mBackStack.isEmpty()&& mBackStack.peekLast().getDestination() instanceof NavGraph&& popBackStackInternal(mBackStack.peekLast().getDestination().getId(), true)) {// Keep popping}if (!mBackStack.isEmpty()) {NavBackStackEntry backStackEntry = mBackStack.peekLast();for (OnDestinationChangedListener listener :mOnDestinationChangedListeners) {listener.onDestinationChanged(this, backStackEntry.getDestination(),backStackEntry.getArguments());}return true;}return false;}
这里面分发了所有实现了OnDestinationChangedListener
接口的方法,继续跟踪,看看都哪些实现了这个接口呢?
只有一个类实现了AbstractAppBarOnDestinationChangedListener,看一下具体实现:
@Overridepublic void onDestinationChanged(@NonNull NavController controller,@NonNull NavDestination destination, @Nullable Bundle arguments) {DrawerLayout drawerLayout = mDrawerLayoutWeakReference != null? mDrawerLayoutWeakReference.get(): null;if (mDrawerLayoutWeakReference != null && drawerLayout == null) {controller.removeOnDestinationChangedListener(this);return;}CharSequence label = destination.getLabel();if (!TextUtils.isEmpty(label)) {............matcher.appendTail(title);//设置titlesetTitle(title);}boolean isTopLevelDestination = NavigationUI.matchDestinations(destination,mTopLevelDestinations);if (drawerLayout == null && isTopLevelDestination) {//设置iconsetNavigationIcon(null, 0);} else {//设置返回箭头状态setActionBarUpIndicator(drawerLayout != null && isTopLevelDestination);}}
原来如此,到这里就应该清楚了,当我们切换Fragment时,大概流程如下:
- 切换目标fragment到栈顶
- 分发目标Fragment切换状态
- 设置toolbar的标题、icon状态等
- 当然setTitle()、setNavigationIcon()等都为抽象方法,具体实现可以看子类里是怎么实现的,具体就不叙述了
到这里,基本的几个核心类以及相关实现我们基本了解了,下面我们看一下基本的流程,首先我们从入口进去,一点点跟进
5.3 Navigation.findNavController(this, R.id.fragment_home)
我们在最开始会初始化一个NavController:
@NonNullpublic static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {View view = ActivityCompat.requireViewById(activity, viewId);NavController navController = findViewNavController(view);.......return navController;}@Nullableprivate static NavController findViewNavController(@NonNull View view) {while (view != null) {NavController controller = getViewNavController(view);.........}return null;}@SuppressWarnings("unchecked")@Nullableprivate static NavController getViewNavController(@NonNull View view) {Object tag = view.getTag(R.id.nav_controller_view_tag);NavController controller = null;if (tag instanceof WeakReference) {controller = ((WeakReference<NavController>) tag).get();} else if (tag instanceof NavController) {controller = (NavController) tag;}return controller;}
查看代码可以看到是通过一个tag值来找到的,那么什么时候设置的呢?还记得5.1里面介绍的NavHostFragment
的生命周期onViewCreated
么?
@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);.......View rootView = view.getParent() != null ? (View) view.getParent() : view;Navigation.setViewNavController(rootView, mNavController);}
在视图创建的时候调用了Naviagtion.setViewNavController()。NavController初始化好了之后,接下来将它和NavigationView、ToolBar、BottomNavigationView、DrawerLayout进行绑定:
5.4 setupActionBarWithNavController
不管是NavigationView还是Bottom``NavigationView,都会调用这个方法,他是AppCompatActivity的一个扩展方法,调用的是NavigationUI这个类:
public static void setupActionBarWithNavController(@NonNull AppCompatActivity activity,@NonNull NavController navController,@NonNull AppBarConfiguration configuration) {navController.addOnDestinationChangedListener(new ActionBarOnDestinationChangedListener(activity, configuration));}
可以看到它就是调用了目标切换的那个接口,用来实现标题按钮等状态的改变。查看它的方法实现:
我们看到它重载了很多方法,包括我们上面提到的NavigationView、ToolBar、BottomNavigationView、DrawerLayout。这样就将组件的状态切换绑定起来了,当fragment切换时,上面提到的接口分发,去切换布局按钮等状态。
5.5 navView.setupWithNavController(navController)
public static void setupWithNavController(@NonNull final NavigationView navigationView,@NonNull final NavController navController) {navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(@NonNull MenuItem item) {//目标页面是否被选中boolean handled = onNavDestinationSelected(item, navController);if (handled) {//切换菜单状态、关闭抽屉ViewParent parent = navigationView.getParent();if (parent instanceof DrawerLayout) {((DrawerLayout) parent).closeDrawer(navigationView);} else {BottomSheetBehavior bottomSheetBehavior =findBottomSheetBehavior(navigationView);if (bottomSheetBehavior != null) {bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);}}}return handled;}});final WeakReference<NavigationView> weakReference = new WeakReference<>(navigationView);navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {@Overridepublic void onDestinationChanged(@NonNull NavController controller,@NonNull NavDestination destination, @Nullable Bundle arguments) {NavigationView 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);item.setChecked(matchDestination(destination, item.getItemId()));}}});}
最后就是状态切换了,当点击menu菜单或者目标Fragment切换的时候,改变状态。
6、 流程
考虑到我们开始如果直接从setupWithNavController 入口进行分析的话,可能不太容易找到怎么创建的graph布局中的fragment,以及NavHostFragment到底是什么,所以我们先分析了布局中的**NavHostFragment,我们发现为什么要在布局中声明了一个NavHostFragment,**它是用来做什么的,最后发现在它的生命周期中创建了一个NavController,并且添加了FragmentNavigator,同时setGraph了。
紧接着我们通过setGraph进入到了NavController类中,通过graph里面设置的初始fragment看到了切换栈内切换Fragment的代码。
在里面我们看到了熟悉的navigate()方法,在里面dispatchOnDestinationChanged()吸引了我的注意力,通过查找,发现切换Fragment之后,通过该方法去改变布局的状态,也就是OnDestinationChangedListener接口。
到这里基本的代码实现已经了解的差不多了,然后我回到了入口,通过初始化NavController,调用NavigationUI中的方法绑定NavigationView、ToolBar、BottomNavigationView、DrawerLayout等布局,在调用navigate()方法后,改变状态,整个流程就走通了。
可能有一些不合理的地方,望大家见谅,但是这是我此次的一个基本流程。
重要参考
即学即用Android Jetpack - Navigation
Jetpack源码解析---看完你就知道Navigation是什么了?
Android Jetpack组件之Navigation使用-源码相关推荐
- Android Jetpack组件之 Room使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之 Paging使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之 LiveData使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack 组件之 Lifecycle源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之Hilt使用
前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. And ...
- Android Jetpack组件App Startup简析
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之WorkManger使用介绍
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack组件之ViewModel使用
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android Jetpack 组件之 Lifecycle使用
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
最新文章
- mpvue 转uniapp 导航栏样式错乱问题修复 tabbar 样式修复
- python北京时间代码_python代码定时同步本机的北京时间详解
- PHP函数之CURL: 常见问题总结
- linux 本地账号密码无法登陆(shell可以登录),一直返回 登陆的login界面
- Azkaban任务调度(使用带有依赖的任务调度)【mapreduce数据清洗,数据入hive库,kylin预编译、数据分析】
- Java 之文件目录操作
- 【leetcode】Median of Two Sorted Arrays
- 【水果识别】基于matlab GUI形态学水果大小识别【含Matlab源码 920期】
- 直接学 Vue 3 吧 —— 对话 Vue.js 作者尤雨溪
- 关于虚拟机访问网页证书错误的问题?
- selenium自动化图片不加载设置
- QML使用ShaderEffect绘制波纹
- oracle 抽样_oracle优化手段--统计信息导出和抽样提取数据
- Excel文件提示文件损坏
- 往事如烟 - 老钟1
- 基于QT平台的手持媒体播放器项目实战视频教程下载
- 破解DELL BIOS开机密码,拔电池放电不好使
- Spring Boot与安全(安全、Spring Security)
- elementui DateTimePicker组件 限制时间范围(包含时分秒)
- dcdc模块降额设计_IGBT门极驱动设计规范
热门文章
- oracle中聚合比较函数,Oracle聚合函数/分析函数
- js关闭手机浏览器_Unity 之 WebGL打开手机摄像头
- nfc pm3 模拟加密门禁卡_华为手机NFC的妙用,模拟门禁卡、公交卡、银行卡,电子证件等...
- 工信部:推动窄带物联网、5G泛在信息基础设施
- 《Groovy语言规范》-语法
- 删除数据 DataIntegrityViolationException: not-null property references a null or transient value解决...
- 搜索引擎优化 SEO
- 现代 PHP 新特性系列
- Linux source命令(转)
- 【ORACLE】碎片整理