I successfully added onPreDraw animations to a RecyclerView using the following code. I call this function immediately after the adapter is set.
public void initialRVAnimation() {
rvAnims = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
rv.getViewTreeObserver().removeOnPreDrawListener(this);
for (int i = 0; i < rv.getChildCount(); i++) {
View v = rv.getChildAt(i);
v.setTranslationY(Utils.getScreenHeight(LiveThreadActivity.this));
v.animate()
.setStartDelay(50 * i)
.translationY(0)
.setInterpolator(new DecelerateInterpolator(3.f))
.setDuration(700)
.start();
}
return true;
}
};
rv.getViewTreeObserver().addOnPreDrawListener(rvAnims);
}
However, this causes an issue when adding items to the RecyclerView. The items seem to inherit the start delay specified in the animation and when a new item is added to the top of the RecyclerView, rather than all the items below moving down at once, they move in a staggered way. Here's a video to demonstrate the problem. Here's the normal animation when adding items when not using the onPreDraw animation.
Items are added to the RecyclerView using the following code.
public void addItem(View v) {
data.add(1, new Comment());
adapter.notifyItemInserted(1);
}
Related
I want to make a RecyclerView that scrolls infinitely while also being able to scroll to an item programmatically.
At the moment I've made the RecyclerView loop infinitely using this hacky method: https://stackoverflow.com/a/31254146/7443375
i.e. Overriding my adapter
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
Getting position of item in my adapter like so:
int positionInList = position % fragmentList.size();
And then initializing my RecyclerView's scroll position like so:
recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
However, in my Fragment that has the RecyclerView, I want to be able to scroll to a specific item in my list (i.e. item 3 out of a list of 10 items). When I call
recyclerView.getLayoutManager().scrollToPosition(2);
The list goes into an infinite scroll. I can't figure out how to go to the specific item itself.
Furthermore, how can I make sure that the specific item is centered in the screen? I am using LinearSnapHelper to snap the items in the center of the screen as I scroll, but LinearSnapHelper does not seem to work when setting positions programmatically.
Try this way..
recyclerView.post(new Runnable() {
#Override
public void run() {
new CountDownTimer(Integer.MAX_VALUE, 20 ) {
public void onTick(long millis) {
recyclerView.scrollBy(0, 20);
}
public void onFinish() {
}
}.start();
}
});
I have a layout that has several cardView, that have recyclerViews in them
in the beginning the first recyclerview is empty, so the card doesn't show.
later on, the recyclerview gets populated, so I want it to have a nice animation to it.
I'm trying to move the other cards down, and then fade in the card.
to do that I set the database of the recyclerView, and then I attach a OnPreDrawListener to the cardview around it, so I can get the height of the view, then I set the view to GONE and run a transationY animation on the card below it.
Thing is that when I call getMeasuredHeight I get 0.
It's almost as the notifyDatasetChanged() only happens in the next frame, so the view didn't get it's new height yet.
here is my code:
private void runSearchAnimation(List<Route> searchResult) {
if(rvSearchResults.getMeasuredHeight() == 0) {
Log.i(TAG, "height is 0");
resultsAdapter.setDatabase(searchResult);
cvSearchResults.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
cvSearchResults.getViewTreeObserver().removeOnPreDrawListener(this);
Log.i(TAG, "cvSearchResults.getMeasuredHeight() = " + cvSearchResults.getMeasuredHeight());
cvSearchResults.setVisibility(View.GONE);
ObjectAnimator contentAnim = ObjectAnimator.ofFloat(cvRecentSearches,
"translationY", cvRecentSearches.getTranslationY(), cvSearchResults.getMeasuredHeight());
contentAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
Log.i(TAG, "animation started");
}
#Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG, "animation ended");
cvSearchResults.setVisibility(View.VISIBLE);
}
});
contentAnim.setDuration(300);
contentAnim.start();
return true;
}
});
}
}
Does anyone have an idea on how to solve this?
I believe that RecyclerView supports such animations via RecyclerView.ItemAnimator.
See the docs.
so after some thinking I came up with this easy solution:
cvSearchResults.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(cvSearchResults.getMeasuredHeight() != 0) {
cvSearchResults.getViewTreeObserver().removeOnPreDrawListener(this);
Since I will keep getting my onPreDraw method called until I will remove the listener, I remove it only when I know the view has been measured with the recyclerview data I gave it
normaly scrolling down within a Testcase I could use this peace of code:
onView( withId( R.id.button)).perform( scrollTo(), click());
However in this test i want to scroll down a Listview, where the childelements are loaded dynamically (I dont know how many there are), when the view is scrolled down. Is there anything like scroll to bottom in espresso like in Robotium?
My workarround:
int [] childCount = getListElements();
for(int i = 0;i<childCount[0];i++) {
onData(anything()).inAdapterView(withId(R.id.ScheduleOrderListViewListView)).atPosition(i).perform(click());
childCount = getListElements();
}
with
public int[] getListElements(){
final int[] counts = new int[1];
onView(withId(R.id.ScheduleOrderListViewListView)).check(matches(new TypeSafeMatcher<View>() {
#Override
public boolean matchesSafely(View view) {
ListView listView = (ListView) view;
counts[0] = listView.getCount();
return true;
}
#Override
public void describeTo(Description description) {
}
}));
return counts;
You should use onData() to interact with ListView elements. If you want to scroll to the bottom, just scroll to the last item. For example:
onData(instanceOf(ListData.class))
.atPosition(INITIAL_LIST_SIZE - 1)
.perform(scrollTo());
So in my android app I have a HorizontalScrollView that will display images. All images are downloaded an added to the View before it is avalible to the user. However when it does apear I want each image to animate in seperatly. Ive tried a LayoutTransition object attached to my layout with this code to show the views:
transition = new LayoutTransition();
transition.setStagger(LayoutTransition.APPEARING, 500);
transition.setDuration(1000);
transition.setAnimateParentHierarchy(true);
for (int i = 0; i < mGallery.getChildCount(); i++) {
mGallery.getChildAt(i).setVisibility(View.VISIBLE);
transition.showChild(mGallery, mGallery.getChildAt(i));
}
I have also tried this method using the the AnimationEndListener, and a recursive animateView() method
private void animateView(final int index) {
if (mGallery.getChildAt(index) == null)
return;
final View child = mGallery.getChildAt(index);
final Animation an = AnimationUtils.loadAnimation(mRootView.getContext(), R.anim.slideup);
AnimationEndListener listener = new AnimationEndListener() {
#Override
public void onAnimationEnd(Animation animation) {
animateView(index + 1);
}
};
an.setAnimationListener(listener);
child.setAnimation(an);
child.setVisibility(View.VISIBLE);
an.start();
}
The first method is preferable however it always animates in all my images at the same time. The second method kind of works, however it will apply the animation to the first view and the all subsequent views will appear in sequence without the animation playing.
I'm using
<ExpandableListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ExpandableListView>
i want add animation slide for child when onclick parent . So How can i do ?
Final Update
It's been quite a while since I wrote this answer. Since then a lot has changed. The biggest change is with the introduction of RecyclerView that makes animating a list or grid easy. I highly recommend switching over to RecyclerViews if you can. For those who can't I will see what I can do regarding fixing the bugs for my library.
Original answer
I actually do not like the popular implementation of an animated ExpandableListView that simply uses a ListView with an expand animation because in my use case, each of my groups had a lot of children, therefore it was not feasible to use a normal ListView as the child views will not be recycled and the memory usage will be huge with poor performance. Instead, I went with a much more difficult but more scalable and flexible approach.
I extended the ExpandableListView class and overrode the onCollapse and onExpand functions, I also created a subclass of a BaseExpandableListAdapter called AnimatedExpandableListAdapter. Inside the adapter, I overrode the getChildView function and made the function final so that the function cannot be overrode again. Instead I provided another function called getRealChildView for subclasses to override to provide a real child view. I then added an animation flag to the class and made getChildView return a dummy view if the animation flag was set and the real view if the flag was not set. Now with the stage set I do the following for onExpand:
Set the animation flag in the adapter and tell the adapter which group is expanding.
Call notifyDataSetChanged() (forces the adapter to call getChildView() for all views on screen).
The adapter (in animation mode) will then create a dummy view for the expanding group that has initial height 0. The adapter will then get the real child views and pass these views to the dummy view.
The dummy view will then start to draw the real child views within it's own onDraw() function.
The adapter will kick off an animation loop that will expand the dummy view until it is of the right size. It will also set an animation listener so that it can clear the animation flag once the animation completes and will call notifyDataSetChanged() as well.
Finally with all of this done, I was able to not only get the desired animation effect but also the desired performance as this method will work with group with over 100 children.
For the collapsing animation, a little more work needs to be done to get this all setup and running. In particular, when you override onCollapse, you do not want to call the parent's function as it will collapse the group immediately leaving you no chance to play an animation. Instead you want to call super.onCollapse at the end of the collapse animation.
UPDATE:
I spent some time this weekend to rewrite my implementation of this AnimatedExpandableListView and I'm releasing the source with an example usage here:
https://github.com/idunnololz/AnimatedExpandableListView/
animateLayoutChanges adds auto-animation
<ExpandableListView
android:animateLayoutChanges="true"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
#idunnololz solution works great. however i would like to add some code to collapse previously expanded group.
private int previousGroup=-1;
listView.setOnGroupClickListener(new OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
// We call collapseGroupWithAnimation(int) and
// expandGroupWithAnimation(int) to animate group
// expansion/collapse.
if (listView.isGroupExpanded(groupPosition)) {
listView.collapseGroupWithAnimation(groupPosition);
previousGroup=-1;
} else {
listView.expandGroupWithAnimation(groupPosition);
if(previousGroup!=-1){
listView.collapseGroupWithAnimation(previousGroup);
}
previousGroup=groupPosition;
}
return true;
}
});
#idunnololz solution is working great, but I experienced weird behavior with my custom layout for group. The expand operation was not executed properly, the collapse however worked perfect. I imported his test project and it worked just fine, so I realized the problem is with my custom layout. However when I was not able to locate the problem after some investigation, I decided to uncomment these lines of code in his AnimatedExpandListView:
if (lastGroup && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return expandGroup(groupPos, true);
}
which caused the problem (my app is aimed for Android 4.0+).
Found this snnipet not remebering where here in Stack Overflow. Have two basic static methods: expand(View v) and collapse(View v).
You only have to pass the view you want to hide show.
Note: I Don't recomend pass a view having wrap_content as height. May not work fine.
public class expand {
public static void expand(View view) {
view.setVisibility(View.VISIBLE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(widthSpec, heightSpec);
ValueAnimator mAnimator = slideAnimator(view, 0, view.getMeasuredHeight());
mAnimator.start();
}
public static void collapse(final View view) {
int finalHeight = view.getHeight();
ValueAnimator mAnimator = slideAnimator(view, finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimator.start();
}
private static ValueAnimator slideAnimator(final View v, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
layoutParams.height = value;
v.setLayoutParams(layoutParams);
}
});
return animator;
}
}