Horizontal LinearLayout in Anko - android

What is a good way to do a horizontalLayout in anko / kotlin ? verticalLayout works fine - could set orientation on it but it feels wrong. Not sure what I am missing there.

Just use a linearLayout() function instead.
linearLayout {
button("Some button")
button("Another button")
}

Yeah, LinearLayout is by default horizontal, but I tend to be extra specific and rather use a separate horizontalLayout function for that.
You can simply add the horizontalLayout function to your project:
val HORIZONTAL_LAYOUT_FACTORY = { ctx: Context ->
val view = _LinearLayout(ctx)
view.orientation = LinearLayout.HORIZONTAL
view
}
inline fun ViewManager.horizontalLayout(#StyleRes theme: Int = 0, init: _LinearLayout.() -> Unit): _LinearLayout {
return ankoView(HORIZONTAL_LAYOUT_FACTORY, theme, init)
}
I have opened a feature request at Anko: https://github.com/Kotlin/anko/issues/413

Related

How to incorporate a linearlaout xml file in a compose file?

I'm trying to use Robinhood Spark in my app but my entire UI is built using jetpack compose. I'm going through these docs but it's only mentioned how you can use XML resource files like String, Dimensions, Colors, Images, Vector drawbles, Icons, and Fonts. I don't know which one Spark falls under. Looking at the XML code it's a LinearLayout but I can't see how that falls under any of the resource types I've mentioned prior. So for now I'm going through non-compose documentations to see if there is some kind of class or method I can use but would appreciate some feedback and help.
It is as nglauber mentioned, but you would need to define layout parameters that will fit into your compose view as well.
More about it https://foso.github.io/Jetpack-Compose-Playground/viewinterop/androidview/
AndroidView(factory = { ctx ->
val view = LayoutInflater.from(ctx)
.inflate(R.layout.activity_sample, null, false)
.apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
// Use your view as usual...
view
}, update = {
// here update your view or get element from it and update
})
You can use AndroidView to load your XML file.
AndroidView(
factory = { context: Context ->
val view = LayoutInflater.from(context)
.inflate(R.layout.your_layout, null, false)
// Use your view as usual...
view // return the view
},
update = { view ->
// Update view if needed
}
)

Does Compose ConstraintLayout provide same handling for INVISIBLE and GONE as view based ConstraintLayout?

