Is margin animation slow? - android

I want to move a view when the ViewPager scrolls. I found scroll listener in view pager, with the parameter positionOffset, which I use to adjust the left margin of the view I want to move.
It works, but the scrolling isn't smooth anymore. If I comment the scroll listener out, it's smooth again. The view I'm animating is super simple - only a small square with a plain color. The requestLayout() call is done only on this view. The code:
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset != 0 || (positionOffset == 0 && position == 0)) {
pars.leftMargin = (int)(scrollablePart * positionOffset);
tabBG.requestLayout();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
I don't know what's the problem, I have other place with a view which I animate in a similar way (adjusting margins, according to a -not pager-slider) and it's smooth. I also have seen an app where the positions of some views are adjusted dynamically according to the scrolling of a pager, and it's very smooth.
Any idea? Thanks in advance!

Any animation that involves requestLayout() will be slow. If all you are trying to do is move a View around, use a TranslateAnimation, or View.offsetLeftAndRight() or View.setTranslationX(), etc. Do not use requestLayout() or anything layout related.

Related

Android ViewPager: Scroll to next item only when the previous item is scrolled to 90%

I have a view pager implementation in my app. I want to change its scroll behaviour.
Currently (by default) when we scroll to 50% (almost) of the current item, the view pager scrolls to the next item. I want to change this behaviour. I want that instead of 50%, the next item should come up when the current item is scrolled to 90%. Is it possible to implement this kind of behaviour?
You can make use of :
pager.addOnPageChangeListener
the solution could be using SimpleOnPageChangeListener method and override it's onPageScrolled method:
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
if (positionOffset> 0.9f){
scrolPage()
}
}
}) ;
please take a look into the positionOffset documentation:
* #param positionOffset Value from [0, 1) indicating the offset from the page at position.

Android ViewPager "slide lag"

I have a ViewPager Fragment with 3 fragments. Each of it has an image as background and a TextView. The TextView is always absolute on the left side of the screen (not the Fragment itself). When I move the fragments with my finger, it stays at the correct position. Also when I move it fast. But when I let it slide into the other Fragment, the TextView doesn't stays at the correct position. It looks like it lags around.
It should stay like in these 2 pictures:
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
SingleTabFragment singleTabFragment = (SingleTabFragment) mSectionsPagerAdapter.getRegisteredFragment(position);
TextView textView = singleTabFragment.getNonResizingTextView();
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams)textView.getLayoutParams();
marginLayoutParams.leftMargin = positionOffsetPixels;
textView.setLayoutParams(marginLayoutParams);
}
I really don't know what the problem is. I tried it multiple ways, i added Thread.sleep(50); to see if it also happens there. And yes, also there it doesn't work correct. How can I fix this?
EDIT:
Now I have put in the middle. But the problem is the same.
Here is how it should look like (I know, not that much frames. But I think you will get the point):
And if I let it slide it looks like this:
TextView textView = singleTabFragment.getNonResizingTextView();
textView.translationX = positionOffsetPixels;
You should not do marginLayoutParams.leftMargin = positionOffsetPixels; because changing the margin may change the position of the View, which will cause the View to be re-layout on every frame. That is why it is lagging.
Changing the translationX will only change how the view is drawn instead of it's position.
You can use following method to do the same instead of doing it in onPageScrolled:
mPager.setPageTransformer(false, new FadePageTransformer());
public class FadePageTransformer implements ViewPager.PageTransformer {
public void transformPage(View view, float position) {
view.setAlpha(1 - Math.abs(position));
if (position < 0) {
view.setScrollX(-(int) ((float) (view.getWidth()) * -position));
} else if (position > 0) {
view.setScrollX((int) ((float) (view.getWidth()) * position));
} else {
view.setScrollX(0);
}
}
}
use textView.setTranslationX(positionOffsetPixels); instead of margins
Have a look at Slowing speed of Viewpager controller in android
You can set custom duration for Viewpager scrolling.

OnPageScrolled Scale FAB

