Anko LayoutsおよびAnkoコルーチンを使用したAndroidアプリの作成

画像







約1年前、AndroidプロジェクトでKotlinの使用を開始しました。 学ぶのが面白い何か新しいことを試してみたかった。 それからアンコに出会いました。 その時までに、xmlでUIを書くのはかなり退屈でした。 WYSIWYGとAndroid Studioで使用されるxmlマークアップに頼ることなく、私はいつも自分の手でインターフェースを書くのが好きでした。 唯一のマイナスは、変更を確認するためにアプリケーションを再起動する必要があることです。 アプリケーションを起動せずに、UIがどのように見えるかを示すプラグインを使用できますが、私にはかなり奇妙に思えました。 また、XMLをAnko Layouts DSLに変換する優れた能力も備えています。







ライブラリの最大の欠点は、ドキュメントがほとんど完全に欠けていることです。 正しく使用する方法を理解するために、ソースをよく調べなければなりませんでした。 この記事では、Anko LayoutsとAnko Coroutinesを使用したアプリケーション開発について詳しく説明します。







Ankoライブラリ自体は、4つの独立した部分に分かれています。









ライブラリをプロジェクトに追加するには、プロジェクトに応じて1行追加するだけです。







implementation "org.jetbrains.anko:anko:$anko_version"









anko_version



は、プロジェクトレベルでbuild.gradleファイルに登録されているライブラリの現在のバージョンです。







ext.anko_version='0.10.8'









あんこレイアウト



Anko Layoutsでは、Javaを使用するよりも効率的にUI Androidアプリケーションを開発できます。







フィールドのメインプレーヤーは、 AnkoComponent<T>



を受け入れてViewを返す単一のcreateViewメソッドを持つAnkoComponent<T>



インターフェイスです。 UI全体が作成されるのはこのメソッドです。 AnkoContext<T>



インターフェースは、 ViewManagerのラッパーです 。 それについての詳細は後ほどです。







AnkoComponent<T>



少し理解したので、アクティビティのクラスで簡単なUIを作成してみましょう。 そのようなUIの「直接」スペルは、アクティビティでのみ可能であることを明確にする価値があります。これは、 addViewメソッドが呼び出されsetContentView = true



メソッドでAnkoContextImpl<T>



が作成される別の拡張関数ankoViewが記述されているためです







 class AppActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } }
      
      





明らかに、複数のTextViewの場合、onCreateメソッドはすぐに一種のダンプに変わります。 ActivityクラスをUIから分離してみましょう。 これを行うには、記述される別のクラスを作成します。







 class AppActivityUi: AnkoComponent<AppActivity> { override fun createView(ui: AnkoContext<AppActivity>): View = with(ui) { verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } } }
      
      





UIをアクティビティに渡すために、 次を使用できます。







 AppActivityUi().setContentView(this)
      
      





わかりましたが、フラグメントのUIを作成する場合はどうでしょうか。 これを行うには、onCreateViewフラグメントメソッドからcreateViewメソッドを直接呼び出して使用します。 次のようになります。







 class AppFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return AppFragmentUi().createView(AnkoContext.create(requireContext(), this)) } }
      
      





すでに述べたように、 AnkoContext<T>



はViewManagerのラッパーです。 コンパニオンオブジェクトには、 AnkoContext<T>



を返す3つの主要なメソッドがあります。 それらをより詳細に分析します。







作成する



  fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
      
      





そして彼の双子の兄弟







 fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
      
      





AnkoContextImpl<T>



返します。