Disclaimer: I know that Compose just entered alpha01 and thus I do not expect that every functionality
is available. However, layout and handling of specific layout cases is IMHO an important topic
that should be addressed early 😉.
The current view based ConstraintLayout provides some specific handling in case a child view is
marked as GONE, refer to the
ConstrainLayout documentation.
I checked the Compose ConstraintLayout documentation, the available modifiers and so on, however
did not find anything that points in this direction. I also could not find any hint regarding
INVISIBLE and how/if Compose ConstraintLayout handles it like the view based ConstraintLayout.
In general, the current view based layouts (LinearLayout for example) handle INVISIBLE and GONE in a
similar fashion:
if a view is in state INVISIBLE then the view is part of the layout with its sizes, just not
shown. The overall layout of other views does not change and they stay in their positions.
if a view is in state GONE its sizes a usually treated as 0 and the layout is recomputed and
changed, other views usually change their positions.
Here a simple Compose ConstraintLayout UI, just 4 buttons in a row, chained to have them nicely
spread.
// if dontShow is 0 then show all buttons, otherwise make the button with this number
// somehow INVISIBLE. This feature is not yet implemented.
#Composable
fun fourButtonsCL(dontShow: Int) {
ConstraintLayout(Modifier.fillMaxSize()) {
val (btn1, btn2, btn3, btn4) = createRefs()
TextButton(onClick = {}, Modifier.constrainAs(btn1) {}.background(teal200)) { Text("Button1") }
TextButton(onClick = {}, Modifier.constrainAs(btn2) {}.background(teal200)) { Text("Button2") }
TextButton(onClick = {}, Modifier.constrainAs(btn3) {}.background(teal200)) { Text("Button3") }
TextButton(onClick = {}, Modifier.constrainAs(btn4) {}.background(teal200)) { Text("Button4") }
createHorizontalChain(btn1, btn2, btn3, btn4)
}
}
#Preview(showBackground = true)
#Composable
fun previewThreeButtons() {
ComposeAppTheme {
fourButtonsCL()
}
}
Assume I would like to make Button3 invisible, but keep the other 3 buttons positioned where they
are. Thus just a hole between Button2 and Button4. How to achieve this without creating yet another
Composable or adding additional logic. While the logic in this simple case may be just a view lines
of code, more complex layouts would the need some more complex logic. In veiw based ConstraintLayout
wwe just need to modify the child view.
The other assumption: make Button3 disappear completely from the layout (GONE) and re-compute the
layout, remaining buttons become wider and evenly spread out. At a first glance this looks simple,
and in this very simple example it maybe easy. However in more complex layouts this could require
some or even a lot of re-wiring of constraints of the embedded Composables.
Thus the question is:
how does compose handles these cases for Column and Row layouts (like in view based LinearLayout)
and for ConstraintLayout in particular? However with the following restriction 😉: without defining
many new Composables and/or without adding complex layout logic inside the Composables (re-wiring
constraints for example).
Did I miss some modifier? Is this even planned or possible in the Composable layouts? What would be
the preferred way to solve such layout cases in Compose?
Based on #CommonsWare comment to the question I could solve the INVISIBLE
option, see code below.
Currently (in alpha-01) the implementation of ConstraintLayout seems to be incomplete, at least a few TODO comments in the code indicate this. This
seems to include the yet missing support of the GONE feature.
I saw some of these:
// TODO(popam, b/158069248): add parameter for gone margin
Also the chain feature does not yet perform layout rendering in the same way as
in the view based ConstraintLayout.
object FourElementsNoDSL {
const val elementA = "ElementA"
const val elementB = "ElementB"
const val elementC = "ElementC"
const val elementD = "ElementD"
private val noDSLConstraintSet = ConstraintSet {
// Create references with defines ids, here using a string as id. Could be an Int as well,
// actually it's defined as 'Any'
val elemA = createRefFor(elementA)
val elemB = createRefFor(elementB)
val elemC = createRefFor(elementC)
val elemD = createRefFor(elementD)
// Simple chain only. Instead of this simple chain we can use (for example):
// constrain(elemA) {start.linkTo(parent.start) }
// to set a constraint as known in XML
// constrain(elemA) {start.linkTo(parent.start, 16.dp) }
// constrain(elemB) {start.linkTo(elemA.end) }
// constrain(elemC) {start.linkTo(elemB.end) }
// constrain(elemD) {end.linkTo(parent.end) }
createHorizontalChain(elemA, elemB, elemC, elemD)
}
#Composable
fun fourButtonsCLNoDSL(doNotShow: List<String>) {
ConstraintLayout(constraintSet = noDSLConstraintSet, modifier = Modifier.fillMaxSize()) {
// This block contains the children
Text(text = "A",
modifier = Modifier.layoutId(elementA)
.drawOpacity(if (doNotShow.contains(elementA)) 0f else 1f)
.padding(0.dp),
style = TextStyle(fontSize = 20.sp)
)
Text(text = "B",
modifier = Modifier.layoutId(elementB)
.drawOpacity(if (doNotShow.contains(elementB)) 0f else 1f)
.padding(0.dp),
style = TextStyle(fontSize = 20.sp)
)
Text(text = "C",
modifier = Modifier.layoutId(elementC)
.drawOpacity(if (doNotShow.contains(elementC)) 0f else 1f)
.padding(0.dp),
style = TextStyle(fontSize = 20.sp)
)
Text(text = "D",
modifier = Modifier.layoutId(elementD)
.drawOpacity(if (doNotShow.contains(elementD)) 0f else 1f)
.padding(0.dp),
style = TextStyle(fontSize = 20.sp))
}
}
}
#Preview(showBackground = true)
#Composable
fun previewFourFieldsNoDSL() {
val noShow = listOf(FourElementsNoDSL.elementC)
PlaygroundTheme {
FourElementsNoDSL.fourButtonsCLNoDSL(noShow)
}
}
The object FourElementsNoDSL defines the layout, provides element ids and so on.
This is roughly comparable to an XML file that contains such layout.
noDSL means, that this layout does not use the Compose ConstraintLayout's Kotlin
DSL. Currently the DSL does not provide a mechanism to setup element references
(used in layoutId) with defined ids as its done in this example.

Android - Scroll to Selected Child Position in ChipGroup

