KotlinでAndroidプロジェクトを開発するとどんな感じですか?

22281 ワード

前言
初めてKotlinを勉強してから、体験コードを書いてみて、実験的にカプセル化作業をして、最後までクローズアップしてプロジェクトを書きました.過程の中でやはり多くの問題に遭遇しました.たくさんのピットは自分の選択から来たものですが、例えば、ankoレイアウトを使ってxmlを諦めました.
この文章はただこの道で歩いてきた感想と驚きを整理したいだけです.Kotlinの学習と使用について、長期的に修正します.
本文
1.暇があれば安全です.仕事を終えて空の相手に戻るのが怖くないです.
簡単な例ですが、StringとStringですか?は2つのタイプです.Stringは空ではなく、価値があると確定しました.String?未知で、価値があるかもしれません.空かもしれません.オブジェクトの属性や方法を使用する場合、Stringタイプのオブジェクトは遠慮なく直接使用できますが、Stringタイプですか?タイプはあなたが先に判断してください.
fun demo() {
    val string1: String = "string1"
    val string2: String? = null
    val string3: String? = "string3"

    println(string1.length)
    println(string2?.length)
    println(string3?.length)
}
7
null
7
string 2は空のオブジェクトですが、属性/方法を呼び出したので、ポインタが空になりました.あなたが必要としているのは、一つだけ」ということです.
これは空の安全のメリットが現れないというなら、次の例を見ます.
val a: A? = A()
println(a?.b?.c)
ちょっと考えてみます.各クラスの属性が全部空になる可能性があります.JAVAではどうすればいいですか?
2.モデルチェンジと知能転換、省力と手間が省けます.
このようなJAVAコードを書いたことがあります.
if(view instanceof TextView) {
    TextView textView = (TextView) view;
    textView.setText("text");
}
Kotlinでは書き方が違っています.
if(view is TextView) {
    TextView textView = view as TextView
    textView.setText("text")
}
コードを削減した後のコントラストがより鮮明です.
JAVA

if(view instanceof TextView) {
    ((TextView) view).setText("text");
}

Kotlin

if(view is TextView) {
    (view as TextView).setText("text")
}
JAVAが対象になる前に(Class)という書き方に比べて、Kotlinは対象の後にas Classを加えてモデルチェンジしています.少なくとも私個人的には、asクラスのスムーズな書き方に慣れてからは、JAVAの前置の書き方に耐えられなくなります.castショートカットの存在があっても、コードを書く順番と考え方が中断しやすいです.
実は、Kotlinはここでもっと簡単にできます.
if(view is TextView) {
    view.setText("text")
}
現在の文脈ではviewがTextViewであることが判明しているので、現在のコードブロックではviewはView類ではなく、TextView類である.これはKotlinのインテリジェント変換です.
次に上の空の安全例を挙げます.StringとStringなら?違うタイプです.このようなコードを書く可能性がありますか?
val a: A? = A()
if (a != null) {
    println(a?.b)
}
このように書くと、Kotlinはかえってあなたに高い警告を示します.これは必要でないsafe callです.なぜかというと、前に書いてあるからです.a!nullですか?それでaはこのコードブロックにAではないですか?タイプではなく、Aタイプです.
val a: A? = A()
if (a != null) {
    println(a.b)
}
インテリジェント変換にはもう一つよく出てくるシーンがあります.それはswitch case文の中です.Kotlinではwhen文法です.
fun testWhen(obj: Any) {
    when(obj) {
        is Int -> {
            println("obj is a int")
            println(obj + 1)
        }

        is String -> {
            println("obj is a string")
            println(obj.length)
        }

        else -> {
            println("obj is something i don't care")
        }
    }
}

fun main(args: Array) {
    testWhen(98)
    testWhen("98")
}
    :
obj is a int
99
obj is a string
2
Stringと判断された条件で、元々はAny類のobjオブジェクトであり、String類に属する.length属性をそのまま使用することができます.JAVAでは、こうしなければなりません.
System.out.println("obj is a string")
String string = (String) obj;
System.out.println(string.length)
または
System.out.println("obj is a string")
System.out.println(((String) obj).length)
前者は書くことと読むことの一貫性に障害があります.後者は.
Kotlinの知能はそれだけではないです.今でも、コードを作る時には時々高い警告が出ます.この時になって初めて私の書き方が余計だと分かりました.Kotlinはもう処理してくれました.ここではいちいちくどくどと述べない.
3.switchより強いwhen
上記のスマート変換の例を通して、一部のwhenの機能を示しました.JAVAのswitchに対して、Ktlinのwhenが私にくれた驚きはこれだけではないです.
たとえば:
fun testWhen(int: Int) {
    when(int) {
        in 10 .. Int.MAX_VALUE -> println("${int}        ")
        2, 3, 5, 7 -> println("${int}    ")
        else -> println("${int}     ")
    }
}

fun main(args: Array) {
    (0..10).forEach { testWhen(it) }
}
    :
