Accessing views from the Activity with Anko

不问归期 提交于 2019-12-03 14:33:19

So, why still use XML id to locate the View? since we already use the Anko instead of the XML.

In my opinion, we can store the view elements inside the AnkoComponent instead of the find view's id method. Check the code blow:

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

}

Do not use id to identify views with Anko DSL! It is unnecessary and useless because Anko was designed to get rid off XML layouts. Instead use this pattern:

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

Please note the UI class definition:

class ActivityMainUI : AnkoComponent<ActivityMain> {

If you put there your activity class name in brackets then all its public variables become accessible via owner in UI class body so you can assing them there.
But you may put AppCompatActivity easily and make some universal class which might be cloned. In this case use lateinit var mTextView : TextView in the body of UI class as described in Jacob's answer here.

I believe that, as you can add behavior to your Anko files, you don't have to instantiate your views in the activity at all.

That can be really cool, because you can separate the view layer even more. All the code that acts in your views can be inserted in the Anko files. So all you have to do is to call your activity's methods from the Anko and not instantiate any view.

But if you need to instantiate any view... you can use Kotlin Android Extensions in your activity.

Exemple:

Code in your activity:

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

Code in Anko:

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

Now the code is in AnkoComponent. No need to instantiate the view.

Conclusion:

It's a more 'Anko' way to program if you put all your view logic in the AnkoComponents, not in your activities.

Edit:

As an exemple of a code where you don't have to instantiate a view:

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

In your activity:

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

This is a very different approach. I'm not so sure if I like it or not, but this is a whole different discussion...

To generalize the question a bit: How can one make an AnkoComponent that is encapsulated, can be used from the DSL and can have its data programmatically set after creation?

Here is how I did it using the 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)
    }
}

}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!