使用Anko布局和Anko协程创建Android应用程序

图片


大约一年前,我开始在Android项目中使用Kotlin。 我想尝试一些有趣的新东西。 然后我遇到了安科 。 到那时,用xml编写UI相当枯燥。 我一直喜欢用手编写界面,而无需借助所见即所得和Android Studio中使用的xml标记。 唯一的缺点是您将必须重新启动应用程序以检查是否有任何更改。 您可以使用一个插件来显示ui的外观,而无需启动该应用程序,但这对我来说似乎很奇怪。 他还具有将xml转换为Anko Layouts DSL的出色能力。


该库的最大缺点是几乎完全缺少文档。 为了弄清楚如何正确使用它,我经常不得不查看源代码。 在本文中,将详细讨论使用Anko Layouts和Anko Coroutines进行应用程序开发。


Anko库本身分为4个独立的部分:


  • Anko Layouts-建立UI。
  • Anko Commons-有用的工具和功能。
  • Anko SQLite-使用SQLite数据库。
  • Anko协程是使用协程的有用工具。

要将库添加到项目,只需根据项目添加一行:


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


其中anko_version是该库的当前版本,在项目级别的build.gradle文件中注册:


ext.anko_version='0.10.8'


Anko布局


Anko Layouts使您可以比使用Java更有效地开发UI Android应用程序。


该领域的主要参与者是AnkoComponent<T>接口,该接口带有一个接受AnkoContext<T>并返回View的单个createView方法。 正是通过这种方法创建了整个UI。 AnkoContext<T>接口是ViewManager的包装。 关于它的更多信息将在以后。


了解AnkoComponent<T> ,让我们尝试在Activity类中创建一个简单的UI。 值得说明的是,只有在Activity中才可以实现UI的这种“直接”拼写,因为为此编写了单独的扩展函数ankoView ,其中调用addView方法,并使用setContentView = true参数在方法本身中创建了AnkoContextImpl<T>


 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 } } } } 

现在,为了将我们的用户界面传递给我们的活动, 您可以使用


 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>三个主要方法。 我们将更详细地分析它们。


创造


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

和他的双胞胎兄弟


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

返回AnkoContextImpl<T>


在所有标准情况下都使用方法,例如,在前面的带有Activity和Fragment的示例中。 这里最有趣的是owner和setContentView选项。 第一个允许您将特定的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 //     } } 

如果Context 是Activity或ContextWrapper的实例,则第二个参数setContentView将自动尝试添加结果视图。 如果他不成功,则将抛出IllegalStateException。


createDelegate


这种方法可能非常有用,但是有关gihab官方文档并未对此作任何说明。 当我解决过大的UI类问题时,在源代码中遇到了它:


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

它允许您将createView组件的结果添加到所有者。


以它的使用为例。 假设我们有一个描述应用程序屏幕之一的大类-AppFragmentUi。


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

从逻辑上讲,它可以分为两部分-工具栏和内容,分别为AppFragmentUiToolbar和AppFragmentUiContent。 然后我们的主类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") } } 

在标准实现中,如果设置了根视图, 则尝试并行设置第二个视图将抛出异常


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)


  • factory-将输入传递给Context并返回View的函数。 实际上,正是在其中创建View的工厂。
  • 主题是一种样式资源,将应用于当前视图。
  • init是一项功能,其中将为创建的视图设置必要的参数。

为您的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() } 

值得注意的是,这并不适用于所有View-通常在包装器中声明所有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 }) 

现在使用Anko


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

此外,一些侦听器支持协程:


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

安科协程


为了安全地传输内存泄漏敏感对象,请使用asReference方法。 它基于WeakReference并返回Ref对象

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

假设您想在标准ViewPager.OnPageChangeListener中添加对corutin的支持。 让我们像搜索栏示例一样酷。
首先,创建一个单独的类并从ViewPager.OnPageChangeListener继承。


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

我们将lambda存储在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 } 

最后,我们实现一个具有相同名称的函数。


  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库中,有许多有用的方法,例如转换为各种度量

Source: https://habr.com/ru/post/zh-CN442440/


All Articles