Jetpack入门教程(一)

330 阅读6分钟

本文 已参与「新人创作礼」活动,一起开启掘金创作之路。

很久没玩 Android 了,据说Android发生了很大的变化,今天提到的这个 Jetpack 就是一个很大的变化。当然编程语言也增加了kotlin。好了,话不多说,开干。

简单介绍

本篇简单搭建一个工程的框架

创建项目

这里我们选择带有Navigation的模板,学习一下模板里面的写法 image.png

项目基本介绍

1、Activity

可以看到,现在默认都是 kotlin 语言了,本人对kotlin也不是很熟悉,稍后会在末尾对 kotlin 语法糖进行补充

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 使用 ActivityMainBinding 绑定View
        binding = ActivityMainBinding.inflate(layoutInflater)
        // 设置Content View
        setContentView(binding.root)

        val navView: BottomNavigationView = binding.navView

        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
            )
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }
}

viewBinding

这是一个很神奇的工具,可以避免我们重复的写很多的 findViewById,为了实现这个功能,我们需要实现以下几个地方

  1. app->build.gradle
buildFeatures {
    viewBinding true
}
  1. 创建binding
private lateinit var binding: ActivityMainBinding

这里的 ActivityMainBinding 是有命名规则的,我们看下这个Activity对应的布局

image.png 其实就是布局名去掉下划线,将首字符大写再加上Binding

image.png 我们这里新建了一个Activity,可以看到是遵循这样的规则 3.绑定View 这里的layoutInflater 其实是布局管理器在kotlin可以直接这样写,在java中需要 getLayoutInflater()

binding = ActivityMain2Binding.inflate(layoutInflater)

//@NonNull
//public LayoutInflater getLayoutInflater() {
//    throw new RuntimeException("Stub!");
//}

2、BottomNavigationView

这是一个很棒的控件,也就是我们经常提到的导航选项卡

  1. 首先来看下效果 image.png
  2. 获取BottomNavigationView 这个是底部的选项按钮
//使用了binding的方式就可以很方便的获取布局里面的View了只用通过binding.既可以

val navView: BottomNavigationView = binding.navView

3、获取 NavController 这个就是对应的fragment

val navController = findNavController(R.id.nav_host_fragment_activity_main)

4、创建 AppBarConfiguration

val appBarConfiguration = AppBarConfiguration(
    setOf(
        R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
    )
)

这个也就是三个tab对应fragment的ID,AppBarConfiguration是一个容器,为什么需要这个,我们在下面介绍 5、设置关联

setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)

在上面的代码上中,我们没有看到具体的点击事件,实际上到此,工程也未出现点击事件,但是写到这里的时候,下面的tab实际上是可以点击的,那么这里的点击事件在哪里写的呢,其实就是上面两句封装,我们逐一来查看下:

setupActionBarWithNavController: 点进去看源码:

fun AppCompatActivity.setupActionBarWithNavController(
    navController: NavController,
    configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
) {
    NavigationUI.setupActionBarWithNavController(this, navController, configuration)
}

我们看下源码里的一句注释

Sets up the ActionBar returned by AppCompatActivity.getSupportActionBar for use with a NavController. By calling this method, the title in the action bar will automatically be updated when the destination changes (assuming there is a valid label). 什么意思呢,意思就是设置当前的 屏幕底部的tab会随着 NavController的改变而改变,而这个NavController就是fragment,这种在什么情况下使用呢,也就是当你的fragment可以左右滑动,通过上面的View来带动Tab的时候,就需要使用到这个,而当你只需要通过下面的Tab来控制中间的View的时候,这个函数就没有用了,可以注释

setupWithNavController: 点进去看源码:

fun BottomNavigationView.setupWithNavController(navController: NavController) {
    NavigationUI.setupWithNavController(this, navController)
}