I am trying to make An Activity with 4 Chips in a Horizontal Scrolling ChipGroup.
This Activity is using the ChipGroup as a filter for API call, and can have Extras telling which Chip was selected by user.
Currently I'm doing it like :
chipScroll.postDelayed(() -> {
chipScroll.smoothScrollBy(800, 0);
},100);
But this is quite a hacky solution to what I want to achieve.
Is there any other way to scroll to selected Chip in a Horizontal Scrolling ChipGroup?
Edit :
I've thought of trying to iteratively get all the Chips in the ChipGroup and match its IDs. But it seems hacky too. Something like spinnerAdapter.getItem(spinner.getSelectedItemPosition) is what I'm aiming for
I found the answer here.
I forgot that ChipGroup is basically just a ViewGroup, and HorizontalScrollView is just an extra for its scrolling purpose.
I could just do something like :
chipGroup.post(() -> {
Chip chipByTag = chipGroup.findViewWithTag(filterModel.getComplaint_status());
chipGroup.check(chipByTag.getId());
chipScroll.smoothScrollTo(chipByTag.getLeft() - chipByTag.getPaddingLeft(), chipByTag.getTop());
});
Doing this in onCreate would crash as the Tag isn't assigned yet, and I'm using DataBinding for the tag in XML (CMIIW), hence, we should do it in a .post() runnable.
For Kotlin: Using #Kevin Murvie answer
binding.layoutChipChoiceProducts.chipGroupDrinksChoice.post {
val chip =
binding.layoutChipChoiceProducts.chipGroupDrinksChoice.findViewWithTag<Chip>(tag)
binding.layoutChipChoiceProducts.scrollLayoutChip.scrollTo(
chip.left - chip.paddingLeft, chip.top
)
}
You should wrap your ChipGroup with the HorizontalScrollView, and just call this extension function after the chip selection:
inline fun <reified T : View> HorizontalScrollView.scrollToPosition(
tag: String?,
) {
val view = findViewWithTag<T>(tag) ?: return
val leftEdgePx = view.left
val screenCenterPx = Resources.getSystem().displayMetrics.widthPixels / 2
val scrollPx = if (leftEdgePx < screenCenterPx) 0
else leftEdgePx - screenCenterPx + view.width / 2
this.post {
this.smoothScrollTo(scrollPx, view.top)
}
}
This will always show selected chip centered on the screen (if it's possible).

How to create a View extension function in Kotlin

I'm trying to create an extension function in Kotlin. I did try several tutorials, but didn't quite understand, how to implement this one.
I'm trying to create a setWidth() function as such
//Find my_view in the fragment
val myView = v.findViewById<RelativeLayout>(R.id.my_view)
//Then use the extension function
myView.setNewWidth(500)
This is how I've defined my extension function
private fun View?.setNewWidth(i: Int) {
val layoutParams: ViewGroup.LayoutParams = View.layoutParams
layoutParams.width = i
View.layoutParams = layoutParams
}
I don't understand what I need to do here.
I want to call the extension function as myView.ExtensionFunction(), but I don't know how to do that. The tutorials, were un-informative.
I think the main problem here is how the extension function is defined, in particular, the lines that have View.layoutParams - this is calling a static property on View that doesn't exist. You need to use the one from the instance. If you'd write the extension function like so:
private fun View?.setNewWidth(i: Int) {
val layoutParams = this?.layoutParams
layoutParams?.width = i
this?.layoutParams = layoutParams
}
Then you can call the method like you want. Personally, I don't find this so readable and I'd remove the nullability here and write it as:
private fun View.setNewWidth(i: Int) {
val newLayoutParams = layoutParams
newLayoutParams?.width = i
layoutParams = newLayoutParams
}
The only difference is that now you need ?. to call the method if the view is nullable, which I personally find fine - myView?.setNewWidth(123). I assume most of the time you won't have a nullable view.
Ok, So my issue was that I didn't know how to get reference to the calling View. i.e., I didn't know how to call myView and set its property inside the extension function setNewWidth()
So, I tried using this? and it worked.
Then, I did a few changes to the extension function to work for myView which is a Relative Layout.
This is what I worked out:
private fun RelativeLayout?.setWidth(i: Int) {
val layoutParams: ViewGroup.LayoutParams? = this?.layoutParams
layoutParams?.width = i
this?.layoutParams = layoutParams
}

Is it possible to reuse a layout in Kotlin Anko

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

Categories

Resources