Is it possible to reuse a layout in Kotlin Anko

2020-06-04 02:46发布

问题:

I read that the most benefit of using Anko is its reusability. But i could't find its exact example.

Currently in the new Android layout system, the boiler plate is like below:

DrawerLayout (with some setup)
   CoordinatorLayout (with some setup)
      AppBarLayout (with some setup)
         ToolBar
      <The Main Content>
   NavigationView (with header inflated)

From the layout structure above, only <The Main Content> is varry. And in many cases those ceremonial setup duplicated almost in every activity.

So here with Anko im thinking if there is a reusable solution about that issue. Im not expecting it will be reusable for general purpose layout, but et least i can minimize the ceremonial code in the project. Maybe i need something like:

class MainUI: AnkoComponent<MainActivity> {
  override fun createView(ui: AnkoContext<MainActivity>): View{
     return with(ui) {
        myCustomRootLayout {
           //here is what <The Main Content> will be
        }
     }
  }
}

From the code above im expecting myCustomRootLayout will do all the ceremonial setup for the root layout such as (DrawerLayout, CoordinatorLayout etc etc).

Is that possible?

EDIT So i think my question is: How to make a custom component which can host other component

回答1:

One way to reuse the code is to simply extract myCustomRootLayout into a extension method like so:

class MainUI: AnkoComponent<MainActivity> {
    override fun createView(ui: AnkoContext<MainActivity>): View {
        return with(ui) {
            myCustomRootLayout {
               recyclerView()
            }
        }
    }
}

fun <T> AnkoContext<T>.myCustomRootLayout(customize: AnkoContext<T>.() -> Unit = {}): View {
    return relativeLayout {
        button("Hello")
        textView("myFriend")
        customize()
    }
}

However as stated in the documentation:

Although you can use the DSL directly (in onCreate() or everywhere else), without creating any extra classes, it is often convenient to have UI in the separate class. If you use the provided AnkoComponent interface, you also you get a DSL layout preview feature for free.

It seems to be a good idea to extract the reusable piece into separate AnkoComponent:

class MainUI : AnkoComponent<MainActivity> {
    override fun createView(ui: AnkoContext<MainActivity>): View {
        return with(ui) {
            MyCustomRootLayout<MainActivity>({
                recyclerView()
            }).createView(ui)
        }
    }
}


class MyCustomRootLayout<T : Context>(val customize: AnkoContext<T>.() -> Unit = {}) : AnkoComponent<T> {
    override fun createView(ui: AnkoContext<T>) = with(ui) {
        relativeLayout {
            button("Hello")
            textView("myFriend")
            customize()
        }
    }
}


回答2:

I actually found a way to do this, took me a while to figure it out.

I have a very basic test layout here, the content gets added to a RelativeLayout.

The key here is to add your custom layout in a delegated AnkoContext that delegates to the immediate parent (the RelativeLayout in my case).

abstract class BaseAnkoComponent<T> : AnkoComponent<T> {

    companion object {
        val TOOLBAR_ID = View.generateViewId()
        val COLLAPSING_ID = View.generateViewId()
        val COORDINATOR_ID = View.generateViewId()
        val APPBAR_ID = View.generateViewId()
        val CONTENT_ID = View.generateViewId()
    }

    abstract fun <T> AnkoContext<T>.content(ui: AnkoContext<T>): View?

    override fun createView(ui: AnkoContext<T>) = with(ui) {
        coordinatorLayout {
            id = COORDINATOR_ID
            lparams(matchParent, matchParent)
            appBarLayout(R.style.AppTheme_AppBarOverlay) {
                id = APPBAR_ID
                lparams(matchParent, wrapContent)
                fitsSystemWindows = true
                collapsingToolbarLayout {
                    id = COLLAPSING_ID
                    val collapsingToolbarLayoutParams = AppBarLayout.LayoutParams(matchParent, matchParent)
                    collapsingToolbarLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
                    layoutParams = collapsingToolbarLayoutParams
                    isTitleEnabled = false
                    toolbar {
                        id = TOOLBAR_ID
                        val toolbarLayoutParams = CollapsingToolbarLayout.LayoutParams(matchParent, dimenAttr(R.attr.actionBarSize))
                        toolbarLayoutParams.collapseMode = CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN
                        layoutParams = toolbarLayoutParams
                        minimumHeight = dimenAttr(R.attr.actionBarSize)
                        background = ColorDrawable(colorAttr(R.attr.colorPrimary))
                        popupTheme = R.style.AppTheme_PopupOverlay
                    }
                }
            }
            with(AnkoContext.createDelegate(relativeLayout {
                id = CONTENT_ID
                val relativeLayoutParams = CoordinatorLayout.LayoutParams(matchParent, matchParent)
                relativeLayoutParams.behavior = AppBarLayout.ScrollingViewBehavior()
                layoutParams = relativeLayoutParams
            })) {
                content(ui)
            }
        }
    }
}

And then you can extend the BaseAnkoComponent and build your content in the same way with Anko DSL.

class FooActivityUi : BaseAnkoComponent<FooActivity>() {
  override fun <T> AnkoContext<T>.content(): View? {
    return verticalLayout {
      lparams(width = matchParent, height = matchParent)
      button("Hello")
      textView("myFriend")
    }
  }
}

I am sure there is a better way to do this but I have not found it. Kinda new to Kotlin and Anko.