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”?/>
<TextView
android: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”?/>
<TextView
android:id="@+id/repository_owner"
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”?/>
<TextView
android: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.google.com/codelabs/constraint-layout/index.html?index=…%2F…%2Findex#0
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 file
apply plugin: ‘kotlin-kapt’
android {
//other things that we already used
dataBinding.enabled = true
}
dependencies {
//other dependencies that we used
kapt “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.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“me.mladenrakonjac.modernandroidapp.MainActivity”>
<TextView
android: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”?/>
<TextView
android: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”?/>
<TextView
android:id="@+id/repository_owner"
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”?/>
<TextView
android: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>
注意,你需要将所有的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?=?1
set(value)?{?field?=?Math.abs(value)?}
field ?? 这又是个什么东西?为了更清楚明白,请看下面代码:
var?a?=?1
set(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标签。
<variable
name=“repository”
type=“me.mladenrakonjac.modernandroidapp.uimodels.Repository”
/>
我们可以在布局中访问存储的变量repository ,例如,我们可以如下使用id是repository_name 的TextView,如下:
`android:text="@{repository.repositoryName}"`
repository_name文本视图将显示从repository变量的属性repositoryName 获取的文本。剩下的唯一事情就是将repository 变量从xml绑定到MainActivity.kt 中的repository。
点击Build使DataBinding 为我们生成类,然后在MainActivity中添加两行代码:
binding.repository?=?repository
binding.executePendingBindings()
如果你运行APP,你会看到TextView上显示的是:“Medium Android Repository Article” ,非常棒的功能,是吧?
但是,如果我们像下面这样改一下呢?
Handler().postDelayed({repository.repositoryName=“New?Name”},?2000)
新的文本将会在2000ms后显示吗?不会的,你必须重新设置一次repository ,像这样:
Handler().postDelayed({repository.repositoryName=“New?Name”
binding.repository?=?repository
binding.executePendingBindings()},?2000)
但是,如果我们每次更改一个属性都要这么写的话,那就非常蛋疼了,这里有一个更好的方案叫做Property Observer 。
让我们首先解释一下什么是观察者模式,因为在rxJava部分中我们也将需要它:
可能你已经听说过http://androidweekly.net/ ,这是一个关于Android开发的周刊。如果您想接收它,则必须订阅它并提供您的电子邮件地址。过了一段时间,如果你不想看了,你可以去网站上取消订阅。
后在MainActivity中添加两行代码:
binding.repository?=?repository
binding.executePendingBindings()
如果你运行APP,你会看到TextView上显示的是:“Medium Android Repository Article” ,非常棒的功能,是吧?
但是,如果我们像下面这样改一下呢?
Handler().postDelayed({repository.repositoryName=“New?Name”},?2000)
新的文本将会在2000ms后显示吗?不会的,你必须重新设置一次repository ,像这样:
Handler().postDelayed({repository.repositoryName=“New?Name”
binding.repository?=?repository
binding.executePendingBindings()},?2000)
但是,如果我们每次更改一个属性都要这么写的话,那就非常蛋疼了,这里有一个更好的方案叫做Property Observer 。
让我们首先解释一下什么是观察者模式,因为在rxJava部分中我们也将需要它:
可能你已经听说过http://androidweekly.net/ ,这是一个关于Android开发的周刊。如果您想接收它,则必须订阅它并提供您的电子邮件地址。过了一段时间,如果你不想看了,你可以去网站上取消订阅。
|