How to add custom view groups to Anko DSL?
Asked Answered
P

4

8

Anko docs tell us how to add custom views to DSL. But if my custom view is a view group, problems arise.

class MyFrameLayout(context: Context) : FrameLayout(context)

fun ViewManager.myFrameLayout(init: MyFrameLayout.() -> Unit = {}) = ankoView({ MyFrameLayout(it) }, init)

class MyUI : AnkoComponent<Fragment> {
    override fun createView(ui: AnkoContext<Fragment>) = with(ui) {

        myFrameLayout {
            textView("hello").lparams { // error: Unresolved reference: lparams
                bottomMargin = dip(40)
            }
        }
    }
}

but if I change myFrameLayout invocation to frameLayout it works OK. So what's the proper way to make view groups be used with Anko DSL?

Paleobiology answered 26/2, 2016 at 12:32 Comment(0)
P
0

If we take a look at Anko sources we can see that frameLayout actualy returns an instance of _FrameLayout class, where these lparams functions are defined. In my understanding this is needed, so these lparams functions are only available in the layout building code.

In Anko's Layouts.kt file there are these _<ViewGroup> classes for every supported ViewGroup.

So the straightforward way to support a custom view group is to create a _<ViewGroup> class with lparams methods implementation. The problem is that this _<ViewGroup> class often contains much more code than my <ViewGroup> itself!

And if I want create many custom view groups, adding Anko support for them becomes a big pain with this approach. Let's say I have MyFrameLayout1, MyFrameLayout2, MyFrameLayout3 classes. They are basically FrameLayout's so I want the same layout parameters to be used with them. But I have to create _FrameLayout1, _FrameLayout2, _FrameLaoyt3 classes which are just copy/paste of Anko's _FrameLayout.

So I slightly improved this approach. I create an interface _FrameLayout:

interface _FrameLayout {
    // copy/paste from Anko's _FrameLayout
}

and now to support any custom FrameLayout subclass I just have to:

class _MyFrameLayout(ctx: Context) : MyFrameLayout(ctx), _FrameLayout

fun ViewManager.myFrameLayout(init: _MyFrameLayout.() -> Unit = {})= ankoView({ _MyFrameLayout(it) }, init) 

This reduces copy/paste a lot, when creating many custom view groups.

Paleobiology answered 1/3, 2016 at 14:42 Comment(0)
G
6

Actually you just have to extend anko and declare your customview then use it in the DSL normally:

public inline fun ViewManager.customView() = customView {}
public inline fun ViewManager.customView(init: CustomView.() -> Unit) = ankoView({ CustomView(it) }, init)

Then use it in the DSL normally

frameLayout {
    customView()
}
Gertrudis answered 21/3, 2016 at 16:27 Comment(0)
U
2

if you inherit from e.g. _RelativeLayout instead of RelativeLayout, you can use your custom layout as you would expect.

Understudy answered 20/9, 2017 at 14:59 Comment(0)
M
0

If you go to any of Anko's lparams declarations from your code, you can see that inside Anko generated DSL code, lparams is an extension function for T: View which looks like this:

fun <T: View> T.lparams(
        width: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        height: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        init: android.widget.FrameLayout.LayoutParams.() -> Unit = defaultInit
): T {
    val layoutParams = android.widget.FrameLayout.LayoutParams(width, height)
    layoutParams.init()
    [email protected] = layoutParams
    return this
}

(and more overloads for different LayoutParams constructors)

It is declared inside a class, so it is only visible in functions with a receiver of that class, see another question about this method of DSL programming.


In order to be able to use lparams for your custom ViewGroup in Anko DSL, you have to declare a similar function or functions inside your custom view code, that will create an appropriate LayoutParams for your class.

If you want also to hide lparams function from outside the DSL, you can make a subclass of your MyFrameLayout and use it in DSL code only, working with MyFrameLayout itself elsewhere.

After that, you can call lparams on any View inside a lambda which you pass as init: MyFrameLayout.() -> Unit to fun ViewManager.myFrameLayout.

Mir answered 29/2, 2016 at 8:31 Comment(0)
P
0

If we take a look at Anko sources we can see that frameLayout actualy returns an instance of _FrameLayout class, where these lparams functions are defined. In my understanding this is needed, so these lparams functions are only available in the layout building code.

In Anko's Layouts.kt file there are these _<ViewGroup> classes for every supported ViewGroup.

So the straightforward way to support a custom view group is to create a _<ViewGroup> class with lparams methods implementation. The problem is that this _<ViewGroup> class often contains much more code than my <ViewGroup> itself!

And if I want create many custom view groups, adding Anko support for them becomes a big pain with this approach. Let's say I have MyFrameLayout1, MyFrameLayout2, MyFrameLayout3 classes. They are basically FrameLayout's so I want the same layout parameters to be used with them. But I have to create _FrameLayout1, _FrameLayout2, _FrameLaoyt3 classes which are just copy/paste of Anko's _FrameLayout.

So I slightly improved this approach. I create an interface _FrameLayout:

interface _FrameLayout {
    // copy/paste from Anko's _FrameLayout
}

and now to support any custom FrameLayout subclass I just have to:

class _MyFrameLayout(ctx: Context) : MyFrameLayout(ctx), _FrameLayout

fun ViewManager.myFrameLayout(init: _MyFrameLayout.() -> Unit = {})= ankoView({ _MyFrameLayout(it) }, init) 

This reduces copy/paste a lot, when creating many custom view groups.

Paleobiology answered 1/3, 2016 at 14:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.