写点什么

【译】使用 Kotlin 从零开始写一个现代 Android- 项目 -Part1(1)

用户头像
Android架构
关注
发布于: 刚刚

android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:layout_marginEnd="16dp"android:layout_marginStart="16dp"android:layout_marginTop="8dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/repository_name"app:layout_constraintVertical_bias="0.0"tools:text="Mladen Rakonjac" />


<TextViewandroid:id="@+id/number_of_starts"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:layout_marginEnd="16dp"android:layout_marginStart="16dp"android:layout_marginTop="8dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="1"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/repository_owner"app:layout_constraintVertical_bias="0.0"tools:text="0 stars" />


</android.support.constraint.ConstraintLayout>


不要被tools:text搞迷惑了,它的作用仅仅是让我们可以预览我们的布局。


我们可以注意到,我们的布局是扁平的,没有任何嵌套,你应该尽量少的使用布局嵌套,因为它会影响我们的性能。ConstraintLayout 也可以在不同的屏幕尺寸下正常工作。



我有种预感,很快就能达到我们想要的布局效果了。


上面只是一些关于ConstraintLayout的少部分介绍,你也可以看一下关于ConstraintLayout使用的 google code lab:?https://codelabs.developers.g...

4. Data binding library

当我听到 Data binding 库的时候,我的第一反应是:Butterknife 已经很好了,再加上,我现在使用一个插件来从 xml 中获取 View,我为啥要改变,来使用 Data binding 呢?但当我对 Data binding 有了更多的了解之后,我的它的感觉就像我第一次见到 Butterknife 一样,无法自拔。

Butterknife 能帮我们做啥?

ButterKnife 帮助我们摆脱无聊的findViewById。因此,如果您有 5 个视图,而没有 Butterknife,则你有 5 + 5 行代码来绑定您的视图。使用 ButterKnife,您只有我行代码就搞定。就是这样。

Butterknife 的缺点是什么?

Butterknife 仍然没有解决代码可维护问题,使用 ButterKnife 时,我经常发现自己遇到运行时异常,这是因为我删除了 xml 中的视图,而没有删除 Activity/Fragment 类中的绑定代码。另外,如果要在 xml 中添加视图,则必须再次进行绑定。真的很不好维护。你将浪费大量时间来维护 View 绑定。

那与之相比,Data Binding 怎么样呢?

有很多好处,使用 Data Binding,你可以只用一行代码就搞定 View 的绑定,让我们看看它是如何工作的,首先,先将 Data Binding 添加到项目:


// at the top of fileapply plugin: 'kotlin-kapt'


