最近公司新开发了一个新App,android程序员就我一个人,爱咋写咋写~~于是我尝试用Kotlin完成了这个App。开发过程中越来越感动……太好用了!!!解决了android开发中的好多痛点!

dp, sp, px间的转换

传统的java写法通常是写个类似Util.dpToPx(int value)这样的方法。看看Kotlin是怎么通过Extension来优雅的解决的:

1
2
3
4
5
6
fun Int.dpToPx(): Int {
if (toInt() in intArrayOf(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) {
return this
}
return (this * Global.density).toInt() //这里的Gloabl.density是在应用启动时获取的
}

然后就可以这样写了:params.topMargin = 16.dpTpPx()

是不是感动到哭😭

设置View的宽高

如果用java来写通常也是会有个Util方法ViewHelper.setSize(View view, int width, int height),我每次在ViewHolder中调用一堆这样的方法都觉得很蛋疼……我们的救世主来了:

1
2
3
4
5
6
fun View.setSize(width: Int, height: Int) {
val params = layoutParams
params.width = width
params.height = height
layoutParams = params
}

注意这里的layoutParams是Kotlin将setLayoutParams()getLayoutParams()自动转为了Property,写过C#的应该不会陌生。然后我们就可以愉快的写代码了~yourView.setSize(100, 100)

View的动画

每次写Animation都是一个头两个大……这回我们不看java了,直接上Kotlin代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
fun View.animateTopMargin(valueFromInDP: Int, valueToInDP: Int, duration: Long = 300) {
val animation = object: Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val params = layoutParams as ViewGroup.MarginLayoutParams
val from = valueFromInDP.dpToPx()
val to = valueToInDP.dpToPx()
params.topMargin = from + ((to - from) * interpolatedTime).toInt()
layoutParams = params
}
}
animation.duration = duration
startAnimation(animation)
}

哇,duration还贴心的设了个默认值呢!然后就可以这样写了~yourView.animateTopMargin(16, 32)或者你想把duration设长一点yourView.animateTopMargin(16, 32, 500)

让Fresco更易用

Fresco是Facebook出品的网络图片加载库,实现了许多在android上非常有用的功能。比如Multi-URI功能可以优先显示低分辨率的图片,等高分辨率的图片下载完成后再进行替换。这个功能在移动设备上太太太有用了,就是写起来有点蛋疼:

1
2
3
4
5
6
7
Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);

于是我又祭出了神器Extension

1
2
3
4
5
6
7
8
fun SimpleDraweeView.setMultiUri(lowResUri: Uri, highResUri: Uri) {
val newController = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(controller)
.build();
controller = newController
}

然后怎么使用我就不写了,大家都知道了:smiley:

焕然一新的SharedPreference

你看到这里可能觉得不过是扩展方法嘛,Obj-C也可以做到啊,没什么嘛。哈哈哈哈图样图森破!有请Delegated Properties!下面这段代码来源于Omar Miatello在今年米兰的GDG DevFest的演讲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class AppPreferences(private val context: Context) {
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
var userName by PreferenceDelegates.string(defaultValue = context.getString(R.string.user_name))
var password by PreferenceDelegates.string()
}

object PreferenceDelegates {
public fun string(defaultValue: String? = null): ReadWriteProperty<AppPreferences, String?> {
return PrefString(defaultValue)
}
}

private class PrefString(private val defaultValue: String?) : ReadWriteProperty<AppPreferences, String?> {
override fun getValue(thisRef: AppPreferences, property: KProperty<*>): String? {
return thisRef.preferences.getString(property.name, defaultValue)
}

override fun setValue(thisRef: AppPreferences, property: KProperty<*>, value: String?) {
thisRef.preferences.edit().putString(property.name, value).apply()
}
}

Hmm,乍一看有点摸不着头脑呢……简单的讲,AppPreferences这个class里面有两个delegate property,分别是userName和password。他们的get和set方法分别对应了SharedPreference中的getString和setString方法。纳尼?这么神奇???接着往下看

1
2
3
4
5
6
7
8
class App: Application() {
val pref by lazy { AppPreferences(this) }

override fun onCreate() {
super.onCreate()
// 其他代码
}
}

在Application中定义了pref,注意它被by lazy修饰了,这样它就只在第一次调用的时候初始化,是不是很方便?

1
2
3
4
open class BaseActivity: AppCompatActivity() {
val app by lazy { application as App }
val pref by lazy { app.pref }
}

这里我们在所有Activity的父类中引入了pref,即AppPreferences的实例。然后……就可以爽了~~

1
2
3
4
5
6
7
8
9
10
class MyActivity: BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

pref.userName = "Who"
pref.password = "You"
Log.d("userName", pref.userName)
Log.d("password", pref.password)
}
}

看到没有,用起来简直和field一毛一样!再也不用写pref.edit().putString("userName", "Who").apply()了!!!老板再也不用担心我把SharedPreference的key写错了😂

Lambdas

这个话题可就大了,一本书都讲不完,举个例子好了。RxJava现在很火,光头哥写了一篇很好的文章介绍了RxJava在android中的应用。抄一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
getUser(userId)
.doOnNext(new Action1<User>() {
@Override
public void call(User user) {
processUser(user);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onNext(User user) {
userView.setUser(user);
}

@Override
public void onCompleted() {
}

@Override
public void onError(Throwable error) {
// Error handling
...
}
});

嗯,链式调用,很清晰,但用Kotlin还可以更简洁:

1
2
3
4
5
6
7
8
getUser(userId)
.doOnNext{ user -> processUser(user) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
user -> userView.setUser(user)
},{
error -> // Error handling
})

Done!收拾东西下班回家~

风险

我知道你在担心什么。Kotlin目前还是beta版,用于正式项目要评估风险。事实上在第一个beta版中就出现了在android 4.3设备上无法安装的bug,好在官方很快就修复了。怎么讲呢,这是需要项目组全体成员一起评估的问题。我之所以敢在实际项目中使用,一是因为项目比较简单,万一遇到不可解决的问题可以改用java写,反正Kotlin和java可以无缝混用;二是因为程序员就我一个人,胆子粗啊哈哈哈哈~

感想

整个项目做下来让我感到很愉悦,代码的优雅足以说服我冒一点点风险。开发过程中我常常在想,要是有一个包含各种扩展方法的library就好了,我就不用自己造小轮子了。但转念一下,我真的需要一个完整的library吗?那必然会像Guava一样成为一个巨无霸,但实际用到的功能可能只有10%。在Kotlin的官方SlackJake Wharton(没错就是那个大神)提了一个有趣的思路:在一个git里放了很多不同类别的扩展方法文件(比如ViewExt.kt,IntExt.kt),需要哪个文件就把它pull下来,而不是compile整个jar包。如果能用gradle实现就太方便了😆

补充资料

Antonio Leiva写了一本书Kotlin for Android Developers,价格实惠干货满满,值得购买。配套的源码在https://github.com/antoniolg/Kotlin-for-Android-Developers