How can I improve this code? I'd like to show a FAB only if fragment with position 0 is displayed, hiding it with scale effect when user swipe to other view pager fragments. The value:
(1 - positionOffset)
never reach exactly 0, so I've add setScale(0). This way before complete disappearing the FAB needs to wait that the new position (e.g. 1) is set. I'd like to have a more fluid behaviour, can you help me?
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (fab != null && position == 0) {
fab.setScaleX(1 - positionOffset);
fab.setScaleY(1 - positionOffset);
} else {
fab.setScaleX(0);
fab.setScaleX(0);
}
}
});

ListView overlay another layout when scroll

Actually I don't know how it properly called - overlay, parallax or slideUP, whatever, I have an Activity called "cafe details" which presents a Container(LinearLayout) with header information (name, min price, delivery time min e.t.c) and other container (ViewPager) which contains a ExpandableListView with something information (menus&dishes) and all I want to do is slide up my Viewpager when scrolls listview to scpecific Y position to cover(or overlay) header information.
A similar effect (but with parallax that I don't need to use) looks like this
I can detect when user scrolling listview down or up but how I can move container with ViewPager to overlay other container? Please give me ideas, regards.
UPD
I have tried a huge number of ways how to implement it and all of them unfortunately are not suitable. So now I have come to next variant - add scroll listener to ListView, calculate scrollY position of view and then based on that move the viewpager on y axis by calling setTranslationY();
Here is some code
1) ViewPager's fragment
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (getActivity() != null) {
((MainActivity) getActivity()).resizePagerContainer(absListView);
}
}
});
2) MainActivity
//object fields
int previousPos;
private float minTranslation;
private float minHeight;
<--------somewhere in onCreate
minTranslation = - (llVendorDescHeaders.getMeasuredHeight()+llVendorDescNote.getMeasuredHeight());
//llVendorDescHeaders is linearLayout with headers that should be hidden
//lVendorDescNote is a textView on green background;
minHeight = llVendorDescriptionPagerContainer.getMeasuredHeight();
//llVendorDescriptionPagerContainer is a container which contains ViewPager
--------->
public void resizePagerContainer(AbsListView absListView){
final int scrollY = getScrollY(absListView);
if (scrollY != previousPos) {
final float translationY = Math.max(-scrollY, minTranslation);
llVendorDescriptionPagerContainer.setTranslationY(translationY);
previousPos = scrollY;
}
}
private int getScrollY(AbsListView view) {
View child = view.getChildAt(0);
if (child == null) {
return 0;
}
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = child.getTop();
return -top + firstVisiblePosition * child.getHeight() ;
}
This simple solution unfortunately has a problem - it is blinking and twitching (I don't know how to call it right) when scrolls slowly. So instead setTranslationY() I've used an objectAnimator:
public void resizePagerContainer(AbsListView absListView){
.............
ObjectAnimator moveAnim = ObjectAnimator.ofFloat(llVendorDescriptionPagerContainer, "translationY", translationY);
moveAnim.start();
..............
}
I don't like this solution because 1) anyway it does resize viewpager with delay, not instantly 2) I don't think that is good idea to create many ObjectAnimator's objects every time when I scroll my listView.
Need your help and fresh ideas. Regards.
I'm assuming that you are scrolling the top header (the ImageView is a child of the header) based on the scrollY of the ListView/ScrollView, as shown below:
float translationY = Math.max(-scrollY, mMinHeaderTranslation);
mHeader.setTranslationY(translationY);
mTopImage.setTranslationY(-translationY / 3); // For parallax effect
If you want to stick the header/image to a certain dimension and continue the scrolling without moving it anymore, then you can change the value of mMinHeaderTranslation to achieve that effect.
//change this value to increase the dimension of header stuck on the top
int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height);
mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
mMinHeaderTranslation = -mHeaderHeight + tabHeight;
The code snippets above are in reference to my demo but I think it's still general enough for you.
If you're interested you can check out my demo
https://github.com/boxme/ParallaxHeaderViewPager
Have you tried CoordinatorLayout from this new android's design support library? It looks like it's what you need. Check this video from 3:40 or this blog post.

Android ExpandableListView using animation

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;
}
}

Categories

Resources