How can I add a slide in animation on my recycler view items one after the other. Like as the activity starts, the list items of the recycler view slides in one by one. I am using LinearLayoutManager
Not all at the same time should slide in. And not even while scrolling. Just at the time of activity creation.
I searched but didn't find anything.
I want to achieve something like this : https://youtu.be/Q8TXgCzxEnw?t=30s
I put together a sample app a couple of months ago that has a sequential slide in-slide out animation during reshuffles. A demo video is available here. It should give you some ideas.
A link to the most relevant class file is here, and I'll copy the code below.
public class AllNotesFragmentRecyclerView extends RecyclerView {
private static final int BASE_ANIMATION_TIME = 50;
private static final int MAX_ANIMATION_TIME_INCREMENT = 100;
private int screenWidth;
private int startX, finalX;
private int[] interpolatedAnimationTimes;
public AllNotesFragmentRecyclerView(Context context) {
super(context);
init(context);
}
public AllNotesFragmentRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public AllNotesFragmentRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
calculateScreenWidth(context);
startX = 0;
finalX = -(screenWidth);
}
private void calculateScreenWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
screenWidth = metrics.widthPixels;
}
private int calculateInterpolatedAnimationTime(int currentIndex, int maxIndex) {
float percentage = ((float)currentIndex/(float)maxIndex);
float increment = (float) MAX_ANIMATION_TIME_INCREMENT * percentage;
return (int) (BASE_ANIMATION_TIME + increment);
}
public void updateListOrder() {
createAnimatorSet();
}
private void createAnimatorSet() {
AnimatorSet set = new AnimatorSet();
ArrayList<Animator> animArrayList = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
ObjectAnimator anim = ObjectAnimator
.ofFloat(getChildAt(i), "translationX", finalX);
int duration = calculateInterpolatedAnimationTime(i, getChildCount());
anim.setDuration(duration);
anim.addListener(new RowAnimationListener(i, duration, startX));
animArrayList.add(anim);
}
set.setInterpolator(new AccelerateInterpolator());
set.playSequentially(animArrayList);
set.start();
}
private void animateOn(int childPosition, int duration, int targetValue) {
ObjectAnimator animator = ObjectAnimator
.ofFloat(getChildAt(childPosition), "translationX", targetValue);
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(duration);
animator.start();
}
//...
private class RowAnimationListener implements Animator.AnimatorListener {
private int position, duration, targetX;
public RowAnimationListener(int position, int duration, int targetX) {
this.position = position;
this.duration = duration;
this.targetX = targetX;
}
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
int currentItem = getLinearLayoutManager().findFirstVisibleItemPosition() + position;
getAdapter().notifyItemChanged(currentItem);
notifyRowsPeripheralToVisibleItemsDataChanged(position);
animateOn(position, duration, targetX);
}
#Override
public void onAnimationCancel(Animator animation) { }
#Override
public void onAnimationRepeat(Animator animation) { }
}
}
Finally I found a solution. In below snippet I will explain how to implement. It is simple and can be done on any existing working RecyclerView. I have explained everything in comments.
Here is the onCreate/onCreateView method (I have used this inside Fragment, You can change accordingly if needed):
RecyclerView recList = (RecyclerView) rootView.findViewById(R.id.event_list);
recList.setHasFixedSize(true);
LinearLayoutmanager llm = new LinearLayoutManager(getActivity().getApplicationContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
recList.setLayoutManager(llm);
// This is important. Setting recyclerView's alpha to zero.
// Basically this is just to hide recyclerview at start before binding data
// As setVisibility is not working on recycler view object.
recList.setAlpha(0);
// Create the EventAdapter with the result we got
// EventAdapter is my custom adapter class.
// you should set your adapter class
EventAdapter ea = new EventAdapter(eventResultList);
// Binding the Adapter to RecyclerView list to show the data
recList.setAdapter(ea);
// ********************* Animate at start ********************************
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// This will give me the initial first and last visible element's position.
// This is required as only this elements needs to be animated
// Start will be always zero in this case as we are calling in onCreate
int start = llm.findFirstVisibleItemPosition();
int end = llm.findLastVisibleItemPosition();
Log.i("Start: ", start + "");
Log.i("End: ", end + "");
// Multiplication factor
int DELAY = 50;
// Loop through all visible element
for (int i = start; i <= end; i++) {
Log.i("Animatining: ", i + "");
// Get View
View v = recList.findViewHolderForAdapterPosition(i).itemView;
// Hide that view initially
v.setAlpha(0);
// Setting animations: slide and alpha 0 to 1
PropertyValuesHolder slide = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 150, 0);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat(View.ALPHA, 0, 1);
ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(v, slide, alpha);
a.setDuration(300);
// It will set delay. As loop progress it will increment
// And it will look like items are appearing one by one.
// Not all at a time
a.setStartDelay(i * DELAY);
a.setInterpolator(new DecelerateInterpolator());
a.start();
}
// Set Recycler View visible as all visible are now hidden
// Animation will start, so set it visible
recList.setAlpha(1);
}
}, 50);
This is quite a small code without comments.
Some things needs an explanation:
Why hiding RecyclerView initially?
If RecyclerView is not hidden initially you will notice a blink initially before the animation starts. The reason for it is when you set a data adapter it will position it on its default positions and after the loop it starts animating. So in between while loop is running you will notice sudden blink in the RecyclerView that at first all are at its initial position and than suddenly animating.
So hiding it at first and than after loop completes and all visible positions animations are set with delays and started, we can show the RecyclerView. It makes sliding looks smooth.
The reason for hiding it with setAlpha(0) is as setVisibility() function is not working on the RecyclerView object.
How only visible elements will animate?
There are functions in the LayoutManager class to get the visible elements position. In LinearLayoutManager used findFirstVisibleItemPosition() to get the position of the first visible view from the recycler view data which is visible on screen. And the last visible view's position can be retried with findLastVisibleItemPosition(). So we can loop from the first view to last view and animate the initial views which are going to be on screen at start.
How delay Works?
As loop will progress from 0(start) to end it will set delay from 0,50,100,150,.. if DELAY variable is set to 50. So this will make first element start animating, second after 50ms delay, third after 100ms delay and so on. So it will look like they are coming in one by one. Not all together.
Create animation in anim/slide_in.xml file like below
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="#android:anim/decelerate_interpolator">
<translate
android:fromXDelta="100%" android:toXDelta="0%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="2000"/>
</set>
And then apply this animation on each view of RecyclerView in onBindViewHolder method.
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder vh = (ViewHolder) holder;
vh1.tv_header.setText(mList.get(position));
Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.rec_anim);
animation.setStartOffset(30 * position);//Provide delay here
holder.itemView.startAnimation(animation);
}
Related
In my project I want use ProgressBar and I want set smoothly countdown animation.
I write below codes for smoothly animation and when set 1000ms (1s) for duration show smoothly animation, but I want set 10000ms (10s) for duration.
When set 10000ms (10s) for duration not smoothly animation and show Fragmentary countdown.
But I want set 10000ms for duration and show smoothly countdown.
public class SimpleFrag extends Fragment {
TextView toolbar_title;
RelativeLayout toolbar;
private ProgressBar progressBar;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_simple, container, false);
ButterKnife.bind(this, view);
toolbar_title = getActivity().findViewById(R.id.toolbar_title);
toolbar = getActivity().findViewById(R.id.toolbar);
progressBar = view.findViewById(R.id.progress);
return view;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#SuppressLint("ObjectAnimatorBinding")
#Override
public void run() {
ProgressBarAnimation mProgressAnimation = new ProgressBarAnimation(progressBar, 79, 0);
mProgressAnimation.setDuration(10000);
progressBar.startAnimation(mProgressAnimation);
}
}, 50);
}
}
public class ProgressBarAnimation extends Animation {
private ProgressBar progressBar;
private float from;
private float to;
public ProgressBarAnimation(ProgressBar progressBar, float from, float to) {
super();
this.progressBar = progressBar;
this.from = from;
this.to = to;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float value = from + (to - from) * interpolatedTime;
progressBar.setProgress((int) value);
}
}
}
How can I it? please help me . Thanks all <3
Take what you have now, and delete your setUserVisibleHint() method and your ProgressBarAnimation class. Add this:
private void setUpObserver() {
progressBar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
startAnimation();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
progressBar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else {
progressBar.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
}
private void startAnimation() {
int width = progressBar.getWidth();
progressBar.setMax(width);
ValueAnimator animator = ValueAnimator.ofInt(0, width);
animator.setInterpolator(new LinearInterpolator());
animator.setStartDelay(0);
animator.setDuration(10_000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (int) valueAnimator.getAnimatedValue();
progressBar.setProgress(value);
}
});
animator.start();
}
Then, inside your onCreateView() method, call setUpObserver() right before you return view;
The setUpObserver() method uses an OnGlobalLayoutListener to make sure that the system waits until the ProgressBar is measured an laid out before starting the animation.
The startAnimation() method is what takes care of actually setting up and running the animation. You can modify the values passed to setStartDelay() and setDuration() as you see fit.
The trick to this solution is making sure that the maximum value for the progress bar is equal to its width, meaning that each increment of "progress" is equal to one pixel on the screen. The android framework animation takes care of making sure everything runs as smoothly as possible.
Edit
If you want to be able to control the start/end points for the animation (rather than just go from empty to full), make these changes:
Change the declaration of startAnimation() to this:
private void startAnimation(float startPercent, float endPercent)
Delete this line from inside startAnimation()
ValueAnimator animator = ValueAnimator.ofInt(0, width);
and add these lines instead:
int start = (int) (startPercent * width);
int end = (int) (endPercent * width);
ValueAnimator animator = ValueAnimator.ofInt(start, end);
Finally, call startAnimation() by passing in fractional percentages:
startAnimation(0.79f, 0.10f); // will animate from 79% to 10%
RecyclerView by default, does come with a nice deletion animation, as long as you setHasStableIds(true) and provide correct implementation on getItemId.
Recently, I had added divider into RecyclerView via https://stackoverflow.com/a/27037230/72437
The outcome looks as following
https://www.youtube.com/watch?v=u-2kPZwF_0w
https://youtu.be/c81OsFAL3zY (To make the dividers more visible when delete animation played, I temporary change the RecyclerView background to red)
The dividers are still visible, when deletion animation being played.
However, if I look at GMail example, when deletion animation being played, divider lines are no longer visible. They are being covered a solid color area.
https://www.youtube.com/watch?v=cLs7paU-BIg
May I know, how can I achieve the same effect as GMail, by not showing divider lines, when deletion animation played?
The solution is fairly easy. To animate a decoration, you can and should use view.getTranslation_() and view.getAlpha(). I wrote a blog post some time ago on this exact issue, you can read it here.
Translation and fading off
The default layout manager will fade views out (alpha) and translate them, when they get added or removed. You have to account for this in your decoration.
The idea is simple:
However you draw your decoration, apply the same alpha and translation to your drawing by using view.getAlpha() and view.getTranslationY().
Following your linked answer, it would have to be adapted like the following:
// translate
int top = child.getBottom() + params.bottomMargin + view.getTranslationY();
int bottom = top + mDivider.getIntrinsicHeight();
// apply alpha
mDivider.setAlpha((int) child.getAlpha() * 255f);
mDivider.setBounds(left + view.getTranslationX(), top,
right + view.getTranslationX(), bottom);
mDivider.draw(c);
A complete sample
I like to draw things myself, since I think drawing a line is less overhead than layouting a drawable, this would look like the following:
public class SeparatorDecoration extends RecyclerView.ItemDecoration {
private final Paint mPaint;
private final int mAlpha;
public SeparatorDecoration(#ColorInt int color, float width) {
mPaint = new Paint();
mPaint.setColor(color);
mPaint.setStrokeWidth(width);
mAlpha = mPaint.getAlpha();
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// we retrieve the position in the list
final int position = params.getViewAdapterPosition();
// add space for the separator to the bottom of every view but the last one
if (position < state.getItemCount()) {
outRect.set(0, 0, 0, (int) mPaint.getStrokeWidth()); // left, top, right, bottom
} else {
outRect.setEmpty(); // 0, 0, 0, 0
}
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// a line will draw half its size to top and bottom,
// hence the offset to place it correctly
final int offset = (int) (mPaint.getStrokeWidth() / 2);
// this will iterate over every visible view
for (int i = 0; i < parent.getChildCount(); i++) {
final View view = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// get the position
final int position = params.getViewAdapterPosition();
// and finally draw the separator
if (position < state.getItemCount()) {
// apply alpha to support animations
mPaint.setAlpha((int) (view.getAlpha() * mAlpha));
float positionY = view.getBottom() + offset + view.getTranslationY();
// do the drawing
c.drawLine(view.getLeft() + view.getTranslationX(),
positionY,
view.getRight() + view.getTranslationX(),
positionY,
mPaint);
}
}
}
}
Firstly, sorry for the massive answer size. However, I felt it necessary to include my entire test Activity so that you can see what I have done.
The issue
The issue that you have, is that the DividerItemDecoration has no idea of the state of your row. It does not know whether the item is being deleted.
For this reason, I made a POJO that we can use to contain an integer (that we use as both an itemId and a visual representation and a boolean indicating that this row is being deleted or not.
When you decide to delete entries (in this example adapter.notifyItemRangeRemoved(3, 8);), you must also set the associated Pojo to being deleted (in this example pojo.beingDeleted = true;).
The position of the divider when beingDeleted, is reset to the colour of the parent view. In order to cover up the divider.
I am not very fond of using the dataset itself to manage the state of its parent list. There is perhaps a better way.
The result visualized
The Activity:
public class MainActivity extends AppCompatActivity {
private static final int VERTICAL_ITEM_SPACE = 8;
private List<Pojo> mDataset = new ArrayList<Pojo>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i = 0; i < 30; i++) {
mDataset.add(new Pojo(i));
}
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new VerticalSpaceItemDecoration(VERTICAL_ITEM_SPACE));
recyclerView.addItemDecoration(new DividerItemDecoration(this));
RecyclerView.ItemAnimator ia = recyclerView.getItemAnimator();
ia.setRemoveDuration(4000);
final Adapter adapter = new Adapter(mDataset);
recyclerView.setAdapter(adapter);
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
#Override
public void run() {
int index = 0;
Iterator<Pojo> it = mDataset.iterator();
while(it.hasNext()) {
Pojo pojo = it.next();
if(index >= 3 && index <= 10) {
pojo.beingDeleted = true;
it.remove();
}
index++;
}
adapter.notifyItemRangeRemoved(3, 8);
}
}, 2000);
}
public class Adapter extends RecyclerView.Adapter<Holder> {
private List<Pojo> mDataset;
public Adapter(#NonNull final List<Pojo> dataset) {
setHasStableIds(true);
mDataset = dataset;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_cell, parent, false);
return new Holder(view);
}
#Override
public void onBindViewHolder(final Holder holder, final int position) {
final Pojo data = mDataset.get(position);
holder.itemView.setTag(data);
holder.textView.setText("Test "+data.dataItem);
}
#Override
public long getItemId(int position) {
return mDataset.get(position).dataItem;
}
#Override
public int getItemCount() {
return mDataset.size();
}
}
public class Holder extends RecyclerView.ViewHolder {
public TextView textView;
public Holder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
public class Pojo {
public int dataItem;
public boolean beingDeleted = false;
public Pojo(int dataItem) {
this.dataItem = dataItem;
}
}
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Paint mOverwritePaint;
private Drawable mDivider;
/**
* Default divider will be used
*/
public DividerItemDecoration(Context context) {
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
mDivider = styledAttributes.getDrawable(0);
styledAttributes.recycle();
initializePaint();
}
/**
* Custom divider will be used
*/
public DividerItemDecoration(Context context, int resId) {
mDivider = ContextCompat.getDrawable(context, resId);
initializePaint();
}
private void initializePaint() {
mOverwritePaint = new Paint();
mOverwritePaint.setColor(ContextCompat.getColor(MainActivity.this, android.R.color.background_light));
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
Pojo item = (Pojo) child.getTag();
if(item.beingDeleted) {
c.drawRect(left, top, right, bottom, mOverwritePaint);
} else {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
}
public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int mVerticalSpaceHeight;
public VerticalSpaceItemDecoration(int mVerticalSpaceHeight) {
this.mVerticalSpaceHeight = mVerticalSpaceHeight;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
outRect.bottom = mVerticalSpaceHeight;
}
}
}
The Activity Layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:background="#android:color/background_light"
tools:context="test.dae.myapplication.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
The RecyclerView "row" Layout
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/text"
android:padding="8dp">
</TextView>
I think the ItemDecorator you use to draw a divider after every row is messing things up when swipe to delete is performed.
Instead of Using ItemDecorator to draw a Divider in a recyclerview, add a view at the end of your RecyclerView child layout design.which will draw a divider line like ItemDecorator.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<!-- child layout Design !-->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#android:color/darker_gray"
android:layout_gravity="bottom"
/>
</Linearlayout>
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 currently have a button that when clicked an animation begins that shows a LinearLayout above the button. The LinearLayout is directly above it. In the xml file the LinearLayouts visibility is set to GONE. So when the button is clicked the visibility is set to VISIBLE. Then the animation begins. The animation is a slidedown animation. Everything works perfectly. But When the button is clicked the button jumps to the bottom of where the LinearLayout ends. Even though the LinearLayout is still going through the animation. How can I make the button move with the LinearLayout animation? I want everything to be a smooth transition. But the button jumps and it doesn't look very smooth.
LInearLayout Animation
<?xml version="1.0" encoding="utf-8"?>
<!-- slide down -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<scale
android:duration="500"
android:fromXScale="1.0"
android:fromYScale="0.0"
android:interpolator="#android:anim/linear_interpolator"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>
I just ran into a very similar situation today.
I used a custom animation that re sizes the view.
The required height is measured and saved. Initially the height is set to 0 and it grows as the animation goes on. Finally it reaches the measured height and then it is set to wrap content as it was originally.
public static void expand(final View v) {
v.measure(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
final int targetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? LayoutParams.WRAP_CONTENT
: (int)(targetHeight * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// 1dp/ms
a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
v should be the LinearLayout you would like to animate.
You can choose a fixed time duration if you preffer.
source: https://stackoverflow.com/a/13381228/1646326
Use custom animation, it's very easy you can change whatever you want in applytransformation,
interpolatedTime - this is current position like in % from start to end animation (and has float value from 0 to 1, so here using this interpolatedTime you can iterate anything you can imagine) ;)
static class HeightAnimation extends Animation{
private View view;
private int mViewHeightFrom;
private int mViewHeightTo;
public HeightAnimation(View view, int heightFrom, int heightTo){
this.view = view;
this.mViewHeightFrom = heightFrom;
this.mViewHeightTo = heightTo;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int)((mViewHeightTo - mViewHeightFrom)*interpolatedTime+0.5));
view.setLayoutParams(params);
}
#Override
public boolean willChangeBounds() {
return true;
}
#Override
public boolean isFillEnabled() {
return true;
}
}
and the usage:
public static void applyAnimationHeightTransformation(Context context, View view, int viewHeightFrom, int viewHeightTo, int duration, int animationOffsetMilisec){
HeightAnimation anim = new HeightAnimation(view, viewHeightFrom, viewHeightTo);
anim.setStartOffset(animationOffsetMilisec);
anim.setDuration(duration);
//anim.setInterpolator(new OvershootInterpolator()); // here interpolators can be used
if(view != null) {
view.setAnimation(anim);
view.startAnimation(anim);
}
}
for height to make easier using - use values in dp via transform to pixels:
public static int dpToPx(int dp) {
return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, YourApplication.getMainContext().getResources().getDisplayMetrics()));
}
I need to animate the following scenario:
I have a ListView, each cell have 3 Layouts in it:
A preview_layout (blue): contains a preview of the contents of the contents_layout.
A contents_layout (green): contains a long text with some buttons.
A wrapper_layout (red): contains preview_layout and contents_layout.
The contents_layout is set with visibility to "gone", so only the preview_layout are visible in the list.
When a cell of the ListView is pressed I need to show the contents_layout with a slide down animation.
Until now I used the following solution:
In the getView of the ListView:
// Preview_layouts height
int hPreview = 70;
// Views
final View previewView = rowView.findViewById(R.id.preview);
final View contentsView = rowView.findViewById(R.id.contents);
// On previewView click
previewView.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (!contentsView.isShown()) {
// Close all the contents_layout of the ListView (menuListView)
for (int i=0; i < menuListView.getChildCount(); i++) {
View v = menuListView.getChildAt(i).findViewById(R.id.contents);
if (v.isShown()) { v.startAnimation(new SlideUpAnimation(v, hPreview)); }
}
// Slide down the selected contents_layout (contentsView)
contentsView.startAnimation(new SlideDownAnimation(contentsView, hPreview));
}
}
});
The SlideDownAnimation class:
public class SlideDownAnimation extends Animation {
private View target;
private LayoutParams targetResize;
private int mFromHeight, mToHeight;
public SlideDownAnimation( View targetToSlideDown, int fromHeight ) {
// Show the contents_layout target
target = targetToSlideDown;
target.setVisibility(View.VISIBLE);
// Animation property
setDuration(500);
setInterpolator(new DecelerateInterpolator());
// Target
targetResize = targetToSlideDown.getLayoutParams();
mFromHeight = fromHeight;
mToHeight = targetResize.height - fromHeight;
targetResize.height = 1;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// Set the Alpha to fade in the contents_layout target
t.setAlpha(interpolatedTime);
// Set the height to slide down the contents_layout target
targetResize.height = (int) (mToHeight * interpolatedTime) + mFromHeight;
target.requestLayout();
}
}
The big problem with this solution is that I cannot get the height of the contents_layout if it is set to android:layout_height="wrap_content" and the visibility is GONE, it just return "0".
Do you have any other solutions to perform the animation that I need?