qnote Advent Calendar 2016 8日目を担当いたします。
チャン・ジュン(日本人)です。
よろしくどうぞ
さて、2016年はズンドコキヨシの年でしたね。
しかしながらGetWildはAdventCalendarがあるのにズンドコキヨシにはAdventCalendarが無いようです。
そんなわけで、今回はズンドコキヨシアプリをKotlinで書くお話です
ちなみに、ズンドコキヨシについてはこちら → ズンドコキヨシまとめ
まずは準備から
ということで、まずはKotlinを使うために新規プロジェクト作成 → Kotlin化ですが、
プロジェクトのKotlin化は2日目の記事を御覧ください。
さて、今回はbuild.gradle
に色々追加します
apply plugin: 'kotlin-android-extensions'
↑kotlinExtensionsが利用可能になります。
android {
...
dataBinding {
enabled = true
}
}
dependencies {
...
kapt 'com.android.databinding:compiler:1.0-rc5'
}
kapt {
generateStubs = true
}
↑kotlinでdataBindingが利用可能になります。
dependencies {
...略
compile 'com.android.support:appcompat-v7:25.0.0'
compile "com.android.support:cardview-v7:25.0.0"
compile 'com.android.support:recyclerview-v7:25.0.0'
}
↑サポートライブラリを使う場合には必要です
今回はカードビューを使う機会がなさすぎたので使ってみようという感じです。
Let's ズンドコ
ズンドコDataBinding
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="zundokoObj" type="com.chanjun.zundoko.Zundoko"/>
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:cardUseCompatPadding="true"
app:cardCornerRadius="10dp">
...
</android.support.v7.widget.CardView>
</layout>
ソースコードばかり長くなってしまうのでCardViewの中身は省略しましたがズンドコ表示を行うTextViewが入ります。
式が使えるのでこんな感じに記述すればOKです。
android:text="@{zundokoObj.zundokoText}"
RecyclerViewについては特に特別な実装はないので割愛して次はAdapterです。
onCreateViewHolder
でバインディングクラスを作るのは説明不要かと思いますが、ズンドコオブジェクトのセットはRecyclerView内でViewの使い回しが発生した時にデータが更新されなくなってしまうのでonBindViewHolder
で行うようにしてください。
class ZundokoAdapter(val context: Context) : RecyclerView.Adapter<ZundokoViewHolder>() {
override fun onBindViewHolder(holder: ZundokoViewHolder?, position: Int) {
holder?.layoutBinding?.zundokoObj = zundokoList.get(position)
}
...
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ZundokoViewHolder {
return ZundokoViewHolder(ZundokoCardLayoutBinding.inflate(LayoutInflater.from(context), parent, false))
}
}
ZundokoViewHolder
では、上述した使い回しのためにバインディングクラスを保持するようにします。
コンストラクタから変数を代入したりしなくていいので1行で済みます。
{}も不要です。
別ファイルにする必要も無いのでAdapterと一緒にしておけば良いかと思います。
class ZundokoViewHolder(val layoutBinding: ZundokoCardLayoutBinding) : RecyclerView.ViewHolder(layoutBinding.root)
ZundokoオブジェクトはさらにKotlin独特の書き方になりますね。
特にfield = value
という書き方はわかりやすくて好みです。
class Zundoko() :BaseObservable() {
@get:Bindable
var zundokoText: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.zundokoText)
}
@get:Bindable
var zundokoResult: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.zundokoResult)
}
}
ズンドコロジック
ズンドコ文字列の生成は下記のメソッドで行います。
addZundokoObj()
で生成されたズンドコオブジェクトは即座にリストに追加され、画面に表示されます。
その後zundokoLoop()
で文字列のチェックを行いながら文字列が追加されていきます。
最重要ポイントは500msの遅延で表示されるズンドコを眺めながら、
あのメロディを脳内で補完することです!
/**
* ズンドコオブジェクト追加メソッド
*/
fun addZundokoObj() {
val zundokoObj = Zundoko()
zundokoObj.zundokoText = getZundokoText()
zundokoList.add(zundokoObj)
zundokoView.scrollToPosition(zundokoList.maxPosition())
//リズム良く表示するためのディレイ
Handler().postDelayed({
zundokoObj.zundokoText += "!"
zundokoLoop()
}, 500)
}
fun zundokoLoop() {
//リズム良く表示するためのディレイ
Handler().postDelayed({
var zundokoObj = zundokoList.get(zundokoList.maxPosition())
if (checkZundokoFinal(zundokoObj.zundokoText)) {
zundokoObj.zundokoResult = successText
zundokoList.add(zundokoList.maxPosition(), zundokoObj)
return@postDelayed
}
if (checkZundoko(zundokoObj.zundokoText)) {
zundokoObj.zundokoText += getZundokoText()
} else {
zundokoObj.zundokoResult = failureText
addZundokoObj()
return@postDelayed
}
zundokoList.set(zundokoList.maxPosition(), zundokoObj)
zundokoLoop()
}, 500)
}
ズンドコチェック
さて、肝心のズンドコチェックメソッドですが、今回はTextUtils.equals(a, b)
とか書くのはなんかやだなぁというだけの理由でパターンマッチで行いました。
最終的にsuccessTextを出すか確認するためにパターン文字列は()でグループ化し、5グループあるパターンでマッチしたかをチェックしています。
そして、var regex = "($zun!)"
と記述することで変数zunを""内で扱うことができ、
var regex = "(" + zun + "!)"
とか書かなくていいので快適です。
また、ズンドコパターン生成のループではレンジを利用しています。
2..zundoko.length / 2
は演算子オーバーロードなので2.rangeTo(zundoko.length / 2)
と表すことができます。
val zun = "ズン"
val doko = "ドコ"
fun checkZundoko(zundoko: String): Boolean {
return zundokoMatcher(zundoko).find()
}
fun checkZundokoFinal(zundoko: String): Boolean {
val m = zundokoMatcher(zundoko)
return if (m.find())
m.groupCount() == 5
else
false
}
fun zundokoMatcher(zundoko: String): Matcher {
var regex = "($zun!)"
for(i in 2..zundoko.length / 2) {
if (i == 5)
regex += "($doko)"
else
regex += "($zun)"
}
val pattern = Pattern.compile(regex)
return pattern.matcher(zundoko)
}
まとめ
コード書くよりまとめるのに時間がかかりました