Set runtime margin to any view using Kotlin - android

I am a beginner in Kotlin .I am not too much familier with this language. I am making one example and playing with code. I Just want to set runtime margin to any view. I also trying to google it but not getting any proper solution for this task.
Requirement
Set runtime margin to any View.
Description
I have taking one xml file which is contain on Button and I want to set runtime margin to this button.
Code
I also try below thing but it's not work.
class MainActivity : AppCompatActivity() {
//private lateinit var btnClickMe: Button
//var btnClickMe=Button();
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//For setting runtime text to any view.
btnClickMe.text = "Chirag"
//For getting runtime text to any view
var str: String = btnClickMe.text as String;
//For setting runtimer drawable
btnClickMe.background=ContextCompat.getDrawable(this,R.drawable.abc_ab_share_pack_mtrl_alpha)//this.getDrawable(R.drawable.abc_ab_share_pack_mtrl_alpha)
/*
//For Setting Runtime Margine to any view.
var param:GridLayout.LayoutParams
param.setMargins(10,10,10,10);
btnClickMe.left=10;
btnClickMe.right=10;
btnClickMe.top=10;
btnClickMe.bottom=10;
*/
// Set OnClick Listener.
btnClickMe.setOnClickListener {
Toast.makeText(this,str,5000).show();
}
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
tools:context="chirag.iblazing.com.stackoverflowapp.MainActivity"
android:layout_height="match_parent">
<Button
android:id="#+id/btnClickMe"
android:text="Click Me"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
How can I proceed?

You need to get the layoutParams object from button and cast it to ViewGroup.MarginLayoutParams (which is a parent class of LinearLayout.LayoutParams, RelativeLayout.LayoutParams and others and you don't have to check which is btnClickMe's actual parent) and set margins to whatever you want.
Check following code:
val param = btnClickMe.layoutParams as ViewGroup.MarginLayoutParams
param.setMargins(10,10,10,10)
btnClickMe.layoutParams = param // Tested!! - You need this line for the params to be applied.

This is how I would like to do in Kotlin -
fun View.margin(left: Float? = null, top: Float? = null, right: Float? = null, bottom: Float? = null) {
layoutParams<ViewGroup.MarginLayoutParams> {
left?.run { leftMargin = dpToPx(this) }
top?.run { topMargin = dpToPx(this) }
right?.run { rightMargin = dpToPx(this) }
bottom?.run { bottomMargin = dpToPx(this) }
}
}
inline fun <reified T : ViewGroup.LayoutParams> View.layoutParams(block: T.() -> Unit) {
if (layoutParams is T) block(layoutParams as T)
}
fun View.dpToPx(dp: Float): Int = context.dpToPx(dp)
fun Context.dpToPx(dp: Float): Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).toInt()
now we just have to call this on a view like
textView.margin(left = 16F)

Here's a useful Kotlin extension method:
fun View.setMargins(
left: Int = this.marginLeft,
top: Int = this.marginTop,
right: Int = this.marginRight,
bottom: Int = this.marginBottom,
) {
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
setMargins(left, top, right, bottom)
}
}
Use it like this:
myView.setMargins(
top = someOtherView.height
bottom = anotherView.height
)
EDIT: the solution is similar to the answer from Hitesh, but I'm using the (original) ViewGroup.setMargins in pixels. Of course you can make your own setMarginsDp variant based on these examples, or use Hitesh's dpToPx extension before calling my implementation. Whichever solution you choose depends on your own taste.
Also take note that my solution (re)sets all margins, although this won't be an issue in most cases.

If you want to change specific margin like top or bottom you can use below code with Data binding .
#BindingAdapter("android:layout_marginTop")
#JvmStatic
fun setLayoutMarginTop(view: View, marginTop: Float) {
val layoutParams = view.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = marginTop.toInt()
view.layoutParams = layoutParams
}
and in .xml file you can write like below code
<ImageView
android:id="#+id/imageView3"
android:layout_width="#dimen/_15dp"
android:layout_height="#dimen/_15dp"
android:layout_marginTop="#{homeViewModel.getLanguage() ? #dimen/_14dp : #dimen/_32dp }"
android:contentDescription="#string/health_indicator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/imageView1"
app:layout_constraintEnd_toStartOf="#+id/textView3"
android:src="#{ homeViewModel.remoteStatusVisible ? #drawable/green_rectangle : #drawable/gray_rectangle}"/>

Here is another sample of CardView
myCardView.elevation = 0F
myCardView.radius = 0F
val param = (myCardView.layoutParams as ViewGroup.MarginLayoutParams).apply {
setMargins(0,0,0,0)
}
myCardView.layoutParams = param

Related

How to fit the view to the size of the object? (Kotlin)

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!

Trying programmaticly to space a grid layout evenly

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.

How to set constraintTop_toTopOf attribute from MainActivity.kt for dynamically created TextView [duplicate]

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());

Kotlin how get width of wrap_content TextView

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
}
}
}

constraintlayout.widget.Group animation not working with TransitionManager

does anyone has any idea why animating constraintlayout.widget.Group visibility with TransitionManager is not working? Isn't this widget made for these kind of things?
It is working if hiding or showing items after separating views from Group
<androidx.constraintlayout.widget.Group
android:id="#+id/cardHeadersGroup"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="invisible"
app:constraint_referenced_ids="cardSystemHeader,cardSimpleHeader,cardCombinedHeader"
app:layout_constraintBottom_toBottomOf="#+id/cardCombinedHeader"
app:layout_constraintEnd_toEndOf="#+id/cardSystemHeader"
app:layout_constraintStart_toStartOf="#+id/cardSimpleHeader"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"/>
val headersGroup = binding.cardHeadersGroup
val slideIn = Slide()
slideIn.slideEdge = Gravity.BOTTOM
slideIn.mode = Slide.MODE_IN
slideIn.addTarget(headersGroup)
TransitionManager.beginDelayedTransition(binding.root as ViewGroup, slideIn)
headersGroup.visibility = VISIBLE
I've been recently working with TransitionManager and ConstraintLayout.Group and found it to be very buggy.
Eventually I decided to dump the whole ConstraintLayout.Group and created an in-code AnimationGroup (similar to the in-xml ConstraintLayout.Group):
class AnimationGroup(vararg val views: View) {
var visibility: Int = View.INVISIBLE
set(value) {
views.forEach { it.visibility = value }
field = value
}
}
and an extension function for the Transition:
private fun Transition.addTarget(animationGroup: AnimationGroup) {
animationGroup.views.forEach { viewInGroup -> this.addTarget(viewInGroup) }
}
That way you can do the following (almost exactly the same code, but simpler xml - no ConstraintLayout.Group):
val headersGroup = AnimationGroup(
binding.cardSystemHeader,
binding.cardSimpleHeader,
binding.cardCombinedHeader
)
val slideIn = Slide()
slideIn.slideEdge = Gravity.BOTTOM
slideIn.mode = Slide.MODE_IN
slideIn.addTarget(headersGroup)
TransitionManager.beginDelayedTransition(binding.root as ViewGroup, slideIn)
headersGroup.visibility = VISIBLE
We can also extract the Group's referenced views with simple extension function:
fun Group.getReferencedViews() = referencedIds.map { rootView.findViewById<View>(it) }

Categories

Resources