【问题标题】:Accessing views from the Activity with Anko使用 Anko 从 Activity 访问视图
【发布时间】:2016-12-07 14:42:55
【问题描述】:

我知道我可以使用 Anko 的 id 属性来识别视图:

class MainActivityUI : AnkoComponent<MainActivity> {

    override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
        frameLayout {
            textView {
                id = R.id.text
            }
        }
    }

}

然后使用find()函数(或使用Kotlin Android Extensions)在Activity中获取它:

class MainActivity : AppCompatActivity() {

    private val textView by lazy {
        find<TextView>(R.id.text)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        MainActivityUI().setContentView(this)

        textView.text = "Hello World"
    }

}

但我觉得我错过了什么; README 提到 find 函数或 Kotlin Android 扩展的唯一地方是在标题为支持现有代码的部分中:

您不必使用 Anko 重写所有 UI。你可以保持你的旧 用 Java 编写的类。此外,如果您仍然想要(或必须) 编写一个 Kotlin 活动类并为一些扩展 XML 布局 原因,您可以使用 View 属性,这会使事情变得更容易:

// Same as findViewById(), simpler to use
val name = find<TextView>(R.id.name)
name.hint = "Enter your name"
name.onClick { /*do something*/ }

您可以使用 Kotlin Android 使您的代码更加紧凑 扩展。

这使得find 函数看起来仅用于支持“旧”XML 代码。

所以我的问题是这样的;使用idfind 函数是使用Anko 从Activity 访问View 的正确方法吗?有没有更“Anko”的方式来处理这个问题?还是我错过了 Anko 的其他一些好处,使得从 Activity 访问 View 无关紧要?


还有第二个相关问题;如果这Activity 访问View 的正确方法,有没有办法从AnkoComponent 中创建id 资源(即"@+id/")?而不是在ids.xml 文件中创建每个id

