I have seen the code for adding buttons to a ConstraintLayout in an existing question, but how do I do that with a Flow? That is, I do not want to define the top/left of the button relative to the ConstraintView itself, but I want the buttons to be next to the previous one and wrap at the end of the line.
I have tried the following but no button was showing.
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val flow = findViewById<Flow>(R.id.flow);
val lay = flow.parent as ConstraintLayout;
val cs = ConstraintSet();
cs.clone(lay);
for (i in 1 .. 10)
{
val btn = Button(this)
btn.text = "Test ${i}";
btn.id = 49393+i;
flow.addView(btn)
cs.constrainWidth(btn.id, 400);
cs.constrainHeight(btn.id, 100);
cs.applyTo(lay);
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ScrollView
android:id="#+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/large_text"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
android:textIsSelectable="true" />
</ScrollView>
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/flow"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#FF0000"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
You haven't ever added the button to the layout. The Flow.addView method only adds it to the helper if it's already a child of the relevant ConstraintLayout.
From the documentation:
The referenced view need to be a child of the helper's parent.
Related
I'm triying to create a GridLayout by programmatically in Kotlin. My code for the XML is this:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#46FF4433"
tools:context=".MainActivity">
<androidx.gridlayout.widget.GridLayout
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:rowCount="5">
app:columnCount="3"
</androidx.gridlayout.widget.GridLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I want to set 15 buttons into my Grid. My mainActivity is this:
val gridLayout: GridLayout = findViewById(R.id.grid)
for (i in 1..10){
val button = Button(this)
button.text = "Boton: " + i
button.setBackgroundColor(Color.parseColor("#ffffff"));
gridLayout.addView(button)
}
But I need each button be like this:
<Button
android:id="#+id/button1"
app:layout_gravity="fill"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:text="Button 1"
android:background="#ffffff"
/>
I think it has something to do with the layout param but I don't know how to set those atributes.
Take a look at GridLayout.LayoutParams and GridLayout.Spec. You can do something like the following:
val button = Button(this)
val lp = GridLayout.LayoutParams()
lp.setGravity(Gravity.FILL)
// GridLayout.spec is immutable, so one can be applied more than once.
// There are several other versions of GridLayout.spec which may be applicable.
lp.rowSpec = GridLayout.spec(row, 1.0f) // row start, weight
lp.columnSpec = GridLayout.spec(col, 1.0f) // column start, weight
button.layoutParams = lp
I haven't tested this, but it should get your started.
I am using the constraintLayout with animations using TransitionManager.
I have the following 2 layouts
This is my main_activity.xml
<android.support.constraint.ConstraintLayout
android:id="#+id/constraintLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="160dp"
android:background="#color/colorPrimary"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<Button
android:id="#+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:text="Let's start animating"/>
</android.support.constraint.ConstraintLayout>
And this is my main_activity_alt.xml
<android.support.constraint.ConstraintLayout
android:id="#+id/constraintLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="160dp"
android:background="#color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<Button
android:id="#+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:text="Let's start animating"/>
</android.support.constraint.ConstraintLayout>
In my MainActivity I want to animate the image to slide upwards which would be the main_activity_alt.xml
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val constraintSet1 = ConstraintSet()
constraintSet1.clone(constraintLayout)
val constraintSet2 = ConstraintSet()
constraintSet2.clone(this, R.layout.activity_main_alt)
val transition = ChangeBounds()
transition.duration = 5000
val handler = Handler()
handler.postDelayed({
TransitionManager.beginDelayedTransition(constraintLayout, transition)
constraintSet2.applyTo(constraintLayout)
}, 10)
}
When the above starts the image is already displayed. The main_activity.xml should hide and the main_activity_alt.xml will be its final resting place.
However, when the screens loads it just immediately displays the imageview without any animation
Anything I am doing wrong with the above?
From the documentation for beginDelayedTransition:
Calling this method causes TransitionManager to capture current values in the scene root and then post a request to run a transition on the next frame. At that time, the new values in the scene root will be captured and changes will be animated.
You will have to wait until the layout is laid out before attempting the transition. There are a number of ways to do this, but the easiest would be to post the transition code as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val constraintLayout = findViewById<ConstraintLayout>(R.id.constraintLayout)
constraintLayout.post {
val constraintSet1 = ConstraintSet()
constraintSet1.clone(constraintLayout)
val constraintSet2 = ConstraintSet()
constraintSet2.clone(this, R.layout.activity_main2_alt)
val transition = ChangeBounds()
transition.duration = 5000
TransitionManager.beginDelayedTransition(constraintLayout, transition)
constraintSet2.applyTo(constraintLayout)
}
}
Try to make a delay to your transition so first layout would be inflated when transition starts
transition.delay = 300
if this wont work then try
Handler().postDelayed({do transition here}, 300)
I'm currently working on a UI that looks like this
The blue part is a ConstraintLayout while the purple part is a RecyclerView inside it (it's a RecyclerView because it's content are dynamic based on service response).
I'm setting onClick handler on the ConstraintLayout that would take the user to another page. The problem is the RecyclerView is consuming the clicks and not forwarding it to its parent. Thus onClick handler works for the blue area, but not for the purple area.
I tried setting android:clickable="false" and android:focusable="false" in the RecyclerView but it still won't propagate the clicks to its parent.
One solution I came across is to extend from ConstraintLayout and override onInterceptTouchEvent() to return true. However I have a strict requirement in my project to not create custom widgets, so I cannot use this solution.
Is there a way to tell RecyclerView to stop consuming touch events?
Activity layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="16dp"
android:background="#42d7f4"
android:onClick="navigate"
android:padding="16dp">
<TextView
android:id="#+id/headerText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="FRUITS"
android:textSize="36sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/itemsList"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#9f41f2"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Item layout:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/itemText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:clickable="false"
android:focusable="false" />
Activity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rcView = findViewById<RecyclerView>(R.id.itemsList)
rcView.layoutManager = LinearLayoutManager(this)
val items = listOf("Apple", "Banana", "Oranges", "Avocado")
rcView.adapter = ItemAdapter(items)
}
fun navigate(view: View) {
Toast.makeText(this, "Navigating to details page", Toast.LENGTH_SHORT)
.show()
}
}
class ItemAdapter(private val data: List<String>) : RecyclerView.Adapter<ItemViewHolder>() {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(data[position])
}
}
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val itemTv: TextView = view.findViewById(R.id.itemText)
fun bind(item: String) {
itemTv.text = item
}
}
If you freeze the layout after you've set the adapter it no longer swallows clicks:
recyclerView.isLayoutFrozen = true // kotlin
recyclerView.setLayoutFrozen(true); // java
Just keep in mind if you need to change the data in the adapter you have to unfreeze the layout before you call notifyDataSetChanged and then re-freeze the layout. I don't love this solution but it's the only one that worked for me.
One way to approach this is to set the RecyclerView row with a click listener and to disable clicks and long clicks on the children of the RecyclerView row as follows:
ConstraintLayout: `android:onClick="navigate"`
For the item layout: `android:onClick="navigate"`
TextView: android:clickable="false"
android:longClickable="false"
(etc. for all children of the row)
I think what is missing is to make the TextView not long clickable.
You can set your RecyclerView focus to false.
recyclerView.setFocusable(false);
And/Or set its rows view.setFocusable(false);
EDIT
If you want to give the ConstraintLayout the focus
layout.setFocusable(true);
layout.setClickable(true);
//
// add click listener and event ....
//
For more information please refer to the official documentation provided by Google
Hope this helps, cheers
Probably the easiest way to completely block interaction with anything inside a single view is to put a transparent view over it that intercepts all touch events. You can set click on that view manage all the functionality through that view.
For example like this
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="16dp"
android:background="#42d7f4"
android:padding="16dp">
<TextView
android:id="#+id/headerText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="FRUITS"
android:textSize="36sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/itemsList"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#9f41f2"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="#+id/clickView"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="navigate"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:clickable="true"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Now you can functionality perform through that view instead through ConstraintLayout.
Moreover, you look at this answer
I would like to change the following UI to be full screen programmatically.
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<TextView
android:id="#+id/one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ONE"
android:background="#android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toLeftOf="#+id/two"
/>
<TextView
android:id="#+id/two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TWO"
android:background="#android:color/holo_blue_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="#+id/one"
/>
</android.support.constraint.ConstraintLayout>
In my Kotlin file:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val param = ConstraintLayout.LayoutParams(0, 0)
one.layoutParams = param
two.layoutParams = param
}
}
Both TextView disappears after I set the width and height to 0 instead of being scratched fullscreen.
Desired UI
You're doing it wrong. You've to apply a new ConstraintSet like:
val constraintSet1 = ConstraintSet()
constraintSet1.clone(yourConstraintLayout)
val constraintSet2 = ConstraintSet()
constraintSet2.clone(yourConstraintLayout)
constraintSet2.constrainWidth(R.id.one, 0)
constraintSet2.constrainWidth(R.id.two, 0)
constraintSet2.constrainHeight(R.id.one, 0)
constraintSet2.constrainHeight(R.id.two, 0)
// to switch to full screen
constraintSet2.applyTo(yourConstraintLayout)
// to get back to your original screen
constraintSet1.applyTo(yourConstraintLayout)
I added a View to the bottom of my layout to add a line seperator at the end of this element. The problem is that after putting this View in the layout fills the entire width of the screen but when I take the View out it shows as the correct size (about 400dp in my tests). Why is the added View causing the layout to fill the whole screen.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="vertical"
android:paddingLeft="6dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/text_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textColor="#android:color/darker_gray"
android:textSize="12sp"
tools:text="Hello, Label"
android:paddingBottom="6dp"
android:paddingLeft="6dp"/>
<TextView
android:id="#+id/text_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textColor="#color/sr_red"
android:textSize="12sp"
tools:text="Error!"/>
</LinearLayout>
<!-- this View causes the parent layout to fill the whole screen width-->
<View
android:layout_width="wrap_content"
android:layout_height="1dp"
android:layout_marginTop="2dp"
android:background="#android:color/darker_gray"
/>
</LinearLayout>
It is the bottom most that is causing the problems.
I am modifying the view this way:
val inputLayout = createLabelLayout(context, component.name, button)
viewGroup.addView(inputLayout.root)
inputLayout.setWidth(width)
And here are the relevant classes
private fun <T : View> createLabelLayout(context: Context, text: String, child: T): LabelLayout<T> {
val layout = LabelLayout(context, child)
layout.label.text = text
return layout
}
private class LabelLayout<out T : View>(context: Context, child: T) {
val root = LayoutInflater.from(context).inflate(R.layout.item_custom_field_label, null, false) as ViewGroup
val label = root.findViewById(R.id.text_label) as TextView
val error = root.findViewById(R.id.text_error) as TextView
init {
root.addView(child, root.childCount - 1)
}
fun setWidth(width: Int) {
val params = root.layoutParams
params.width = width
root.layoutParams = params
}
}
Ok, so I fixed this problem by changing both the root LinearLayout and the child LinearLayout to wrap_content. Before I was only changing the parent layouts width.