Sets up a BottomNavigationView for use with a NavController. This will call android.view.MenuItem.onNavDestinationSelected when a menu item is selected. The selected item in the NavigationView will automatically be updated when the destination changes. 这是源码里面的注释,跟setupActionBarWithNavController的介绍大致相同说的就是 BottomNavigationView的会触发选择的改变,也就是当,中间的fragment绑定,点击下面的选项就会触发中间fragment的改变

三个fragment

image.png 从上面的截图可以看到,这里创建了三个fragment 在每个fragment中同样是使用DataBinding的技术,同时还使用ViewModel,这个是JetPack中的一个很重要的库

class DashboardFragment : Fragment() {

    private var _binding: FragmentDashboardBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val dashboardViewModel =
            ViewModelProvider(this).get(DashboardViewModel::class.java)

        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
        val root: View = binding.root

        val textView: TextView = binding.textDashboard
        dashboardViewModel.text.observe(viewLifecycleOwner) {
            textView.text = it
        }
        return root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

现在着重讲下上面的重要部分:

1、ViewModel ViewModel其实是一种数据保存的技术,在传统的数据保存中,当屏幕旋转或者说是Activity和fragment之间通信的时候,极为不变,ViewModel就是为了解决这个问题,我们以上面提到的这个dashboardViewModel为例子

class DashboardViewModel{

    private val _text = MutableLiveData<String>().apply {
        value = "This is dashboard Fragment"
    }
    val text: LiveData<String> = _text
}

我们定义一个DashboardViewModel,继承ViewModel,DashboardViewModel里面的数据,并不会随着随着Activity的pasue和stop的执行而销毁,他贯穿整个Activity的生命周期,因此当屏幕旋转这样的操作时候,并不会丢失数据。

image.png

2、LiveData: 在上面的代码中我们可以到看到LiveData的影子,这个又是什么呢? 其实这个是一种数据封装模型:

public abstract class LiveData<T> {

可以看到,他可以封装任何类型的数据

LiveData is a data holder class that can be observed within a given lifecycle. This means that an Observer can be added in a pair with a LifecycleOwner, and this observer will be notified about modifications of the wrapped data only if the paired LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is Lifecycle.State.STARTED or Lifecycle.State.RESUMED. An observer added via observeForever(Observer) is considered as always active and thus will be always notified about modifications. For those observers, you should manually call removeObserver(Observer). 这个是LiveData源码里面的注释,我们可以看到,其实里面是一种观察者的设计模型,我们可以动态的监控数据的变化

Sets the value. If there are active observers, the value will be dispatched to them. This method must be called from the main thread. If you need set a value from a background thread, you can use postValue(Object) Params: value – The new value 从这段话可以知道,当调用这个setValue的时候,数据发生改变了,就会通知那些观察对象,但是这个只能在主线程中调用,当在子线程的时候,我们需要通过调用postValue

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}


protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

fragment

以下面一个fragment为例子,通过binding拿到View的数据对象,通过ViewModel拿到数据,并通过观察者来监听数据的变化,最后设置到上面去

class DashboardFragment : Fragment() {

    private var _binding: FragmentDashboardBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val dashboardViewModel =
            ViewModelProvider(this).get(DashboardViewModel::class.java)

        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
        val root: View = binding.root

        val textView: TextView = binding.textDashboard
        dashboardViewModel.text.observe(viewLifecycleOwner) {
            textView.text = it
        }
        return root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

其他配置

在res文件夹中还有一个navigation目录,详细的记录着每个fragment的对应关系和一些基本信息。

<?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="vip.hblg.myapp.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="vip.hblg.myapp.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="vip.hblg.myapp.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

kotlin 语法糖

kotlin 新手 ,记录下文章中涉及的 kotlin 语法

  1. kotlin继承使用: :
  2. lateinit 关键字: 延迟加载的意思,在后面会加载,前面只是会定义下而已
  3. var:可变变量
  4. val:不可变的定义,类似final
  5. apply: apply函数扩展了所有的泛型对象,在闭包范围内可以任意调用该对象的任意方法,并在最后返回该对象.apply函数的返回值是本身,在函数内部,你可以任意调用对象的属性或方法或给属性赋值等

image.png