I need to implement animations in RecyclerView which has a slide up effect for every item. I have searched for this and tried many different things. It works when you scroll but it is not working when the page loads for the first time. I have uploaded a video on this link.
I have tried this code
#Override
public void onBindViewHolder(ViewHolderHelper holder, int position, List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
setAnimation(holder.itemView, position);
}
private void setAnimation(View viewToAnimate, int position) {
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(mcontext, R.anim.slide_in_bottom_list_item);
animation.setInterpolator(new AccelerateInterpolator());
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
Also tried
animation.setStartOffset(position * 100);
but that is missing an item on fast scrolling
Can anyone help me ?
After searching on internet i decided to do a hacky solution.
animation.setStartOffset(position * 100);
Many post has this suggestion. But this is messing when you scroll fast in the recyclerview.
So i decided to do this offset for onlt first few visible items and then animation applies to other rows correctly without offset.
#Override
public void onBindViewHolder(ViewHolderHelper holder, int position, List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
setAnimation(holder.itemView, position);
}
private void setAnimation(View viewToAnimate, int position)
{
if (position > lastPosition)
{
Animation animation = AnimationUtils.loadAnimation(mcontext, R.anim.slide_in_bottom_list_item);
animation.setInterpolator(new DecelerateInterpolator());
if(position < 4)
animation.setStartOffset(position * 200);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
Hope this helps someone.
Related
I found and research many way and to custom recycler view item animate when scrolling (space between every item will stretch when scrolling) like this gif, even I referred to many related library such as recyclerview-animators or RecyclerViewItemAnimators or creating Animation for each holder onBindViewHolder(). However, all ways above just create animation in the first time load view, can not implement like what animation I want to create. So anyone has done something like that, please help me to resolve that problem. Thank you so much.
EDIT:
I tried to implement OnScrollListener method as #Android recommended. But it do not work properly. This code below show how I implemented them:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int lastPosition = layoutManager.findLastCompletelyVisibleItemPosition();
int firstPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
for (int i = firstPosition; i <= lastPosition; i++) {
if (dy < 0) {
AnimationUtils.animate(recyclerView.findViewHolderForAdapterPosition(i), false);
} else {
AnimationUtils.animate(recyclerView.findViewHolderForAdapterPosition(i), true);
}
}
}
});
public class AnimationUtils {
public static void animate(RecyclerView.ViewHolder viewHolder, boolean isGoDown){
ObjectAnimator translateY = ObjectAnimator.ofFloat(viewHolder.itemView, "translationY", isGoDown == true ? 300 : -300, 0);
translateY.setDuration(300);
translateY.setInterpolator(new DecelerateInterpolator(2f));
translateY.start();
}
}
This is the animation example.
You define the lastPosition
private int lastPosition = -1;
private void setAnimation(View viewToAnimate, int position) {
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
animation.setDuration(1000);
viewToAnimate.startAnimation(animation);
lastPosition = position;
} else if ( position < lastPosition) {
Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
And then only you call the method onBindViewHolder
setAnimation(viewHolder.itemView, position);
You can animate RecyclerView item like this.
All you need is, to write an animate method and call that method in onBindViewHolder() method.
Here is a example for Fading the RecyclerView item
Method
public void setFadeAnimation(View view) {
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(500);
view.startAnimation(anim);
}
Call this method
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
setFadeAnimation(holder.itemView);
........
}
I implemented an animation for the item holder. However, When I tried to slide the view up and down, I noticed that the view items sometimes get animated and sometimes they don't, basically it's very random. Is there a cause for this?
private static void transition(RecyclerView.ViewHolder holder) {
ObjectAnimator animator = ObjectAnimator.ofFloat(holder.itemView, "translationX", -150, 0);
animator.setDuration(500);
animator.start();
}
#Override
public void onBindViewHolder(MovieView holder, int position) {
.........
.........
transition(holder);
}
I have some code here (which I did not write) which I need to fix. Here's the required flow:
User clicks on an item in a ListView
The item expands to show a footer which is otherwise hidden
If another list item is expanded, it is shrunk back to normal size (so that only 1 item is expanded at a time).
My problem: When tapping an item which is not expanded, nothing happens. The 2nd time, the item expands, tapping again shrinks it, then once again the 1st tap does nothing and so on.
Of course, I'm trying to eliminate the 1st redundant tap which does nothing.
Another interesting side-effect: When I tap an item the 1st time, nothing happens, then I will tap a DIFFERENT item once, and both the items will expand together.
I've been over the code for quite a while now and I can't see what's causing this.
Here's the code:
Setting the listener on the ListView:
productsListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> list, View view,int position, long id)
{
if (lastSelectedPosition == -1) {
lastSelectedPosition = position;
} else if (lastSelectedPosition == position) {
lastSelectedPosition = -1;
} else {
lastSelectedPosition = position;
}
View child;
ProductItemView tag;
for (int i = 0; i < productsListView.getChildCount(); i++) {
child = productsListView.getChildAt(i);
tag = (ProductItemView) child.getTag();
tag.onSomeListItemClicked(position);
productsListView.smoothScrollToPosition(position);
}
}
});
The list view's adapter:
public class ProductsCursorAdapter extends CursorAdapter {
public ProductsCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
ProductItemView item = null;
int pos = cursor.getPosition();
Log.d("BookListFragment", "BookListFragment: Position is: " + pos);
item = new ProductItemView(getActivity(), cursor.getPosition(), view, new ProductDAO(cursor));
view.setTag(item);
item.setContainer(BookListFragment.this, BookListFragment.this);
if (lastSelectedPosition == cursor.getPosition()) {
item.openedFooter();
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View view = (View) getActivity().getLayoutInflater().inflate(R.layout.course_list_item, null);
return view;
}
}
Relevant code inside ProductItemView:
public void onSomeListItemClicked(int position)
{
if (m_position == position)
{
Log.i("ProductItemView", "Animate footer for position: " + m_position);
animateFooter(position);
}
else
{
Log.i("ProductItemView", "Hide footer for position: " + m_position);
hideFooter(position);
}
}
public void showFooter(int position) {
if (!isFooterVisible())
{
animateFooter(position);
}
}
public void hideFooter(int position)
{
Log.i("ProductItemView", "Hide called for position: " + m_position);
if (isFooterVisible() && position != m_position)
{
animateFooter(position);
}
}
public void animateFooter(final int position)
{
if (footer != null && (m_footerExpandAnim == null || m_footerExpandAnim.hasEnded()))
{
Log.i("ProductItemView", "Animating footer for position: " + m_position);
isFooterVisible=!isFooterVisible;
m_footerExpandAnim = new ExpandAnimation(footer, 200, animationDelegate, position);
footer.startAnimation(m_footerExpandAnim);
}
}
ExpandAnimation:
public ExpandAnimation(View view, int duration, AnimationDelegate delegate, int position) {
this.position = position;
this.delegate = delegate;
setDuration(duration);
mAnimatedView = view;
mViewLayoutParams = (LayoutParams) view.getLayoutParams();
// decide to show or hide the view
mIsVisibleAfter = (view.getVisibility() == View.VISIBLE);
mMarginStart = mViewLayoutParams.bottomMargin;
mMarginEnd = (mMarginStart == 0 ? (-view.getHeight()) : 0);
mAnimatedView.clearAnimation();
Log.i("ExpandAnimation", "Margin Start = " + mMarginStart + ", Margin End = " + mMarginEnd);
//view.setVisibility(View.VISIBLE);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
Log.i("ExpandAnimation", "InterpolatedTime: " + interpolatedTime);
if (interpolatedTime < 1.0f) {
// Calculating the new bottom margin, and setting it
mViewLayoutParams.bottomMargin = mMarginStart
+ (int) ((float)(mMarginEnd - mMarginStart) * interpolatedTime);
mAnimatedView.setLayoutParams(mViewLayoutParams);
// Invalidating the layout, making us seeing the changes we made
mAnimatedView.requestLayout();
mAnimatedView.postInvalidate();
// Making sure we didn't run the ending before (it happens!)
} else if (!mWasEndedAlready) {
mViewLayoutParams.bottomMargin = mMarginEnd;
mAnimatedView.setLayoutParams(mViewLayoutParams);
mAnimatedView.requestLayout();
mAnimatedView.postInvalidate();
if (mIsVisibleAfter) {
//mAnimatedView.setVisibility(View.GONE);
}
mWasEndedAlready = true;
}
if(delegate!=null){
delegate.animationDidEnd(position);
}
}
Some things I've noticed:
The 1st time the item is clicked, the ExpandAnimation's constructor is indeed called, but the logs from the applyTransformation method aren't printed.
The 2nd time the item is clicked, the ExpandAnimation's constructor is called, but the mMarginStart value is not what it should be (randomly between -60 to -80 instead of -100), but then the logs in the applyTransformation are printed properly.
If you need any more code, let me know. Any ideas would help.
As I mentioned, this is not my code - I'm trying to edit code which a developer who has since left wrote. If it were up to me, this entire thing would'v been written very differently. I require a solution which involves minimal changes to the code structure.
Okay, I found the problem.
The clue was that I noticed that after the 1st click which "did nothing", if I scrolled the list slightly, the item I clicked would suddenly expand. This told me that the ListView was, for some reason, preventing its child views from performing UI operations.
I added a postInvalidate call on the list on the OnItemClick listener, and everything works as expected.
Interesting.
How to do scrolling effect like twitter when scroll Up hide viewpager tab (Home, Discover, activity). Or effect like facebook scrolling, while scroll up hide option view(status, photo, checkin) when scroll down show option view. Any example link will do please help.
Easy solution:
public abstract class OnScrollObserver implements AbsListView.OnScrollListener {
public abstract void onScrollUp();
public abstract void onScrollDown();
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
int last = 0;
boolean control = true;
#Override
public void onScroll(AbsListView view, int current, int visibles, int total) {
if (current < last && !control) {
onScrollUp();
control = true;
} else if (current > last && control) {
onScrollDown();
control = false;
}
last = current;
}
Usage:
listView.setOnScrollListener(new OnScrollObserver() {
#Override
public void onScrollUp() {
}
#Override
public void onScrollDown() {
}
});
EDIT: better, you have this library https://github.com/ksoichiro/Android-ObservableScrollView
You can look at this https://github.com/LarsWerkman/QuickReturnListView
Source: https://stackoverflow.com/a/25304575/244702
That's my own implementation:
notice:
View to be hidden should be fixed height
We are not hiding the view by Visiblity.GONE
We are setting the final height to 0px
Here is the code:
//Your view which you would like to animate
final RelativeLayout yourViewToHide = (yourViewToHideativeLayout) findViewById(R.id.topWrapper);
//The initial height of that view
final int initialViewHeight = yourViewToHide.getLayoutParams().height;
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//Try catch block for NullPointerExceptions
try{
//Here is a simple delay. If user scrolls ListView from the top of the screen to the bottom then continue
if(firstVisibleItem % visibleItemCount == 0) {
//Here we initialize the animator, doesn't matter what values You will type in
ValueAnimator animator = ValueAnimator.ofInt(0, 1);
//if Scrolling up
if (fastScrollSB.getProgress() > view.getFirstVisiblePosition()){
//Getting actual yourViewToHide params
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
if (!animator.isRunning()) {
//Setting animation from actual value to the initial yourViewToHide height)
animator.setIntValues(params.height, initialViewHeight);
//Animation duration
animator.setDuration(500);
//In this listener we update the view
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
yourViewToHide.setLayoutParams(params);
}
});
//Starting the animation
animator.start();
}
System.out.println("Scrolling up!");
//If not scrolling
} else if (fastScrollSB.getProgress() == view.getFirstVisiblePosition()) {
System.out.println("Not Scrolling!");
//If scrolling down
} else if (fastScrollSB.getProgress() < view.getFirstVisiblePosition()){
//Getting actual yourViewToHide params
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
if (!animator.isRunning()) {
//Setting animation from actual value to the target value (here 0, because we're hiding the view)
animator.setIntValues(params.height, 0);
//Animation duration
animator.setDuration(500);
//In this listener we update the view
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = yourViewToHide.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
yourViewToHide.setLayoutParams(params);
}
});
//Starting the animation
animator.start();
}
System.out.println("Scrolling down!");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
});`
Hope it fits your needs :)
there is no quick example for this. But what you can do is keep track which way you are scrolling and show or hide the view accordingly
For example get first visible position of the ListView keep track of this and if it is smaller then before you know you are scrolling up this way you can show the view. In case it is bigger then hide the view.
This is a simple approach in case you want to have more precise you need to work with onTouchListeners and y coordinates of the movement.
http://developer.android.com/reference/android/widget/ListView.html
I have a ListView with a footer added with listview.addFooterView(footerView);
All works as expected excepted in one case: when my listview's items doesn't fill the whole screen, I would like the footer to be at the bottom of the screen, instead of being in the middle. Is there a way to do this easily? Or should I change my layout?
Thanks
EDIT: that might help (this is what I want)
If you want it to always be at the bottom of the screen, no matter how long your ListView is, then get rid of listview.addFooterView(footerView); and use a RelativeLayout. Give yourListView` the property
android:layout_alignParentTop="true"
and give the property to your footer
android:layout_alignParentBottom="true"
If this doesn't solve your problem then please be a little more specific about what you want and provide a picture of what you want if possible.
Edit
After reading the comments this might work. There might be an easier way but you could do something like
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{
// set your footer on the ListView
}
else
{
footerView.setVisibility(View.VISIBLE);
}
}
footerView would be a custom layout that you would create with the properties I referenced above. This should set that to visible if the items aren't more than can fit on the screen. If they are more than can fit then you apply the footer view on the ListView as you are now. This might not be the best way but its the first thing that comes to mind. You would run this code just before you set the Adapter.
You cannot use ListView footer as footer for the whole layout.
You're better off with RelativeLayout as root element for your layout, and then a direct child of it containing the footer view with the attribute:
android:layout_alignParentBottom="true"
In addition to #codeMagic response, you could add a listener to check when your adapter gets updated and then update the footer
registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
updateSmartFooter();
}
});
where updateSmartFooter is the function he described
private void updateSmartFooter {
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{
// set your footer on the ListView
}
else
{
footerView.setVisibility(View.VISIBLE);
}
}
}
}
After spent a lot of time to research, I found the best solution for it.
Please have a look at: https://stackoverflow.com/a/38890559/6166660
and https://github.com/JohnKuper/recyclerview-sticky-footer
For details:
Create a StickyFooterItemDecoration extends RecyclerView.ItemDecoration like the example code below.
After that, set ItemDecoration to your recyclerView:
recyclerListView.addItemDecoration(new StickyFooterItemDecoration());
---------------------------------------
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
private static final int OFF_SCREEN_OFFSET = 5000;
#Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
int adapterItemCount = parent.getAdapter().getItemCount();
if (isFooter(parent, view, adapterItemCount)) {
if (view.getHeight() == 0 && state.didStructureChange()) {
hideFooterAndUpdate(outRect, view, parent);
} else {
outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
}
}
}
private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
footerView.post(new Runnable() {
#Override
public void run() {
parent.getAdapter().notifyDataSetChanged();
}
});
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
totalHeight += parent.getChildAt(i).getHeight();
}
return totalHeight + footerView.getHeight();
}
private boolean isFooter(RecyclerView parent, View view, int itemCount) {
return parent.getChildAdapterPosition(view) == itemCount - 1;
}
}