I am trying to add chips into ChipGroup(not singleLine):
val chip = Chip(this)
chip.isCloseIconVisible = true
for (i in 0..10) {
chip.setText("Some text $i")
chip_group.addView(chip as View)
}
But I get an exception:
The specified child already has a parent. You must call removeView() on the child's parent first.
How can I mark chip as a unique child? Or what should I do?
You need to declare chip inside for loop
for (i in 0..10) {
val chip = Chip(this)
chip.isCloseIconVisible = true
chip.setText("Some text $i")
chip_group.addView(chip as View)
}
Since you declare it as a val (which means not changed it is value), you are getting the same child error.
You cant add the same view more than once to parent.
Refer this :
https://stackoverflow.com/a/24032857
It throws the above mentioned exception.
You are trying to add the same Chip to the parent more than once.
Try modified code like,
for (i in 0..10) {
val chip = Chip(this)
chip.isCloseIconVisible = true
chip.setText("Some text $i")
chip_group.addView(chip as View)
}
Related
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.
I'm trying to write a simple Android application in Kotlin that changes the colors of different Views.
I have the following code to initialize views and click listeners (private lateinit declarations earlier of course):
private fun setListeners()
{
boxOneText = findViewById(R.id.box_one_text)
boxTwoText = findViewById(R.id.box_two_text)
boxThreeText = findViewById(R.id.box_three_text)
boxFourText = findViewById(R.id.box_four_text)
boxFiveText = findViewById(R.id.box_five_text)
btnRed = findViewById(R.id.button_red)
btnYellow = findViewById(R.id.button_yellow)
btnBlue = findViewById(R.id.button_blue)
val rootLayout = findViewById<View>(R.id.constraint_layout)
// Set click listener for all text views
val views = listOf<View>(boxOneText, boxTwoText, boxThreeText, boxFourText, boxFiveText, rootLayout)
views.map { view -> view.setOnClickListener { makeColored(it) } }
// Set click listener for all buttons
val buttons = listOf(btnRed, btnYellow, btnBlue)
buttons.map { button -> button.setOnClickListener { makeSpecificColor(it, views) } }
}
The code to set the colors of individual TextViews looks like this, and works (disregard the lockdown_earth entry for box_two, I was playing around with stuff):
private fun makeColored(view: View)
{
when(view.id)
{
R.id.box_one_text -> view.setBackgroundColor(Color.DKGRAY)
R.id.box_two_text -> view.setBackgroundResource(R.drawable.lockdown_earth)
R.id.box_three_text -> view.setBackgroundColor(Color.BLUE)
R.id.box_four_text -> view.setBackgroundColor(Color.MAGENTA)
R.id.box_five_text -> view.setBackgroundColor(Color.BLUE)
else -> view.setBackgroundColor(Color.LTGRAY)
}
}
However, the following code to change the color of all TextViews (including the rootLayout) does not work:
// In this context, view is a Button
private fun makeSpecificColor(view: View, views: List<View>)
{
// TODO For some reason the id doesn't match??
val color = when(view.id)
{
R.id.button_yellow -> R.color.my_yellow
R.id.button_red -> R.color.my_red
R.id.button_blue -> R.color.my_blue
else -> R.color.colorPrimary
}
views.map { it.setBackgroundColor(color) }
}
As alluded to by my TODO, for some reason the view id is NOT what it was before the clickListener was triggered, and the 'else' is always entered. I have run this in debug mode and the passed ID doesn't match any of the Views I have in the entire app. Is a new ID being assigned somehow? If so, why do the TextViews work with their listener?
Is it due to my usage of map on the buttons list? I have been kinda blindly using that as a replacement for a for loop.
It turned out the issue was that I was using the wrong method to set the background color. Instead of using setBackgroundColor() I needed to use setBackgroundResource().
It's unfortunate the debugger doesn't resolve resource IDs correctly because it sent me down a rabbit hole trying to fix the wrong problem.
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).
I am implementing a selection mode in ExpandableListView. The selection toggles when I click a child. I have a CheckBox in each parent, from which I want to control the selection of all the children at once.
My problem is that when the parent is collapsed and I click its CheckBox, the app crashes due to null pointer exception because when I try to change the selection of the children, I can't find the children and get null. But everything works fine when the parent is expanded.
So, what is a good approach to tackle such kind of problem?
I solved by adding some lines of code without changing the previous code, so this answer may be helpful for someone who doesn't want to rewrite the code with a different approach.
In the calling Fragment or Activity, where the Adapter is being set, add:
private val isMyGroupExpanded = SparseBooleanArray()
val MyAdapterEV = AdapterEV(/* params */) { isChecked, groupPosition ->
changeSelection(isChecked, groupPosition)
}
// record which groups are expanded and which are not
MyAdapterEV.setOnGroupExpandListener { i -> isMyGroupExpanded.put(i, true) }
MyAdapterEV.setOnGroupCollapseListener { i -> isMyGroupExpanded.put(i, false) }
// and where changing the selection of child
private fun changeSelection(isChecked: Boolean, groupPosition: Int) {
if (isMyGroupExpanded.get(groupPosition, false)) {
/* change only if the group is expanded */
}
}
So, the children of the collapsed group are not affected, but they are needed to be changed when the group expands, for that, add some lines of code in the Adapter:
private val isGroupChecked = SparseBooleanArray()
// inside override fun getGroupView(...
MyCheckBoxView.setOnCheckedChangeListener { _, isChecked ->
onCheckedChangeListener(isChecked, groupPosition)
isGroupChecked.put(groupPosition, isChecked)
}
// inside override fun getChildView(...
if (isGroupChecked.contains(groupPosition)) {
myView.visibility = if (isGroupChecked.get(groupPosition)) View.VISIBLE else View.INVISIBLE
}
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