0     
1     
2    
3    
4     
5    
6     
7    
8     
9     
10        
JAVAのハングアップのswitch-case文と違って、whenの中で、10からInt.MAX_にパラメータを合わせることができます.VALEの区間は、2、3、5、7のセットの値を合わせてもいいです.もちろんここでは全ての特性を挙げていません.whenのフレキシブルで簡潔で、それを使っているうちにとても楽しくなりました.(JAVAのSwitchと比べると)
4.容器の操作子
RxJavaに夢中になってから、私は本当に以前に戻りにくいです.この中にRxJavaの中にたくさんの便利な操作があります.Kotlinでは、容器自体に一連の操作符が付いていて、非常に簡潔にいくつかの論理を実現することができます.
たとえば:
(0 until container.childCount)
        .map { container.getChildAt(it) }
        .filter { it.visibility == View.GONE }
        .forEach { it.visibility = View.VISIBLE }
上記のコードはまず0からcontainer.childCount-1までの区間を作成しました.map操作符を使ってchildのコードを取り出し、このIntの集合をchild Viewの集合に変えます.それからfilter操作符で集合をスクリーニングして、childViewの中のすべての視認性をGONEとする新しい集合を選出します.最終的にforEachは全てのchildViewをVISIBLEに設定しました.
ここにJAVAのコードを貼って比較します.
for(int i = 0; i < container.childCount - 1;  i++) {
    View childView = container.getChildAt(i);
    if(childView.getVisibility() == View.GONE) {
        childView.setVisibility(View.VISIBLE);
    }
}
ここではこのチェーン式の書き方の長所を詳しく説明しません.
5.スレッド切替、so easy
上記でRxJavaに言及した以上は、RxJavaのもう一つの長所であるスレッドスケジュールを思い出さなければならない.Kotlinの中にはAndroidの開発量のために作られたライブラリがあります.ankoという名前で、簡単に開発できるコードがたくさん含まれています.スレッドを簡略化しました.
async {
    val response = URL("https://www.baidu.com").readText()
    uiThread {
        textView.text = response
    }
}
上のコードは簡単です.ASync方式でコードを非同期スレッドに実装し、http要求に対する応答を読み取った後、uiThread方法でuiスレッドに切り替えてtextViewに表示します.
内部の実現を抜きにして、簡単な非同期任務のために、無効なコードをたくさん書く必要はありません.慣例によって、ここでJAVAのコードを貼って対比をするべきですが、スクリーンを打ちたくないことを許してください.
6.キーワードの一つの実現例
そうです.キーワード一つで単例が実現できます.
object Log {
    fun i(string: String) {
        println(string)
    }
}

fun main(args: Array<String>) {
    Log.i("test")
}
さよなら、シングルモード
7.自動getter、setter及びclassの簡潔な声明
JAVAには次のような種類があります.
class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void getName() {
        return name;
    }
}

Person person = new Person("  ");
標準表記では、一つの属性はgetとsetの2つの方法に対応しており、手動で書くべきコードの量はかなり大きいことがわかる.もちろんショートカットキーがあります.これらのコードを生成するのに役立ちますが、複雑な状況を考慮すると完璧ではありません.
Ktlinでは、このようにしています.
class Person(var name: String)
val person = Person("  ");
標準値を追加することもできます.
class Person(var name: String = "  ")
val person = Person()
私のプロジェクトの中の比較的複雑なデータを添付します.
data class Column(
        var subId: String?,
        var subTitle: String?,
        var subImg: String?,
        var subCreatetime: String?,
        var subUpdatetime: String?,
        var subFocusnum: Int?,
        var lastId: String?,
        var lastMsg: String?,
        var lastType: String?,
        var lastMember: String?,
        var lastTIme: String?,
        var focus: String?,
        var subDesc: String?,
        var subLikenum: Int?,
        var subContentnum: Int?,
        var pushSet: String?
)
一目で見たら、余分なコードがありません.なぜかというと、KotlinコードはJAVAコードよりもきれいに書きやすいと思います.
8.DSLプログラミング
dslといえば、Androidの開発者が一番多く接するのはgradleかもしれません.
たとえば:
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.zll.demo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
これはGrouvyのDSLの一段で、コンパイル構成を宣言します.
Androidプロジェクトのコードの中でDSLを使うとどんな感じですか?
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val homeFragment = HomeFragment()
    val columnFragment = ColumnFragment()
    val mineFragment = MineFragment()

    setContentView(
            tabPages {
                backgroundColor = R.color.white
                dividerColor = R.color.colorPrimary
                behavior = ByeBurgerBottomBehavior(context, null)

                tabFragment {
                    icon = R.drawable.selector_tab_home
                    body = homeFragment
                    onSelect { toast("home selected") }
                }

                tabFragment {
                    icon = R.drawable.selector_tab_search
                    body = columnFragment
                }

                tabImage {
                    imageResource = R.drawable.selector_tab_photo
                    onClick { showSheet() }
                }

                tabFragment {
                    icon = R.drawable.selector_tab_mine
                    body = mineFragment
                }
            }
    )
}
はい、上のコードはこのメインインターフェースを構築するためのviewPager+fragments+tabBarです.tabPagesを開始として、背景色、分割線などの属性を設定します.tabFraamentでfragment+tabButtonを追加します.tabImage方法はtabButtonだけを追加します.見たコードは全部配置していますが、具体的な実装はパッケージ化されています.
前述のankoという倉庫は、実はxmlの代わりにレイアウト用にも使えます.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    verticalLayout {
        textView {
            text = "    "
        }.lparams {
            width = matchParent
            height = dip(44)
        }

        textView {
            text = "    "
            gravity = Gravity.CENTER
        }.lparams {
            width = matchParent
            height = matchParent
        }
    }
}
JAVAコードでレイアウトするよりも、このDSLの方式も配置をしています.レイアウトの実現コードを後ろに入れて、xmlレイアウトに近いです.
DSLとankoのレイアウトについては、これから専門的な文章を紹介します.
9.委託/代理、ShardPreferenceはもう面倒をかけません.
Kotlinの中の委託機能を通して、私達は簡単にSharedPreferenceの代理種類を書くことができます.
class Preference(val context: Context, val name: String?, val default: T) : ReadWriteProperty {
    val prefs by lazy {
        context.getSharedPreferences("xxxx", Context.MODE_PRIVATE)
    }

