I want to create a view (i.e. TextView) in my constraint layout and animate it's position on button click.
This is how my onClick looks like:
fun onClick(view: View) {
val layout = findViewById<ConstraintLayout>(R.id.layout)
val txt2 = TextView(this)
txt2.text = "Hello2"
txt2.textSize = 70F
txt2.id = View.generateViewId()
tid = txt2.id
layout.addView(txt2)
val cs = ConstraintSet()
cs.clone(layout)
cs.connect(txt2.id, ConstraintSet.BOTTOM, textbox.id, ConstraintSet.TOP, 0)
cs.connect(txt2.id, ConstraintSet.LEFT, layout.id, ConstraintSet.LEFT)
cs.connect(txt2.id, ConstraintSet.RIGHT, layout.id, ConstraintSet.RIGHT)
cs.applyTo(layout)
startAnimation(txt2.id)
}
fun startAnimation(txt: Int) {
val layout = findViewById<ConstraintLayout>(R.id.layout)
TransitionManager.beginDelayedTransition(layout)
val cs2 = ConstraintSet()
cs2.clone(layout)
cs2.connect(txt, ConstraintSet.TOP, layout.id, ConstraintSet.TOP, 50)
cs2.applyTo(layout)
}
Animation works if I create TextView somewhere before actual click, but how to do this in one shot?
I think problem in the way how android calculate layout. When you call TextView(this) and layout.addView(txt2) view created and added to children list, but have no height, width and position on screen. In Looper of UI thread will be enqueued task to layout textView, that call onMeausure and layout for txt2 and its children. If it enqueued in UI thread, it will be copmlite after your functions finish, and you start animation before tx2 was "layouted".
I see 2 ways to resolve this. First: create txt2 in layout on start of activity or specify it in xml, just not create constrains for it and set visibility to GONE. ConstraintSet.applyTo correctly works with appearing and disappearing when it change visibility. Second: put starting of animation as delayed. Not the perfect solution, but it should works.
Handler().postDelayed({
startAnimation(txt2.id)
}, 100)
Related
I have been using constraintlayout animations using two layout files
private void animate() {
ConstraintSet set1 = new ConstraintSet();
set1.clone(root);
ConstraintSet set2 = new ConstraintSet();
set2.clone(this, R.layout.layout_file_2);
TransitionManager.beginDelayedTransition(root, transition);
set2.applyTo(root);
}
However, in the layout i am only changing two constraints. I decided to try doing so programmatically and did this, which worked.
private void showHidden() {
ConstraintLayout parentLayout = findViewById(R.id.root_constraint_layout);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(parentLayout);
constraintSet.connect(R.id.search_layout, ConstraintSet.TOP, R.id.toolbar_layout, ConstraintSet.BOTTOM);
constraintSet.connect(R.id.search_layout, ConstraintSet.BOTTOM, R.id.root_constraint_layout, ConstraintSet.BOTTOM);
constraintSet.applyTo(parentLayout);
}
This is however, not animated. I tried to animate as I did before in the first one (using only 1 layout file this time) and my result was this
private void animate2() {
ConstraintLayout parentLayout = findViewById(R.id.root_constraint_layout);
ConstraintSet set1 = new ConstraintSet();
set1.clone(parentLayout);
ConstraintSet set2 = new ConstraintSet();
set2.clone(parentLayout);
set2.connect(R.id.search_layout, ConstraintSet.TOP, R.id.toolbar_layout, ConstraintSet.BOTTOM);
set2.connect(R.id.search_layout, ConstraintSet.BOTTOM, R.id.root_constraint_layout, ConstraintSet.BOTTOM);
Transition transition = new Slide(Gravity.END).setDuration(500);
TransitionManager.beginDelayedTransition(parentLayout, transition);
set2.applyTo(parentLayout);
}
This works like the second one, but shows no animation, the new constraints immediately jump into place. Is there any way I can make the animation work without using two layoyuts? or is there something i'm doing wrong? thanks.
You didn't clear the constraints on side of view on which you applying constraint set .
You can do this approach using
constraintSet.clear(view.Layout.id, ConstraintSet.LEFT) //You can change LEFT with whatever side you are choosing
Before connecting layout sides using connect function .
Here is the full code for getting it in better way :-
val constraintSet = ConstraintSet()
constraintSet.clone(view.rootLayout)
view.layout.animate().alpha(1f).duration = 700
constraintSet.clear(view.layout.id, ConstraintSet.LEFT)
constraintSet.connect(
view.layout.id,
ConstraintSet.RIGHT,
ConstraintSet.PARENT_ID,
ConstraintSet.RIGHT
)
view.layout.animate().translationX(resources.getDimension(R.dimen.zeroDP))
val transition: Transition = ChangeBounds()
transition.interpolator = EasingInterpolator(Ease.QUINT_OUT)
transition.duration = 700
TransitionManager.beginDelayedTransition(view.rootLayout, transition)
constraintSet.applyTo(view.rootLayout)
I have a constraint
app:layout_constraintStart_toStartOf="parent". Runtime I need to change this constraint to app:layout_constraintStart_toEndOf="#+id\myid" .
From my research i found only constraintSet.connect(viewid, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, 50); . But how to achieve my requirements with this. Any idea on this?
This is in kotlin but it's barely different from Java.
Give an ID to the rootLayout.
Now, use this code:
val constraintSet = ConstraintSet()
constraintSet.clone(rootLayout) //Your rootLayout
constraintSet.connect(
yourView.id, //ID of the view whose position you want to change
ConstraintSet.START,
yourMyIdView.id, //ID of the correspondent view
ConstraintSet.END
)
constraintSet.applyTo(rootLayout) //Your rootLayout
ProTip : You can also animate the change by setting animateChange to true in rootLayout (XML) or you can use a Transition animation which I always prefer.
val transition: Transition = ChangeBounds()
transition.interpolator = AccelerateInterpolator() //Or Any other Interpolator
transition.duration = 700 //Duration
TransitionManager.beginDelayedTransition(rootLayout, transition) //Your rootLayout
constraintSet.applyTo(rootLayout) //Remember to put above 4 lines before this
You can further add Alpha animation it by using yourView.animate().alpha(1f).duration = 700 //1f (0 to 1) is the value and 700 is the duration.
Remember, it is barely different from Java, you just have to add ; at the end possibly.
I am trying to change the constraints programmatically as follows:
final ConstraintSet set = new ConstraintSet();
set.clone(this);
if(text == null) {
view.setVisibility(GONE);
set.clear(div.getId(), ConstraintSet.TOP);
set.connect(div.getId(), ConstraintSet.TOP, otherView.getId(), ConstraintSet.BOTTOM);
}
set.applyTo(this);
But the rule is not applied. The div goes to the top of the layout like there is no top constraint.
What am I doing wrong?
Update
If I change the following:
set.clone(this);
...
set.apply(this);
to:
View root = LayoutInflater.from(getContext()).inflate(R.layout.my_layout, this);
ConstraintLayout layout = root.findViewById(R.id.constraint_layout);
....
set.clone(layout);
....
set.apply(layout);
The layout is correct! But I don't understand why.
this is a ConstraintLayout since I am extending a ConstraintLayout.
Good day,
I need to show a list of fidelity cards, one on the others with a small border margin, and while clicking on it, it reveal it completely while sliding.
In a fragment, I use a ConstraintLayout somewhere in the screen that I populate by code.
I add all child's (total known after WS called) into the ConstraintLayout, then I am using ConstraintSet to set the initial constraints.
But at the initial fragment creation, I have found out that I must apply a TimeOut between the ConstraintLayout population and creating the Constraints (disgusting, right?)
adding each card...
val card = FrequencyCardView()
fid_card_container.addView(card as View)
listCard.add(card)
Then create the constraints:
val set = ConstraintSet()
val containerId = fid_card_container.id
set.clone(fid_card_container)
listCard.forEachIndexed { index, card ->
val marginTop = when (index) {
0 -> 0
else -> (listCard[index -1] as ViewGroup).height / 4
}
val rootId = if (index == 0) containerId else (index - 1)
set.connect(card.getId(), TOP, rootId, TOP, marginTop)
set.connect(card.getId(), ConstraintSet.START, containerId, ConstraintSet.START, 0)
set.connect(card.getId(), ConstraintSet.END, containerId, ConstraintSet.END, 0)
card.setOpen(false)
}
// Apply the changes
set.applyTo(fid_card_container)
Any idea, why I need to apply any timeout between adding the Childs into the ConstraintLayout and then add the Constraints?
Any better solution, that this ugly TimeOut?
Thanks
I have found a better way.
It seems that the constraints creation must be ensured to be done on the UI Thread. This is why it does work with the timeout.
So, I have changed the timeout call by the Anko call:
doAsync {
uiThread {
resetCardLayout()
}
}
I want to remove and add constraint programmatically based on some condition. Here are the screenshots:
and I want to remove it like this but in code:
so the same effect in want to achieve programmatically
and here is the code that I tried:
if (advertisements.size() > 0) { //my own condition
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) btnCreateAd.getLayoutParams();
layoutParams.topToBottom = R.id.imvEmpty; //the imageview that is in center of the view
btnCreateAd.setLayoutParams(layoutParams);
recyclerView.setVisibility(View.VISIBLE);
txvMyAdEmptyText.setVisibility(View.GONE);
imvEmpty.setVisibility(View.GONE);
adapter.setList(advertisements);
adapter.notifyDataSetChanged();
} else {
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) btnCreateAd.getLayoutParams();
layoutParams.topToBottom = -1; //here i am trying to remove top constraint but it doesn't work
btnCreateAd.setLayoutParams(layoutParams);
recyclerView.setVisibility(View.GONE);
txvMyAdEmptyText.setVisibility(View.VISIBLE);
imvEmpty.setVisibility(View.VISIBLE);
adapter.setList(new ArrayList<Advertisement>());
}
mConstraintView.invalidate(); //this is my constraint view
EDIT
I have tried using ConstraintSet also, but the result was even different somehow my RecyclerView (which is set to boundaries of parent view) was disappearing
ConstraintSet set = new ConstraintSet();
set.clone(parentView);
if (advertisements.size() > 0) {
recyclerView.setVisibility(View.VISIBLE);
txvMyAdEmptyText.setVisibility(View.GONE);
imvEmpty.setVisibility(View.GONE);
adapter.setList(advertisements);
adapter.notifyDataSetChanged();
} else {
set.connect(btnCreateAd.getId(), ConstraintSet.TOP, imvEmpty.getId(), ConstraintSet.BOTTOM, 0);
recyclerView.setVisibility(View.GONE);
txvMyAdEmptyText.setVisibility(View.VISIBLE);
imvEmpty.setVisibility(View.VISIBLE);
adapter.setList(new ArrayList<Advertisement>());
}
set.connect(btnCreateAd.getId(), ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, 0);
set.connect(btnCreateAd.getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, 0);
set.connect(btnCreateAd.getId(), ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 0);
set.applyTo(parentView);
I have not worked through your code, but the following illustrates how to break and make the constraint using ConstraintSet.
ConstraintSet set = new ConstraintSet();
ConstraintLayout layout;
layout = (ConstraintLayout) findViewById(R.id.layout);
set.clone(layout);
// The following breaks the connection.
set.clear(R.id.bottomText, ConstraintSet.TOP);
// Comment out line above and uncomment line below to make the connection.
// set.connect(R.id.bottomText, ConstraintSet.TOP, R.id.imageView, ConstraintSet.BOTTOM, 0);
set.applyTo(layout);
Another way of doing this is with ConstraintLayout.LayoutParams.UNSET. This way is not using ConstraintSet.clear.
Assuming we want to remove the bottom constraint of our constraintLayout itself but can be any view:
val containerParams = cl_container.layoutParams as ConstraintLayout.LayoutParams
containerParams.bottomToBottom = ConstraintLayout.LayoutParams.UNSET
cl_container.layoutParams = containerParams
Here is the Java version of the answer from j2emanue using ConstraintLayout.LayoutParams, which works without specifying the id of the element that the layout is contrained to:
final ConstraintLayout constraintLayout = findViewById(R.id.constraintLayout);
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) constraintLayout.getLayoutParams();
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.UNSET;
constraintLayout.setLayoutParams(layoutParams);
There is an easier way to do it now if you're using viewBinding/dataBinding with Kotlin.
There is an extension function called updateLayoutParams from import androidx.core.view.updateLayoutParams and use it like below
binding.yourView.updateLayoutParams<ConstraintLayout.LayoutParams> {
this.startToEnd = ConstraintLayout.LayoutParams.UNSET
this.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
}