animateLayoutChanges does not work well with nested layout? - android

I have a nested layout like the following:
<LinearLayout> <!----Parent layout--->
<LinearLayout> <!-----child 1--->
...
</LinearLayout> <!----child 1 ended--->
<LinearLayout> <!-----child 2--->
...
</LinearLayout> <!----child 2 ended--->
</LinearLayout> <!----Parent endded--->
The problem I am having now is that since all my data items are within child 1 or child 2 Linearlayout, if I add or delete a item the child linearlayout will animated with the effect of animateLayoutChanges but the parent layout will not do any animation. (I have android:animateLayoutChanges set to true for all linear layouts). Especially when I delete an item within child 1 the animation effect becomes weird (basically child 2 will jump up while child 1 is still doing its animation).
Does anybody have any idea how to solve this?
Thanks
UPDATE
Shortly after I posted this question, I found this on android developer's site in the LayoutTransition API.
Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the interrelationship of the various levels of layout.
So does anyone have any work around suggestions for this issue?

The animateLayoutChanges property makes use of LayoutTransitions, which animate both the layout's children and, from Android 4.0 onward, ancestors in the layout hierarchy all the way to the top of the tree. In Honeycomb, only the layout's children will be animated. See this Android Developers Blog post for details.
Unfortunately, it seems that there's currently no simple way to have the siblings of a layout react to its LayoutTransitions. You could try using a TransitionListener to get notified when the layout's bounds are being changed, and move the sibling views accordingly using Animators. See Chet Haase's second answer in this Google+ post.
EDIT - Turns out there is a way. In Android 4.1+ (API level 16+) you can use a layout transition type CHANGING, which is disabled by default. To enable it in code:
ViewGroup layout = (ViewGroup) findViewById(R.id.yourLayout);
LayoutTransition layoutTransition = layout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
So, in your example, to have child 2 layout animated, you'd need to enable the CHANGING layout transformation for it. The transformation would then be applied when there is a change in the bounds of its parent.
See this DevBytes video for more details.

Ok, after digesting the first answer, I make it simple here, for those who don't get proper animation result when using android:animateLayoutChanges="true" in NESTED layout:
Make sure you add android:animateLayoutChanges="true" to the will-be-resized ViewGroup (LinearLayout/RelativeLayout/FrameLayout/CoordinatorLayout).
Use setVisibility() to control the visibility of your target View.
Listen carefully from here, add android:animateLayoutChanges="true" to the outer ViewGroup of your will-be-resized ViewGroup, this outer ViewGroup must be the one who wraps all the position-changing View affected by the animation.
Add following code in your Activity before the setVisibility(), here the rootLinearLayout is the outer ViewGroup I mentioned above:
LayoutTransition layoutTransition = rootLinearLayout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
Before:
After:
Reminder: If you miss the 3rd step, you will get null pointer exception.
Good luck!

As a Kotlin Extension
fun ViewGroup.forceLayoutChanges() {
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
}
Usage
someContainer.forceLayoutChanges()
Notes:
In my experience, this happens when the container is a deep nested layout. For some reason android:animateLayoutChanges="true" just doesn't work, but using this function will force it to work.

We had added the android:animateLayoutChanges attribute to our LinearLayout but the change didn’t trigger an animation. To fix that, use this code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((ViewGroup) findViewById(R.id.llRoot)).getLayoutTransition()
.enableTransitionType(LayoutTransition.CHANGING);
}
More details.

It seems that a delayed transition on the parent also works for animating. At least for me the following code gives a proper expand/collapse animation.
expandTrigger.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(parentLayout);
expanded = !expanded;
child1.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
});
For deeply nested layouts you sometimes might need to use a parent higher up in the hierarchy in the call to the TransitionManager.

I had a similar issue:
I was using a animatelayoutchanges in one of my activity with a recycler view, I also added some custom layout transition because I wanted to increase speed of the animation while an item disappears in the list. It was working fine when it was not in a nested layout.
I had used the same adapter for another recyclerview which was in a nested layout. It was not working and I tried all the above solutions, None worked for me.
The real reason was, I forgot to set
mTicketsListAdapter.setHasStableIds(true);
in the nested layout activity. And after setting setHasStableIds to true, the animations was working perfectly in the nested layout.