    override fun getValue(thisRef: Any?, property: KProperty): T = with(prefs) {
        val res: Any = when (default) {
            is Long -> {
                getLong(name, 0)
            }
            is String -> {
                getString(name, default)
            }
            is Float -> {
                getFloat(name, default)
            }
            is Int -> {
                getInt(name, default)
            }
            is Boolean -> {
                getBoolean(name, default)
            }
            else -> {
                throw IllegalArgumentException("This type can't be saved into Preferences")
            }
        }
        res as T
    }

    override fun setValue(thisRef: Any?, property: KProperty, value: T) = with(prefs.edit()) {
        when (value) {
            is Long -> putLong(name, value)
            is String -> putString(name, value)
            is Float -> putFloat(name, value)
            is Int -> putInt(name, value)
            is Boolean -> putBoolean(name, value)
            else -> {
                throw IllegalArgumentException("This type can't be saved into Preferences")
            }
        }.apply()
    }
}
とりあえず原理を飛ばして、どうやって使うかを見に行きます.
class EntranceActivity : BaseActivity() {

    private var userId: String by Preference(this, "userId", "")

    override fun onCreate(savedInstanceState: Bundle?) {
        testUserId()
    }

    fun testUserId() {
        if (userId.isEmpty()) {
            println("userId is empty")
            userId = "default userId"
        } else {
            println("userId is $userId")
        }
    }
}
     app     :
userId is empty
userId is default userId
userId is default userId
...
初めてアプリを起動した時、シェアから取り出したuserIdは空いていますが、後ろは空いていません.これにより、userId=「default userId」というコードは、SharedPreferenceの値を修正することに成功しました.
つまり、このPreferenceエージェントの助けで、SharedPreferenceアクセス操作は通常のオブジェクト呼び出し、賦課と同じ簡単になります.
10.拡張、工具類とのバイバイ
昔、ある人はツール自体がオブジェクト指向の思想に反するものだと私に言いました.でも、その時はツールを使わせないようにと思っていました.コードはどう書きますか?私はこの概念を拡張するということを知って、やっと開豁しました.
fun ImageView.displayUrl(url: String?) {
    if (url == null || url.isEmpty() || url == "url") {
        imageResource = R.mipmap.ic_launcher
    } else {
        Glide.with(context)
                .load(ColumnServer.SERVER_URL + url)
                .into(this)
    }
}
...
val imageView = findViewById(R.id.avatarIv) as ImageView
imageView.displayUrl(url)
上記のコードは、
1.ImageViewというカテゴリーにディsplayUrlという方法を拡張しましたが、この方法はurlというStringを受信しますか?クラスのオブジェクトもし意外でないならば、Glideを通じて(通って)このurlのピクチャーをロードすることができて、当面のmageViewの上で表示します;
2.私は別の場所でFIndView ByIdを通じてImageView類の実例をもらいました.そしてこのimagewのdisplayUrl方法を呼び出して、私が入ってきたurlをロードしようとします.
ImageViewのための拡張方法は、ImageViewを継承することによりCustomImageViewを書くよりも、侵入性が低く、コードの中にCustoomImageViewを全て書く必要もなく、xmlレイアウトでパケット名を書き込み死ぬ必要もなく、移植の面倒を引き起こす.
このことは工具類でももちろんできます.例えばImageUtil.displayUrl(imagView、url)を作ってもいいですが、工具類の読み方は拡張されていません.もっと自然にすらすら読めます.
拡張はKotlinよりJAVAの一大殺人器です.
今はここまで書いて、後はまた更新します.