メソッドは、たとえば、アクティビティとフラグメントの前の例のように、すべての標準的なケースで使用されます。 ここで最も興味深いのは、ownerオプションとsetContentViewオプションです。 1つ目は、特定のFragment、ActivityなどをcreateViewインスタンスメソッドに渡すことを可能にします。







 MyComponent().createView(AnkoContext.create(context, myVew)) class MyComponent: AnkoComponent<View> { override fun createView(ui: AnkoContext<View>): View = with(ui) { val myView: View= ui.owner //     } }
      
      





2番目のパラメーターsetContentView は、 Context がActivityまたはContextWrapperのインスタンスである場合、結果のビューを自動的に追加しようとします 。 彼が成功しなかった場合、IllegalStateExceptionをスローします。







createDelegate



この方法は非常に便利ですが、gihabの公式ドキュメントには何も記載されていません。 UIクラスの肥大化の問題を解決したときに、ソースでそれを見つけました。







 fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
      
      





createViewコンポーネントの結果を所有者に追加できます。







例としての使用を検討してください。 アプリケーション画面の1つであるAppFragmentUiを記述する大きなクラスがあるとします。







 verticalLayout { relativeLayout { id = R.id.toolbar //    view } relativeLayout{ id = R.id.content //     view } }
      
      





論理的には、ツールバーとコンテンツのAppFragmentUiToolbarとAppFragmentUiContentの2つの部分にそれぞれ分割できます。 次に、メインクラスのAppFragmentUiがはるかに単純になります。







 class AppFragmentUi: AnkoComponent<AppFragment> { override fun createView(ui: AnkoContext<AppFragment>) = with(ui) { verticalLayout { AppFragmentUiToolbar().createView(AnkoContext.createDelegate(this)) AppFragmentUiContent().createView(AnkoContext.createDelegate(this)) } } } class AppFragmentUiToolbar : AnkoComponent<_LinearLayout> { override fun createView(ui: AnkoContext<_LinearLayout>): View = with(ui.owner) { relativeLayout { id = R.id.toolbar //   view } } }
      
      





uiではなく、ui.ownerがwith



関数にオブジェクトとして渡されることに注意してください。

したがって、次のアルゴリズムがあります。







  1. コンポーネントインスタンスが作成されます。
  2. createViewメソッドは、追加するビューを作成します。
  3. 結果のビューが所有者に追加されます。


近似値: this.addView(AppFragmentUiToolbar().createView(...))





ご覧のとおり、createDelegateのオプションは読みやすくなっています。







createReusable



標準のAnkoContext.createに似ていますが、少し追加されています-最新のビューはルートビューと見なされます。







 class MyComponent: AnkoComponent<MyObject> { override fun createView(ui: AnkoContext<MyObject>): View = with(ui) { textView("Some text") //       IllegalStateException: View is already set //     "Another text" textView("Another text") } }
      
      





標準実装では、ルートビューが設定されている場合、2番目のビューを並行して設定しようとすると、例外がスローされます







createReusableメソッドはReusableAnkoContextクラスを返します。このクラスはAnkoContextImplを継承し、 alreadyHasView()



メソッドをオーバーライドします。







カスタムビュー



幸いなことに、Anko Layoutsはこの機能に限定されていません。 独自のCustomViewを表示する必要がある場合は、書く必要はありません。







 verticalLayout { val view = CustomView(context) //.... addView(view) //  addView(view.apply { ... }) }
      
      





これを行うには、独自のラッパーを追加しますが、これも同じことを行います。







ここの主なコンポーネントは、ViewManager、Context、またはActivityからの拡張メソッド<T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit)



です。









CustomViewの実装を追加します







 inline fun ViewManager.customView(theme: Int = 0, init: (CustomView).() -> Unit): CustomView { return ankoView({ CustomView(it) }, theme, init) }
      
      





これで、CustomViewが非常に簡単に作成されます。







 customView { id = R.id.customview //   }
      
      





lparamsを使用して、LayoutParamsをビューに適用できます。







 textView("text") { textSize = 12f }.lparams(width = matchParent, height = wrapContent) { centerInParent() }
      
      





これはすべてのビューに適用できるわけではないことに注意してください。通常、すべてのlparamsメソッドはラッパーで宣言されます。 たとえば、 _RelativeLayoutRelativeLayoutのラッパーです。 みんなのために。







幸いなことに、Androidサポートライブラリ用にいくつかのラッパーが記述されているため、gradleファイルにのみ依存関係を含めることができます。







  // Appcompat-v7 (Anko Layouts) implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" implementation "org.jetbrains.anko:anko-coroutines:$anko_version" // CardView-v7 implementation "org.jetbrains.anko:anko-cardview-v7:$anko_version" // Design implementation "org.jetbrains.anko:anko-design:$anko_version" implementation "org.jetbrains.anko:anko-design-coroutines:$anko_version" // GridLayout-v7 implementation "org.jetbrains.anko:anko-gridlayout-v7:$anko_version" // Percent implementation "org.jetbrains.anko:anko-percent:$anko_version" // RecyclerView-v7 implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version" implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version" // Support-v4 (Anko Layouts) implementation "org.jetbrains.anko:anko-support-v4:$anko_version" // ConstraintLayout implementation "org.jetbrains.anko:anko-constraint-layout:$anko_version"
      
      





とりわけ、ライブラリはさまざまなリスナーのより便利な実装を可能にします。 リポジトリからの小さな例:







 seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar) = Unit })
      
      





そして今、アンコを使用して







 seekBar { onSeekBarChangeListener { onProgressChanged { seekBar, progress, fromUser -> // do something } } }
      
      





また、一部のリスナーはコルーチンをサポートしています。







  verticalLayout{ val anyCoroutineContext = GlobalScope.coroutineContext onClick(anyCoroutineContext) { //this: CoroutineScope } }
      
      





あんこコルーチン



メモリリークの影響を受けやすいオブジェクトを安全に転送するには、 asReference



メソッドをasReference



ます。 WeakReferenceに基づいており、 Refオブジェクトを返します





 verticalLayout{ val activity = ui.owner val activityReference: Ref<AppActivity> = activity.asReference() onClick(anyCoroutineContext) { ref().doSomething() } }
      
      





標準のViewPager.OnPageChangeListenerでコルチンのサポートを追加するとします。 シークバーの例と同じくらいクールにしましょう。

最初に、別個のクラスを作成し、 ViewPager.OnPageChangeListenerから継承します







 class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { }
      
      





ViewPager.OnPageChangeListenerによって呼び出される変数にラムダを格納します。







  private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null
      
      





これらの変数の1つに初期化を実装します(残りは同じ方法で行われます)







  fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action }
      
      





そして最後に、同じ名前の関数を実装します。







  override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } }
      
      





拡張機能を追加して機能させることは残っています







 fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
      
      





そしてViewPagerの下にこの全体を貼り付けます







 viewPager { onPageChangeListenerCoroutines { onPageScrolled { position, offset, pixels, coroutineContext -> //  -   . } } }
      
      





完全なコードはこちら
 class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action } fun onPageScrolled(action: ((Int, Float, Int, CoroutineContext) -> Unit)?) { onPageScrolled = action } fun onPageSelected(action: ((Int, CoroutineContext) -> Unit)?) { onPageSelected = action } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { GlobalScope.launch(coroutineContext) { onPageScrolled?.invoke(position, positionOffset, positionOffsetPixels, coroutineContext) } } override fun onPageSelected(position: Int) { GlobalScope.launch(coroutineContext) { onPageSelected?.invoke(position, coroutineContext) } } override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } } } fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
      
      





また、Anko Layoutsライブラリには、 さまざまなメトリックへの変換など、多くの便利なメソッドがあります












All Articles