android {//other things that we already useddataBinding.enabled = true}dependencies {//other dependencies that we usedkapt "com.android.databinding:compiler:3.0.0-beta1"}


请注意,数据绑定编译器的版本与项目build.gradle文件中的 gradle 版本相同:


classpath?'com.android.tools.build:gradle:3.0.0-beta1'


然后,点击Sync Now,打开activity_main.xml,将Constraint Layout?用 layout 标签包裹


<?xml version="1.0" encoding="utf-8"?><layout 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.support.constraint.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">


<TextViewandroid:id="@+id/repository_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_marginStart="16dp"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintHorizontal_bias="0.0"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.083"tools:text="Modern Android app" />


<TextViewandroid:id="@+id/repository_has_issues"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_marginStart="16dp"android:layout_marginTop="8dp"android:text="@string/has_issues"android:textStyle="bold"app:layout_constraintBottom_toBottomOf="@+id/repository_name"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="1.0"app:layout_constraintStart_toEndOf="@+id/repository_name"app:layout_constraintTop_toTopOf="@+id/repository_name"app:layout_constraintVertical_bias="1.0" />


<TextViewandroid:id="@+id/repository_owner"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:layout_marginE


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


nd="16dp"android:layout_marginStart="16dp"android:layout_marginTop="8dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/repository_name"app:layout_constraintVertical_bias="0.0"tools:text="Mladen Rakonjac" />


<TextViewandroid:id="@+id/number_of_starts"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:layout_marginEnd="16dp"android:layout_marginStart="16dp"android:layout_marginTop="8dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="1"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/repository_owner"app:layout_constraintVertical_bias="0.0"tools:text="0 stars" />


</android.support.constraint.ConstraintLayout>


</layout>


注意,你需要将所有的 xml 移动到 layout 标签下面,然后点击Build图标或者使用快捷键Cmd + F9,我们需要构建项目来使 Data Binding 库为我们生成ActivityMainBinding类,后面在 MainActivity 中将用到它。


如果没有重新编译项目,你是看不到ActivityMainBinding的,因为它在编译时生成。


我们还没有完成绑定,我们只是定义了一个非空的 ActivityMainBinding 类型的变量。你会注意到我没有把??放在 ActivityMainBinding 的后面,而且也没有初始化它。这怎么可能呢?lateinit?关键字允许我们使用非空的延迟被初始化的变量。和 ButterKnife 类似,在我们的布局准备完成后,初始化绑定需要在 onCreate 方法中进行。此外,你不应该在 onCreate 方法中声明绑定,因为你很有可能在 onCreate 方法外使用它。我们的 binding 不能为空,所以这就是我们使用 lateinit 的原因。使用 lateinit 修饰,我们不需要在每次访问它的时候检查 binding 变量是否为空。


我们初始化 binding 变量,你需要替换:


setContentView(R.layout.activity_main)


为:


binding = DataBindingUtil.setContentView(this, R.layout.activity_main)


就是这样,你成功的绑定了所有 View,现在你可以访问它并且做一些更改,例如,我们将仓库名字改为Modern Android Medium Article:


binding.repositoryName.text =?"Modern Android Medium Article"


如你所见,现在我们可以通过bingding变量来访问main_activity.xml的所有 View 了(前提是它们有 id),这就是 Data Binding 比 ButterKnife 好用的原因。

kotlin 的 Getters 和 setters

大概,你已经注意到了,我们没有像 Java 那样使用.setText(),我想在这里暂停一下,以说明与 Java 相比,Kotlin 中的 getter 和 setter 方法如何工作的。


首先,你需要知道,我们为什么要使用 getters 和 setters,我们用它来隐藏类中的变量,仅允许使用方法来访问这些变量,这样我们就可以向用户隐藏类中的细节,并禁止用户直接修改我们的类。假设我们用 Java 写了一个 Square 类:


public class Square {private int a;


Square(){a = 1;}


public void setA(int a){this.a = Math.abs(a);}


public int getA(){return this.a;}


}


使用setA()方法,我们禁止了用户向Square类的a变量设置一个负数,因为正方形的边长一定是正数,要使用这种方法,我们必须将其设为私有,因此不能直接设置它。这也意味着我们不能直接获得a,需要给它定一个 get 方法来返回a,如果有 10 个变量,那么我们就得定义 10 个相似的 get 方法,写这样无聊的样板代码,通常会影响我们的心情。


Kotling 使我们的开发人员更轻松了。如果你调用下面的代码:


var?side:?Int?= square.a


这并不意味着你是在直接访问 a 变量,它和 Java 中调用getA()是相同的


int?side = square.getA();


因为 Kotlin 自动生成默认的 getter 和 setter。在 Kotlin 中,只有当您有特殊的 setter 或 getter 时,才应指定它。否则,Kotlin 会为您自动生成:


var a = 1set(value) { field = Math.abs(value) }


field?? 这又是个什么东西?为了更清楚明白,请看下面代码:


var a = 1set(value) { a = Math.abs(value) }


这表明你在调用 set 方法中的set(value){},因为 Kotlin 的世界中,没有直接访问属性,这就会造成无限递归,当你调用a = something,会自动调用 set 方法。使用 filed 就能避免无限递归,我希望这能让你明白为什么要用 filed 关键字,并且了解 getters 和 setters 是如何工作的。


回到代码中继续,我将向你介绍 Kotlin 语言的另一个重要功能:apply 函数:


class MainActivity : AppCompatActivity() {


lateinit var binding: ActivityMainBinding


override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)


binding = DataBindingUtil.setContentView(this, R.layout.activity_main)binding.apply {repositoryName.text = "Medium Android Repository Article"repositoryOwner.text = "Mladen Rakonjac"numberOfStarts.text = "1000 stars"


}}}