Related

Animation with TranslateY is holding Constraints

Before you start:
I'm using Kotlin
I'm using ConstraintLayout (switching to LinearLayout or RelativeLayout is not an option)
I have my layout as follows:
<TextView android:id="#+id/myRefer" .../>
<ScrollView
...
app:layout_constraintTop_toBottomOf="#id/myRefer">
...
</ScrollView>
And my animation is developed as follows:
myRefer.animate()
.translationY(-myRefer.height.toFloat())
.alpha(0.0f)
So far so good, no problem with the animation, everything goes as expected.
The problem is, I thought that from the moment I'm using TranslateY, the ScrollView would follow the new position reference (topToBottom). But it keeps a white hole (as if my TextView is still using the reference height)
Did I misunderstand the use of TranslateY, or am I using animate() incorrectly? How would I solve this problem for my ScrollView to follow the reference.
Note: Don't suggest using onAnimatonEnd and setting View.GONE, it just makes things worse.
I would just use android:animateLayoutChanges="true" on the layout. And then instead of animate... just call myRefer.visibility = View.VISIBLE or View.GONE. It will have the same effect and the Scrollview will adjust to the hidden element.

How do I set view visiibility in a ConstraintLayout?

I started using ConstraintLayout a few weeks ago and I am finding (I believe) that it has many bugs. One huge issue is that I cannot set view visibilities. The following code does not work:
<TextView
android:visibility="gone"
android:id="#+id/sampleView"
style="#style/RSTextView"
android:layout_width="0dp"
android:text="sample text"
android:textSize="20dp"
android:shadowColor="#00000000"
app:layout_constraintStart_toEndOf="#+id/privacyPolicyText"
app:layout_constraintTop_toBottomOf="#+id/enterAgreementText1" />
You can see I've marked it as "gone", but when I run and inspect the view using the layout inspector, visibility is STILL set to visible. I've had to come up with hacky solutions to this involving setting the alpha to 0 and isEnabled to false when I want a view to be gone and vice versa. When I try to set the visibility programmatically, I have the same issue:
sampleView.visibility = ConstraintLayout.GONE
This still results in a visible view. This is driving me nuts because I'm forced to make multi-line hacky solutions. Any help would be appreciated.
Be careful using Groups in Constraint Layout. Main things to note while using groups are
Group's visibility overrides normal view's visibility
If the group's visibility is unset, it defaults to visible and overrides your view's visibility.
While using Constraint Layout be careful in using Groups. I think almost all visibility issues in constraint layout are caused by groups.
I don't know if the below links I found are related to your problem:
https://issuetracker.google.com/issues/37151322
https://issuetracker.google.com/issues/37139335
https://issuetracker.google.com/issues/37138937
but they are all filed reports about problems/bugs concerning the visibility of views inside a ConstraintLayout.
Check them.
#Pavan Varma pointed out that Group visibility overrides the view's visibility. I thought that if the groups visibility was unset, it wouldn't override anything, but it still does. If you're having issues with visibility in ContraintLayouts, pay close attention to its group's visibility that overrides it. If the group's visibility is unset, it defaults to visible and overrides your view's visibility.

Seamless nested scrolling (Android)

