
大约一年前,我开始在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
从逻辑上讲,它可以分为两部分-工具栏和内容,分别为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
请注意,不是ui,而是ui.owner作为对象传递给with
函数。
因此,我们有以下算法:
- 组件实例已创建。
- createView方法创建要添加的视图。
- 结果视图添加到所有者。
更加近似: this.addView(AppFragmentUiToolbar().createView(...))
如您所见,createDelegate的选项更具可读性。
createReusable
看起来像标准的AnkoContext.create,但有一点点增加-最新视图被认为是根视图:
class MyComponent: AnkoComponent<MyObject> { override fun createView(ui: AnkoContext<MyObject>): View = with(ui) { textView("Some text")
在标准实现中,如果设置了根视图, 则尝试并行设置第二个视图将抛出异常 。
createReusable方法返回ReusableAnkoContext类,该类继承自AnkoContextImpl并覆盖alreadyHasView()
方法。
自定义视图
幸运的是,Anko Layouts不限于此功能。 如果我们需要显示自己的CustomView,则无需编写
verticalLayout { val view = CustomView(context)
为此,您可以添加自己的包装器,该包装器将执行相同的操作。
这里的主要组件是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方法。 例如, _RelativeLayout是RelativeLayout的包装。 因此,对于所有人。
幸运的是,已经为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) {
安科协程
为了安全地传输内存泄漏敏感对象,请使用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库中,有许多有用的方法,例如转换为各种度量 。