apply 允许你在一个实例上调用多个方法,我们仍然还没有完成数据绑定,还有更棒的事儿,让我们为仓库定义一个 UI 模型(这个是 github 仓库的数据模型 Repository,它持有要展示的数据,请不要和 Repository 模式的中的 Repository 搞混淆了哈),要创建一个 Kotlin class,点击New -> Kotlin File/Class :


class?Repository(var?repositoryName: String?,var?repositoryOwner: String?,var?numberOfStars:?Int? ,var?hasIssues:?Boolean?=?false)


在 Kotlin 中,主构造函数是类头的一部分,如果你不想定义次构造函数,那就是这样了,数据类到此就完成了,构造函数没有参数分配给字段,没有 setters 和 getters,整个类就一行代码。


回到MainActivity.kt,为Repository创建一个实例。


var repository = Repository("Medium Android Repository Article","Mladen Rakonjac", 1000, true)


你应该注意到了,创建类实例,没有用new


现在,我们在activity_main.xml?中添加 data 标签。


<data><variablename="repository"type="me.mladenrakonjac.modernandroidapp.uimodels.Repository"/></data>


我们可以在布局中访问存储的变量repository,例如,我们可以如下使用 id 是repository_name的 TextView,如下:


android:text="@{repository.repositoryName}"


repository_name 文本视图将显示从 repository 变量的属性repositoryName获取的文本。剩下的唯一事情就是将repository变量从 xml 绑定到MainActivity.kt中的 repository。


点击 Build 使 DataBinding 为我们生成类,然后在 MainActivity 中添加两行代码:


binding.repository = repositorybinding.executePendingBindings()


如果你运行 APP,你会看到 TextView 上显示的是:“Medium Android Repository Article”,非常棒的功能,是吧?


但是,如果我们像下面这样改一下呢?


Handler().postDelayed({repository.repositoryName="New Name"},?2000)


新的文本将会在 2000ms 后显示吗?不会的,你必须重新设置一次repository,像这样:


Handler().postDelayed({repository.repositoryName="New Name"binding.repository = repositorybinding.executePendingBindings()}, 2000)


但是,如果我们每次更改一个属性都要这么写的话,那就非常蛋疼了,这里有一个更好的方案叫做Property Observer


让我们首先解释一下什么是观察者模式,因为在 rxJava 部分中我们也将需要它:


可能你已经听说过?http://androidweekly.net/,这是一个关于 Android 开发的周刊。如果您想接收它,则必须订阅它并提供您的电子邮件地址。过了一段时间,如果你不想看了,你可以去网站上取消订阅。


这就是一个观察者/被观察者的模式,在这个例子中, Android 周刊是被观察者,它每周都会发布新闻通讯。读者是观察者,因为他们订阅了它,一旦订阅就会收到数据,如果不想读了,则可以停止订阅。


Property Observer在这个例子中就是 xml layout,它将会监听Repository实例的变化。因此,Repository被观察者,例如,一旦在 Repository 类的实例中更改了 repository nane 属性后,xml 不调用下面的代码也会更新:


binding.repository = repositorybinding.executePendingBindings()


如何让它使用 Data Binding 库呢?,Data Binding 库提供了一个BaseObservable类,我们的 Repostory 类必须继承它。


class Repository(repositoryName : String, var repositoryOwner: String?, var numberOfStars: Int?, var hasIssues: Boolean = false) : BaseObservable(){


@get:Bindablevar repositoryName : String = ""set(value) {field = valuenotifyPropertyChanged(BR.repositoryName)}


}


当我们使用了 Bindable 注解时,就会自动生成 BR 类。你会看到,一旦设置新值,就会通知它更新。现在运行 app 你将看到仓库的名字在 2 秒后改变而不必再次调用?executePendingBindings()


以上就是这一节的所有内容,下一节将会讲 MVVM+Repository 模式的使用。敬请期待!感谢阅读。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
【译】使用Kotlin从零开始写一个现代Android-项目-Part1(1)