We've all been advised against nesting views that contain a scrolling mechanism. However, in the latest Android release (5.0), the Phone app caught my attention with what seems to be a ListView inside of a ScrollView.
What really intrigued me was that the scrolling mechanism switched from the ScrollView to the ListView seamlessly.
Notice the content above the tabs is pushed out of view before the actual ListView begins scrolling.
I've tried duplicating this myself, but ended up unsuccessful. Here is the basic approach I was taking...
With a single, continuous touch event (no lifting of the finger) ...
As user scrolls, the ListView slowly covers up the ImageView. Once the ImageView is 100% covered and the ListView takes up the entire screen, the ListView begins to scroll.
I'm currently listening to touch events on the ListView and if the top has been reached, call requestDisallowInterceptTouchEvent on the ListView, i.e.
#Override
public boolean onTouch(View v, MotionEvent event) {
if (listViewAtTop) {
v.requestDisallowInterceptTouchEvent(true);
} else {
v.requestDisallowInterceptTouchEvent(false);
}
return false;
}
The switching scrolling context works, only if you lift your finger and continue scrolling.
Is there a different approach that will achieve the desired effect?
Android 5.0 Lollipop (API 21) added nested scrolling support.
From what I can tell, both ListView (AbsListView) and ScrollView support this now (if running on API 21), but it must be enabled on the scrolling views.
There are two ways, by calling
setNestedScrollingEnabled(true) or with the layout attribute android:nestedScrollingEnabled="true" (which is undocumented)
To learn about how it works, or to support this for a custom widget, the key methods are these:
onStartNestedScroll
onNestedScrollAccepted
onNestedPreScroll
onNestedScroll
onStopNestedScroll
Unfortunately, there is no guide or training which explains how this works other than the JavaDoc itself which is rather light and there are no examples other than ScrollView.
Add latest support package 'com.android.support:support-v4:22.1.1' to your project. And try this:
<android.support.v4.widget.NestedScrollView
android:id="#+id/nScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<FrameLayout ...>
<ListView ... />
</FrameLayout >
</android.support.v4.widget.NestedScrollView>
By default nested scrolling is Enabled.
While trying to figure out how to solve this issue myself, I found this question first; however, the answer didn't really go into too much detail. I did find a lot of good resources, so if anyone else finds themselves looking for this, I'll link them below. A term for this effect is "Sticky Scrolling".
An article talking about "Synchronized Scrolling".
http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
A good video showcasing some Android scrolling tricks, "Quick Return" and "Sticky Scrolling".
https://www.youtube.com/watch?v=PL9s0IJ9oiI
Code:
https://code.google.com/p/romannurik-code/source/browse/misc/scrolltricks
And lastly, here is another one showcasing the same effect using a listView instead of a ScrollView.
https://www.youtube.com/watch?v=rk-tLisxSgM
Code:
https://github.com/jatago/list_sticky_scroll_trick
I found an alternative 'trick' which is quite simple... Use only a ListView with an added transparent header.
I have been wanting to achieve the same effect as well. I came up finding a relevant library called ObservableScrollView in GitHub and it requires more work on the back-end via a TouchInterceptFramework but at least it did the job even for pre-lollipop devices. It also supports not only child scrollviews and listviews but also recyclerviews. Here's the link:
https://github.com/ksoichiro/Android-ObservableScrollView
I hope they consider nested scrolling for both lollipop and pre-lollipop devices as a part of their design standard soon. This is a good sign.
This is classic example of dummy layouts. Something not entirely obvious at first look. Basically the scenario is something like this.
Grey Area->FrameLayout
Followed by a listview that fills up the entire framelayout and followed by a imageview that overlaps the top half of a listview. The listview's first item is a dummy item and has a height identical to that of the imageview.
(Note: The actual data starts from the second element)
Next Step is easy
Translate the Imageview as per the scroll of the listview.
I suppose this is the best way to do that whilst avoiding nested scrolling
You can use the following combination of attributes on your ListView to achieve this:
<ImageView ... /> <!-- must be before ListView -->
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="..." <!-- height of imageView -->
android:clipToPadding="false"
...
/>
You don't have to manage any scrolling in your code at all, and it requires no header/dummy views in your list adapter.
I am using something like this and it works ok I think
scrollView.onScroll { x, y ->
Timber.d("ScrollView offset: ($x, $y)")
val height = dashboardChart.measuredHeight
val recyclerView = viewPager.findViewById<RecyclerView>(R.id.recyclerView)
if(y >= height) {
Timber.d("ScrollView enable nested scrolling!")
recyclerView.isNestedScrollingEnabled = true
} else {
Timber.d("ScrollView disable nested scrolling!")
recyclerView.isNestedScrollingEnabled = false
}
}
Where scrollView is parent I am listening onScroll event (it is extension underneath it is viewTreeObserver.addOnScrollListener). Then depending whether I've scrolled initial offset or not I am enabling/disabling child recyclerView (similary ListView or other scrollView) scrolling.