【问题讨论】:

    标签: android kotlin anko


    【解决方案1】:

    那么,为什么还要使用XML id 来定位视图呢?因为我们已经使用 Anko 而不是 XML。

    在我看来,我们可以将视图元素存储在AnkoComponent 中,而不是find view 的id 方法中。检查代码:

    class MainActivityUI : AnkoComponent<MainActivity> {
    
        lateinit var txtView: TextView
    
        override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
            frameLayout {
                txtView = textView {
                    id = R.id.text // the id here is useless, we can delete this line.
                }
            }
        }
    
    }
    
    class MainActivity : AppCompatActivity() {
    
        lateinit var mainUI : MainActivityUI
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            mainUI = MainActivityUI()
            mainUI.setContentView(this)
    
            mainUI.txtView.text = "Hello World"
        }
    
    }
    

    【讨论】:

    • 我也遇到过这个;但我确实注意到了一个明显的缺点。例如,如果您想要布局的可重用部分(XML 中的 &lt;include&gt; 标记),据我了解,您必须创建一个单独的 AnkoComponent,并将其用作其他组件中的视图。在这种情况下,每个View 成员都将附加到它所在的组件;而不是整个视图。在这种情况下,您必须使用 mainUI.toolbarUI.toolbar 来访问重用视图;而find() 可以访问层次结构中的任何视图。
    【解决方案2】:

    不要使用 id 来识别 Anko DSL 的视图!这是不必要和无用的,因为 Anko 旨在摆脱 XML 布局。而是使用这种模式:

    class ActivityMain : AppCompatActivity() {
    
        var mTextView: TextView  // put it here to be accessible everywhere
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ActivityMainUI().setContentView(this)
        }
    
        fun yourClassMethod() {
            // So this is am example how to get the textView 
            // defined in your Anko DSL class (not by id!):
            mTextView.text = "bla-bla-bla"  
        }
    
    }
    
    class ActivityMainUI : AnkoComponent<ActivityMain> {
    
        override fun createView(ui: AnkoContext<ActivityMain>) = with(ui) {
    
            // your fancy interface with Anko DSL:
            verticalLayout {
                owner.mTextView = textView
            }
        }
    }
    

    请注意 UI 类定义:

    class ActivityMainUI : AnkoComponent<ActivityMain> {
    

    如果您将活动类名称放在括号中,那么它的所有公共变量都可以通过 UI 类主体中的 owner 访问,因此您可以在那里分配它们。
    但是您可以轻松地放置 AppCompatActivity 并制作一些可能被克隆的通用类。在这种情况下,使用 lateinit var mTextView : UI 类正文中的 TextView,如 Jacob 在此处的回答中所述。

    【讨论】:

    • ? 展示了owner 的使用。在阅读了 5 个小时关于 Anko 的文章(当然是 Kotlin 新手)之后,这是我发现的第一次提到它。
    【解决方案3】:

    我相信,由于您可以向 Anko 文件添加行为,因此您根本不必在活动中实例化您的视图。

    这真的很酷,因为您可以进一步分离视图层。在您的视图中起作用的所有代码都可以插入到 Anko 文件中。所以你所要做的就是从 Anko 调用你的活动方法而不是实例化任何视图。

    但如果您需要实例化任何视图...您可以在 Activity 中使用 Kotlin Android 扩展。

    示例:

    活动中的代码:

    seekBar.setOnSeekBarChangeListener(object: OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
            // Something
        }
        override fun onStartTrackingTouch(seekBar: SeekBar?) {
            // Just an empty method
        }
        override fun onStopTrackingTouch(seekBar: SeekBar) {
            // Another empty method
        }
    })
    

    Anko 中的代码:

    seekBar {
        onSeekBarChangeListener {
            onProgressChanged { seekBar, progress, fromUser ->
                // Something
            }
        }
    }
    

    现在代码在 AnkoComponent 中。无需实例化视图。

    结论:

    如果您将所有视图逻辑放在 AnkoComponents 中,而不是在您的活动中,这是一种更“Anko”的编程方式。

    编辑:

    作为无需实例化视图的代码示例:

    在你的 Anko 中:

    var networkFeedback : TextView = null
    
        override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
                frameLayout {
                    textView {
                        id = R.id.text2
                        networkFeedback = this
                        onClick {
                            ui.owner.doSomething(2, this)
                        }
                    }
                }
            }
    
    fun networkFeedback(text: String){
           networkFeedback.text = text
    }
    

    在你的活动中:

    class MainActivity : AppCompatActivity() {
    
        overriding fun onCreate{
                [...]
                val mainUi = AnkoUi()
                // some dynamic task...
                mainUi.networkFeedback("lalala")
         }
    
        fun doSomething(number: Int, callback: TextView){
                //Some network or database task goes here!
    
                //And then, if the operation was successful
    
                callback.text = "Something has changed..."
            }
    

    这是一种非常不同的方法。我不确定我是否喜欢它,但这是一个完全不同的讨论......

    【讨论】:

    • 我知道 Kotlin Android 扩展;但正如我在问题中提到的那样,README 的 Supporting Existing Code 部分似乎暗示这两种方法(Kotlin Android Extensions 和 find 函数)仅在支持 XML 代码时才是必需的。
    • 对不起。我的错。检查编辑的答案。也许有帮助
    • 我知道 Anko 还可以很容易地为 UI 事件添加监听器,例如单击或搜索栏更改;但是如果事件不是 UI 事件呢?举个简单的例子,你将如何使用它来基于动态值(即来自数据库或网络结果)而不是静态值来设置 TextView 中的文本?
    • 您应该只在 Anko 中添加 UI 事件的代码,否则您将混合视图和控制层,项目将一团糟。但是,是的,您可以根据动态值更改 TextView(或任何其他 UI 元素)的文本。以我编辑的答案为例。
    • 我认为这个解决方案并不能真正解决问题。您不能期望视图应该在单击时更新。如果应用程序一打开就需要通过网络调用刷新视图怎么办?在这种情况下,您不能调用doSomething(),因为您没有对TextView 的引用。或者一个更简单的例子;你会如何从另一个视图调用doSomething()(例如SwipeRefreshLayout)?
    【解决方案4】:

    稍微概括一下这个问题:如何制作一个封装好的 AnkoComponent,可以从 DSL 中使用,并且可以在创建后以编程方式设置其数据?

    这是我使用 View.tag 的方法:

    class MyComponent: AnkoComponent<Context> {
        lateinit var innerds: TextView
        override fun createView(ui: AnkoContext<Context>): View {
            val field = with(ui) {
                linearLayout {
                    innerds = complexView("hi")
                }
            }
            field.setTag(this) // store the component in the View
            return field
        }
    
        fun setData(o:SomeObject) { innerds.setStuff(o.getStuff()) }
    }
    
    inline fun ViewManager.myComponent(theme: Int = 0) = myComponent(theme) {}
    inline fun ViewManager.myComponent(theme: Int = 0, init: MyComponent.() -> Unit) = 
        ankoView({ MyComponent(it) }, theme, init)
    
    // And then you can use it anywhere the Anko DSL is used.
    class SomeUser : AnkoComponent<Context>
    {
        lateinit var instance:View 
        override fun createView(ui: AnkoContext<Context>): View {
            linearLayout {
                instance = myComponent {}
            }
        }
        fun onDataChange(o:SomeObject) {
            (instance.Tag as MyComponent).setData(o)
        }
    }
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-24
      • 1970-01-01
      • 2021-03-16
      • 1970-01-01
      • 1970-01-01
      • 2014-05-17
      • 1970-01-01
      相关资源
      最近更新 更多