I'm building my first game in Android Studio. Right now, dots fall from the top of the screen down to the bottom. For some reason, in Layout Inspector the view of each dot is the entire screen even though the dots are comparatively small. This negatively affects the game since when a user presses anywhere on the screen, it deletes the most recently created dot rather than the one pressed. I want to get the dot's view to match the size of the actual dots without effecting other functionality.
Dot.kt
class Dot(context: Context, attrs: AttributeSet?, private var dotColor: Int, private var xPos: Int, private var yPos: Int) : View(context, attrs) {
private var isMatching: Boolean = false
private var dotIsPressed: Boolean = false
private var isDestroyed: Boolean = false
private lateinit var mHandler: Handler
private lateinit var runnable: Runnable
init {
this.isPressed = false
this.isDestroyed = false
mHandler = Handler()
runnable = object : Runnable {
override fun run() {
moveDown()
invalidate()
mHandler.postDelayed(this, 20)
}
}
val random = Random()
xPos = random.nextInt(context.resources.displayMetrics.widthPixels)
startFalling()
startDrawing()
}
// other methods
fun getDotColor() = dotColor
fun getXPos() = xPos
fun getYPos() = yPos
fun isMatching() = isMatching
fun setMatching(matching: Boolean) {
this.isMatching = matching
}
fun dotIsPressed() = dotIsPressed
override fun setPressed(pressed: Boolean) {
this.dotIsPressed = pressed
}
fun isDestroyed() = isDestroyed
fun setDestroyed(destroyed: Boolean) {
this.isDestroyed = destroyed
}
fun moveDown() {
// code to move the dot down the screen
yPos += 10
}
fun checkCollision(line: Line) {
// check if dot is colliding with line
// if yes, check if dot is matching or not
// update the dot state accordingly
}
fun startFalling() {
mHandler.post(runnable)
}
fun startDrawing() {
mHandler.postDelayed(object : Runnable {
override fun run() {
invalidate()
mHandler.postDelayed(this, 500)
}
}, 500)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (!isDestroyed) {
val paint = Paint().apply {
color = dotColor
}
canvas?.drawCircle(xPos.toFloat(), yPos.toFloat(), 30f, paint)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var score = 0
private lateinit var scoreCounter: TextView
private val dots = mutableListOf<Dot>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createLine(Color.RED, 5000)
scoreCounter = TextView(this)
scoreCounter.text = score.toString()
scoreCounter.setTextColor(Color.WHITE)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.setBackgroundColor(Color.BLACK)
val params = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
scoreCounter.layoutParams = params
layout.addView(scoreCounter)
val dotColors = intArrayOf(Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW)
val random = Random()
val handler = Handler()
val runnable = object : Runnable {
override fun run() {
val dotColor = dotColors[random.nextInt(dotColors.size)]
createAndAddDot(0, 0, dotColor)
handler.postDelayed(this, 500)
}
}
handler.post(runnable)
}
fun updateScore(increment: Int) {
score += increment
scoreCounter.text = score.toString()
}
fun createAndAddDot(x: Int, y: Int, color: Int) {
Log.d("Dot", "createAndAddDot called")
val dot = Dot(this, null, color, x, y)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.addView(dot)
dots.add(dot)
dot.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
val dotToRemove = dots.find { it == view }
dotToRemove?.let {
layout.removeView(it)
dots.remove(it)
updateScore(1)
view.performClick()
}
}
true
}
}
fun createLine(color: Int, interval: Int) {
Log.d("Line", "createLine called")
val line = Line(color, interval)
val lineView = Line.LineView(this, null, line)
val layout = findViewById<ConstraintLayout>(R.id.layout)
if (layout == null) {
throw IllegalStateException("Layout not found")
}
layout.addView(lineView)
val params = ConstraintLayout.LayoutParams(2000, 350)
lineView.layoutParams = params
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID
params.bottomMargin = (0.1 * layout.height).toInt()
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your view here -->
<View
android:id="#+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Guideline set to 10% from the bottom -->
<androidx.constraintlayout.widget.Guideline
android:id="#+id/bottom_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>
I tried changing the view size with
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val diameter = 40 // or any other desired diameter for the dots setMeasuredDimension(diameter, diameter) }
That made the view size a square stuck in the top left corner. As I played around with it, I could only get dots to show in that small window in the top corner rather than moving down the screen from different starting x-positions
Your custom view isn't a dot, it's a large display area that draws a dot somewhere inside it and animates its position. In onDraw you're drawing a circle at xPos (a random point on the screen width via displayMetrics.widthPixels) and yPos (an increasing value which moves the dot down the view).
There are two typical approaches to things like this:
use simple views like ImageViews. Let the containing Activity or Fragment add them to a container and control their position, maybe using the View Animation system. Handle player interaction by giving them click listeners and let the view system work out what's been clicked.
create a custom view that acts as the game area. Let that custom view control the game state (what dots exist, where they currently are) and draw that state in onDraw. Handle touch events on the view, and work out if those touches coincide with a dot (by comparing to the current game state).
What you're doing is sort of a combination of the two with none of the advantages that either approach gives on its own. You have multiple equally-sized "game field" views stacked on top of each other, so any clicks will be consumed by the top one, because you're clicking the entire view itself. And because your custom view fills the whole area, you can't move it around with basic view properties to control where the dot is - you have to write the logic to draw the view and animate its contents.
You could implement some code that handles the clicks and decides whether the view consumes it (because it intersects a dot) or passes it on to the next view in the stack, but that's a lot of work and you still have all your logic split between the Activity/Fragment and the custom view itself.
I think it would be way easier to just pick one approach - either use ImageViews sized to the dot you want and let the view system handle the interaction, or make a view that runs the game internally. Personally I'd go with the latter (you'll find it a lot easier to handle dots going out of bounds, get better performance, more control over the look and interaction etc, no need to cancel Runnables) but it's up to you!
I have a GridLayout which should show 25 Buttons spaced evenly. To be able to set an onClickListener without calling each one them I want to do that programmatically.
I made a layout resource file with the grid itself to bind it and being able to inflate it
activity.xml
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:grid="http://schemas.android.com/apk/res-auto"
android:id="#+id/bingo_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:columnCount="5"
android:rowCount="5"
tools:context=".BingoActivity" />
Now I'm creating the fields:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bingoField = (1).rangeTo(25).toSet().toIntArray()
binding = BingoActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.bingoGrid.alignmentMode = GridLayout.ALIGN_BOUNDS
val bingoFieldGrid = binding.bingoGrid
bingoFieldGrid.alignmentMode = GridLayout.ALIGN_BOUNDS
bingoField.forEach {
val button = createButton(it.toString())
val gridLayoutParams = GridLayout.LayoutParams().apply {
rowSpec = spec(GridLayout.UNDEFINED, GridLayout.CENTER, 1f)
columnSpec = spec(GridLayout.UNDEFINED, GridLayout.CENTER, 1f)
height = GridLayout.LayoutParams.WRAP_CONTENT
width = GridLayout.LayoutParams.WRAP_CONTENT
}
bingoFieldGrid.addView(button, gridLayoutParams)
}
#RequiresApi(Build.VERSION_CODES.M)
private fun createButton(buttonText: String): Button {
var isCompleted = false
return Button(baseContext).apply {
setBackgroundColor(getColor(R.color.red))
gravity = Gravity.CENTER
text = buttonText
setOnClickListener {
isCompleted = if (!isCompleted) {
setBackgroundColor(getColor(R.color.green))
true
} else {
setBackgroundColor(getColor(R.color.red))
false
}
}
}
}
So, the fields are auto generated without problems, but the spacing is not right:
I'm quite new to the old layouting, is there a way to easily achieve that?
You're creating two different types of LayoutParams which doesn't make sense. LinearLayout shouldn't be involved at all.
The way they work is each child should get a set of LayoutParams that match the type of LayoutParams that its parent ViewGroup uses. So in this case the parent is GridLayout, so each child should be added using an instance of GridLayout.LayoutParams.
The way GridLayout.LayoutParams work is you define a row Spec and a column Spec that describe how a child should take up cells. We want them to take the single next cell, so we can leave the first parameter as UNDEFINED. We need to give them an equal weight more than 0 so they all share evenly in the leftover space. I'm using 1f for the weight.
I'm using FILL with a size of 0 for the buttons so they fill their cells. The margins put some gap between them.
I'm setting height and width to 0 to prevent them from being oversized. If the rows or columns become too big to fit the screen, the layout goes way too big.
You might want to use MaterialButton instead of a plain Button, so you can easily tint the background color without simply making it a static solid color rectangle.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = BingoBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.bingoGrid.alignmentMode = GridLayout.ALIGN_BOUNDS
for (num in 1..25) {
val button = MaterialButton(this).apply {
setBackgroundColor(resources.getColor(R.color.blue_500))
gravity = Gravity.CENTER
text = num.toString()
setPadding(0)
}
val params = GridLayout.LayoutParams().apply {
rowSpec = spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f)
columnSpec = spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f)
width = 0
height = 0
setMargins((4 * resources.displayMetrics.density).toInt())
}
binding.bingoGrid.addView(button, params)
}
}
AndroidStudio was finnicky about importing the spec function. I had to manually add this at the top:
import android.widget.GridLayout.Spec.*
You could consider Google ConstraintLayout Flows:
To set the number of elements use app:flow_maxElementsWrap="5"
layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/flow"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flow_horizontalGap="8dp"
app:flow_maxElementsWrap="5"
app:flow_verticalGap="8dp"
app:flow_verticalStyle="packed"
app:flow_wrapMode="chain" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then add the buttons programmatically to the ConstraintLayout:
val root = findViewById<ViewGroup>(R.id.root)
val size = 25
val array = IntArray(size)
for (i in 0 until size) {
array[i] = i + 1
val button = Button(this).apply {
layoutParams = ViewGroup.LayoutParams(0, 0)
id = i + 1
text = (i + 1).toString()
}
root.addView(button)
}
val flow = findViewById<Flow>(R.id.flow)
flow.referencedIds = array
Hint: you could use WRAP_CONTENT for the button height to avoid stretching out the buttons height.
I need help with ConstraintSet. My goal is to change view's constraints in code, but I cant figure out how to do this right.
I have 4 TextViews and one ImageView. I need to set ImageView constraints to one of the TextViews.
check_answer4 = (TextView) findViewById(R.id.check_answer4);
check_answer1 = (TextView) findViewById(R.id.check_answer1);
check_answer2 = (TextView) findViewById(R.id.check_answer2);
check_answer3 = (TextView) findViewById(R.id.check_answer3);
correct_answer_icon = (ImageView) findViewById(R.id.correct_answer_icon);
If 1st answer is right, I need to set constraints of ImageView to
app:layout_constraintRight_toRightOf="#+id/check_answer1"
app:layout_constraintTop_toTopOf="#+id/check_answer1"
If 2nd answer is right, I need to set constraints of ImageView to
app:layout_constraintRight_toRightOf="#+id/check_answer2"
app:layout_constraintTop_toTopOf="#+id/check_answer2"
And so on.
To set constraints of image view to:
app:layout_constraintRight_toRightOf="#+id/check_answer1"
app:layout_constraintTop_toTopOf="#+id/check_answer1"
use:
ConstraintLayout constraintLayout = findViewById(R.id.parent_layout);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.connect(R.id.imageView,ConstraintSet.RIGHT,R.id.check_answer1,ConstraintSet.RIGHT,0);
constraintSet.connect(R.id.imageView,ConstraintSet.TOP,R.id.check_answer1,ConstraintSet.TOP,0);
constraintSet.applyTo(constraintLayout);
To set constraints of image view to:
app:layout_constraintRight_toRightOf="#+id/check_answer2"
app:layout_constraintTop_toTopOf="#+id/check_answer2"
use:
ConstraintLayout constraintLayout = findViewById(R.id.parent_layout);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.connect(R.id.imageView,ConstraintSet.RIGHT,R.id.check_answer2,ConstraintSet.RIGHT,0);
constraintSet.connect(R.id.imageView,ConstraintSet.TOP,R.id.check_answer2,ConstraintSet.TOP,0);
constraintSet.applyTo(constraintLayout);
Assume we want to change constraints during runtime, making button1 to be aligned with button2 when clicked:
Then, having this layout:
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1"
app:layout_constraintTop_toTopOf="#+id/button3"
app:layout_constraintBottom_toBottomOf="#+id/button3"
app:layout_constraintStart_toEndOf="#+id/button3"
android:layout_marginStart="0dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="0dp" />
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="Button 2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.5" />
<Button
android:id="#+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="Button 3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.223" />
</android.support.constraint.ConstraintLayout>
We can do following:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button1.setOnClickListener {
val params = button1.layoutParams as ConstraintLayout.LayoutParams
params.leftToRight = button2.id
params.topToTop = button2.id
params.bottomToBottom = button2.id
button1.requestLayout()
}
}
Another approach is to update the layout params of view like this (without requesting Layout):
yourView.updateLayoutParams<ConstraintLayout.LayoutParams> {
startToEnd = targetView.id
topToTop = targetView.id
bottomToBottom = targetView.id
//add other constraints if needed
}
In addition to azizbekian's answer, let me point out two things:
If left/right didn't work, try start/end like this:
params.startToEnd = button2.id
If you want to remove a constraint, use UNSET flag like this:
params.startToEnd = ConstraintLayout.LayoutParams.UNSET
In Kotlin you can simply extend ConstraintSet class and add some methods to take advantage of dsl in Kotlin and produce a more readable code.
Like this
class KotlinConstraintSet : ConstraintSet() {
companion object {
inline fun buildConstraintSet(block:KotlinConstraintSet.()->Unit) =
KotlinConstraintSet().apply(block)
}
//add this if you plan on using the margin param in ConstraintSet.connect
var margin: Int? = null
get() {
val result = field
margin = null //reset it to work with other constraints
return result
}
inline infix fun Unit.and(other: Int) = other // just to join two functions
inline infix fun Int.topToBottomOf(bottom: Int) =
margin?.let {
connect(this, TOP, bottom, BOTTOM, it)
} ?: connect(this, TOP, bottom, BOTTOM)
inline fun margin(margin: Int) {
this.margin = margin
}
inline infix fun Int.bottomToBottomOf(bottom: Int) =
margin?.let {
connect(this, BOTTOM, bottom, BOTTOM, it)
} ?: connect(this, BOTTOM, bottom, BOTTOM)
inline infix fun Int.topToTopOf(top: Int) =
margin?.let {
connect(this, TOP, top, TOP, it)
} ?: connect(this, TOP, top, TOP)
inline infix fun Int.startToEndOf(end: Int) =
margin?.let {
connect(this, START, end, END, it)
} ?: connect(this, START, end, END)
...
//TODO generate other functions depending on your needs
infix fun Int.clear(constraint: Constraints) =
when (constraint) {
Constraints.TOP -> clear(this, TOP)
Constraints.BOTTOM -> clear(this, BOTTOM)
Constraints.END -> clear(this, END)
Constraints.START -> clear(this, START)
}
//inline infix fun clearTopCon
inline infix fun appliesTo(constraintLayout: ConstraintLayout) =
applyTo(constraintLayout)
inline infix fun clones(constraintLayout: ConstraintLayout) =
clone(constraintLayout)
inline fun constraint(view: Int, block: Int.() -> Unit) =
view.apply(block)
}
enum class Constraints {
TOP, BOTTOM, START, END //you could add other values to use with the clear fun like LEFT
}
And use it like this
buildConstraintSet {
this clones yourConstraintLayout
constraint(R.id.view1) {
margin(value:Int) and this topToBottomOf R.id.view2
margin(30) and this bottomToBottomOf ConstraintSet.PARENT_ID
}
constraint(R.id.view2) {
this clear Constraints.BOTTOM
margin(0) and this topToTopOf R.id.topGuide
}
constraint(R.id.view4) {
this topToTopOf R.id.view2
this bottomToBottomOf R.id.view3
this startToEndOf R.id.view2
}
//or you could simply do
R.id.view1 startToEndOf R.view2
R.id.view1 toptToBottomOf R.view3
R.id.view3 bottomtToBottomOf R.view2
R.id.view3 clear Constraints.END
// and finally call applyTo()
this appliesTo yourConstraintLayout
}
I know my answer is very late, yet I'm sure It'd help others that stop by here a lot.
This article is not mine but I made a few changes, that being said, you should endeavor to check out the full article here
Constraint Sets
The key to working with constraint sets in Java code is the ConstraintSet class. This class contains a range of methods that allow tasks such as creating, configuring and applying constraints to a ConstraintLayout instance. In addition, the current constraints for a ConstraintLayout instance may be copied into a ConstraintSet object and used to apply the same constraints to other layouts (with or without modifications).
A ConstraintSet instance is created just like any other Java object:
ConstraintSet set = new ConstraintSet();
Once a constraint set has been created, methods can be called on the instance to perform a wide range of tasks.
The following code configures a constraint set in which the left-hand side of a Button view is connected to the right-hand side of an EditText view with a margin of 70dp:
set.connect(button1.getId(), ConstraintSet.LEFT,
editText1.getId(), ConstraintSet.RIGHT, 70);
Applying Constraints to a Layout
Once the constraint set is configured, it must be applied to a ConstraintLayout instance before it will take effect. A constraint set is applied via a call to the applyTo() method, passing through a reference to the layout object to which the settings are to be applied:
set.applyTo(myLayout);
There are lot more stuffs you can do with the ConstraintSet API, Setting horizontal and vertical bias, center horizontally and vertically, manipulate Chains and a lot more.
Really nice read.
Again, this is just an adaptation.
You can create constraints into ConstraintLayout with ConstraintSet or ConstraintLayout.LayoutParams properties.
I created a ConstraintSet kotlin extension for simple usage.
This is simple and robust.
Usage
applyConstraint {
centerHorizontallyParent(imageView)
centerHorizontallyParent(textView)
topToParent(imageView, px(12))
topToBottom(textView, imageView, px(4))
}
Code
fun ConstraintLayout.applyConstraint(block: ConstraintSet.() -> Unit) {
ConstraintSet().apply {
clone(this#applyConstraint)
block(this)
}.applyTo(this)
}
fun ConstraintSet.centerParent(v1: View) {
centerHorizontallyParent(v1)
centerVerticallyParent(v1)
}
fun ConstraintSet.centerHorizontallyParent(v1: View) {
centerHorizontally(v1.id, PARENT_ID)
}
fun ConstraintSet.centerVerticallyParent(v1: View) {
centerVertically(v1.id, PARENT_ID)
}
fun ConstraintSet.topToTop(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, TOP, v2.id, TOP, margin)
}
fun ConstraintSet.topToParent(v1: View, #Px margin: Int = 0) {
connect(v1.id, TOP, PARENT_ID, TOP, margin)
}
fun ConstraintSet.bottomToBottom(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, BOTTOM, v2.id, BOTTOM, margin)
}
fun ConstraintSet.bottomToParent(v1: View, #Px margin: Int = 0) {
connect(v1.id, BOTTOM, PARENT_ID, BOTTOM, margin)
}
fun ConstraintSet.topToBottom(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, TOP, v2.id, BOTTOM, margin)
}
fun ConstraintSet.bottomToTop(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, BOTTOM, v2.id, TOP, margin)
}
fun ConstraintSet.startToStart(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, START, v2.id, START, margin)
}
fun ConstraintSet.startToParent(v1: View, #Px margin: Int = 0) {
connect(v1.id, START, PARENT_ID, START, margin)
}
fun ConstraintSet.endToEnd(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, END, v2.id, END, margin)
}
fun ConstraintSet.endToParent(v1: View, #Px margin: Int = 0) {
connect(v1.id, END, PARENT_ID, END, margin)
}
fun ConstraintSet.startToEnd(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, START, v2.id, END, margin)
}
fun ConstraintSet.endToStart(v1: View, v2: View, #Px margin: Int = 0) {
connect(v1.id, END, v2.id, START, margin)
}
#vishakha yeolekar 's solution does not work for me.
To change constraints, we need to follow these steps:
clone parent layout
clear previous constraint
connect constraint
apply constraint to parent layout
Solution code (in Kotlin)
val clParent = findViewById<ConstraintLayout>(R.id.parent_layout)
val mConstraintSet = ConstraintSet()
mConstraintSet.clone(clParent)
mConstraintSet.clear(R.id.imageView, ConstraintSet.END)
mConstraintSet.connect(R.id.imageView, ConstraintSet.END, R.id.check_answer, ConstraintSet.END)
mConstraintSet.applyTo(clParent)
Here is the link for more info and methods of ConstraintSet - Click Here.
You can also use TransitionManager to animate the changes:
public static void animateConstraintLayout(ConstraintLayout constraintLayout, ConstraintSet set, long duration) {
AutoTransition trans = new AutoTransition();
trans.setDuration(duration);
trans.setInterpolator(new AccelerateDecelerateInterpolator());
TransitionManager.beginDelayedTransition(constraintLayout, trans);
set.applyTo(constraintLayout);
}
You can call it like this after updating the layout with ConstraintSet:
ConstraintSet set = new ConstraintSet();
set.clone(constraintLayout);
set.connect(R.id.example, ConstraintSet.BOTTOM, R.id.anotherExample, ConstraintSet.BOTTOM);
set.connect(R.id.example, ConstraintSet.TOP, R.id.anotherExample, ConstraintSet.TOP);
animateConstraintLayout(constraintLayout, set, 500);
For example:
app:layout_constraintRight_toRightOf = "parentView"
If you liked to declared it with programe, it will be...
ConstraintLayout.LayoutParams.rightToRight = parentView.getId()
If you used programe to declared the parentview(viewgroup), don't forget to give the parentview a generated id.
parentView.setId(View.generatedId());
I have a problem, because I need to have width of my TextView. I have already width of my Layout, but I need to have a width of specific element too. In this case TextView. I'm trying to get it, but I think that addOnLayoutChangeListener is going on another scope or sth because when I try to assign width to var textWidth I can't do this and variable return 0, but in println I can see that there is a value which I need. How can I get this value?
var textWidth = 0
textViewOne.addOnLayoutChangeListener {
v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom
-> textWidth = right-left
println("${right-left}") <-- this return 389
}
println("${textWidth}") <-- this return 0
Any tips how to do take width of TextView?
I believe the views dimensions are not evaluated at this stage so you don’t have a choice but to wait until the layout is fully rendered.
Assuming you are not measuring a single view, I’d recommend attaching OnGlobalLayoutListener to the root view:
rootView.viewTreeObserver.addOnGlobalLayoutListener {
// Do your thing
}
And if you want the code to be executed only once:
rootView.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
// Do your thing
}
})
I just solved problem if anyone need solution this workes for me:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val displayMetrics: DisplayMetrics = applicationContext.resources.displayMetrics
val pxWidth = displayMetrics.widthPixels
val baseLayout = findViewById<LinearLayout>(R.id.baseLayout)
baseLayout.doOnLayout {
val textViewOne = findViewById<TextView>(R.id.textVieOne)
val oneWidth = textViewOne.width
val textViewTwo = findViewById<TextView>(R.id.textViewTwo)
val twoWidth = textViewTwo.width
val textViewThree = findViewById<TextView>(R.id.textViewThree)
val threeWidth = textViewThree.width
val sumOfChildWidths = oneWidth + twoWidth + threeWidth
if(pxWidth <= sumOfChildWidths){
textViewThree.isVisible = false
}
}
}