How to bring Animation to front: bringToFront and zAdjustment/Z-ordering

I have a LinearLayout with two views in it, next to each other. When a view is tapped upon, it animates to full screen. This goes fine for the view on the right, but it fails for the left view:
I am unable to animate the left view on top of the right view
Without corrupting my layout with bringToFront(). Adjusting the Z-order of my animation does not seem to work.
Not a solution: The problem is gone when I use "brintToFront()" on the left view, but this causes my layout to be completely corrupted afterwards, and there is no brintToBack() function or whatsoever. => brintToFront = not a good solution?
Adjusting the Z-order of my animation does not seem to work (does not change anything).
scaleAnimation.setZAdjustment(Animation.ZORDER_TOP);
translateAnimation.setZAdjustment(Animation.ZORDER_TOP);
AnimationSet set = new AnimationSet(true);
set.addAnimation(scaleAnimation);
set.addAnimation(translateAnimation);
set.setZAdjustment(AnimationSet.ZORDER_TOP);
myFrameLayout.startAnimation(set);
Why does Z-ordering not work as expected?
This should be possible if you extend LinearLayout and override the following method:
getChildDrawingOrder (int childCount, int i)
To make sure layout uses your function you need to call setChildrenDrawingOrderEnabled(true)
See ViewGroup javadoc
For your z reordering will apply you gonna need to request a new layout on the view, but try doing it before starting your animation:
AnimationSet set = new AnimationSet(true);
// init animation ...
scaledView.bringToFront();
scaledView.requestLayout();
myFrameLayout.startAnimation(set);
I guess there is no good answer to this.
I am now animating everything else on the screen too, so if view 1 has to grow larger on top of view 2 than I animate view 2 also, in such a way that it looks like view 1 is growing on top of view 2, while actually view 2 is getting smaller while view 1 is getting larger.
Quite a lot of work and a bad solution, but I guess the only solution.

is it possible to do transition animations when changing views in the same activity?

Suppose I have 2 XML files and my activity will setContentView the appropriate one based on some button press from the user. Is it possible to change the transition animation for the changing of content view?
So far I see super.overridePendingTransition() which is suitable for starting new activities, however my example does not start a new activity, it just changes the layout in the current one.
Mathias Lin has explained it very well.
You can always use default stock animations supplied by Android framework.
Heres an example code:
boolean isFirstXml=evaluatingConditionFunction();
LayoutInflater inflator=getLayoutInflater();
View view=inflator.inflate(isFirstXml?R.layout.myfirstxml:R.layout.myseconxml, null, false);
view.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_out_right));
setContentView(view);
Call this from any of your activity which holds your Parent View.
For custom animations you can visit developer docs. Heres the documentation link.
Yes, you can apply an animation on almost any view you like. Just via view.startAnimation(animation);
Take the outer viewgroup of your respective layout (content view) and apply the animation to it. Depending what kind of animation you want to do, it might make sense to inflate/load both layouts but hide one of them and then swap. Please specify what kind of transition you have in mind.
For example: if you do an alpha transition, you would run the alphaAnimation on the current layout, when when the animation ends (AnimationListener), you set the content view to the new layout, and fade the content back in, via another alphaAnimation.
A better solution is using ViewFlipper: it is a FrameLayout, that can do animations when changing the views.
<ViewFlipper
android:id="#+id/[your_id_here]"
android:inAnimation="..."
android:outAnimation="..."
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RelativeLayout
<!--Your first layout -->
</RelativeLayout>
<RelativeLayout
<!--Your second layout -->
</RelativeLayout>
</ViewFlipper>
Then, switch the views with setDisplayedChild(int) or showNext() or showPrevious. If you want to have different animation for left and right movement, you have to set inAnimation and outAnimation in the code before transition.
More complete example is here.

Categories

Resources