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);
}
}
});
Related
I have a ViewPager which has 5 pages. And I have another TextView tvInstruction which is below ViewPager and it gets texts from the array based on the ViewPager's position.
I was able to achieve this so far: To animate this TextView's alpha property based on the ViewPager's positionOffset. So when page comes from right (when swiped left) tvInstruction alpha property decreases gradually and when two pages of the ViewPager are equally visible on the screen alpha property of the tvInstruction becomes 0f. And then again it gradually gains alpha moving to 1f when the swipe is done.
What I want to achieve:
In the middle of the swipe when alpha property of the TextView becomes 0f I want the tvInstruction to get the other String from the mInstructions array based on the swipe. And it should work even when I swipe almost to the next page but without removing my finger returning to the current page -> tvInstruction should change its text to other text from the array and then when returning to the current page It should change to the current page's text.
What I noticed is that position argument of the onPageScrolled() method gets incremented/decremented when the swipe is complete not in the middle. That is the problem I am asking you to help me to solve.
This is from my MainActivity:
private float alphaVal;
private int mPageNumber;
private mInstructions[] = {"A", "B", "C", "D", "E"};
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// "diff" to determine the swipe
float diff = positionOffset - mLastPositionOffset;
// "alphaVal" based on the positionOffset
alphaVal = (1f - positionOffset * 2);
if (diff > 0) {
System.out.println("swipe left");
tvInstruction.setAlpha(Math.abs(alphaVal));
// This is where the magic happens
if (alphaVal < 0 && mPageNumber == position) {
tvInstruction.setText(mInstructions[mPageNumber + 1]);
mPageNumber++;
}
} else {
System.out.println("swipe right");
tvInstruction.setAlpha(Math.abs(alphaVal));
// This is where the magic happens
if (alphaVal > 0 && mPageNumber == position) {
tvInstruction.setText(mInstructions[mPageNumber - 1]);
mPageNumber--;
}
}
mLastPositionOffset = positionOffset;
}
If there is any ambiguous place in the questions, feel free to ask for clarification.
Thanks to #psking's advice, I was able to achieve the desired animation with ViewPager.PageTransformer. Here is how I did:
private mInstructions[] = {"A", "B", "C", "D", "E"};
#Override
public void transformPage(#NonNull View page, float position) {
int pagePosition = Integer.parseInt((String) page.getTag());
float absPosition = Math.abs(position);
//------------------------------------------------------------
if (position <= -1.0f || position >= 1.0f) {
// page is not visible here - stop running any animations
} else if (position == 0.0f) {
// page is selected -- reset any view if necessary
tvInstruction.setAlpha(1f);
} else {
// page is currently being swiped -- perform animations here
// get the alpha property values based on the `property` value of the swipe
float alpha = Math.abs(1f - 2 * absPosition);
// playing with TextView {tvInstruction} and NextButton {btnNextAndDone}
playWithTextView(pagePosition, position, alpha);
playWithNextButton(pagePosition, position, alpha);
}
}
private void playWithTextView(int pagePosition, float position, float alpha) {
// setting the alpha property of the TextView
tvInstruction.setAlpha(alpha);
// Use only odd numbered pages.
if (pagePosition % 2 == 1) {
if (position > 0.5f) {
tvInstruction.setText(mInstructions[pagePosition - 1]);
} else if (position < 0.5f && position > -0.5f) {
tvInstruction.setText(mInstructions[pagePosition]);
} else if (position < -0.5f) {
tvInstruction.setText(mInstructions[pagePosition + 1]);
}
}
}
private void playWithNextButton(int pagePosition, float position, float alpha) {
if (pagePosition == 3) {
if (position < 0) {
btnNextAndDone.setAlpha(alpha);
if (position < -0.5f) {
setNextButtonFeatures();
} else if (position > -0.5f) {
setDoneButtonFeatures();
}
}
}
}
i am successfull in showing and hiding of appbar when swiping between tabs in tablayout. but now i want to animate this hiding and appearing of tab smoothly like we do in switching for the camera tab in whatsapp.
The way to go about it is to translate the AppbarLayout using its translationY attribute in onPageScrolled() callback of the ViewPager's OnPageChangeListener interface using the AppbarLayout's bottom value.
mViewPager.addOnPageChangeListener(new OnPageChangeListener(){
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// get an instance of the appBarLayout This should probably be done at the activity level in its oncreate() method
/* example:
public ViewPager mViewPager;
public AppBarLayout appBar;
onCreate(...){
...
mViewPager = (ViewPager) findViewById(R.id.viewpager);
appBar = (AppBarLayout) findViewById(R.id.appbar);
mViewPager.addOnPageChangeListener(...);
...
}
*/
// set the position you want the app bar to be hidden on the ViewPager
int hideAppBarAtPosition = 0;
if (appBar != null) {
// get an instance of the bottom value of the appBar
float mBottom = (float) appBar.getBottom();
// going right offset changes from zero to one
if (position == hideAppBarAtPosition) {
// Translate the appBar up as the resideReveal slides into view
float y = (positionOffset * mBottom) - mBottom;
// Raise the appBar a little bit higher when it is no longer visible
if (y == -mBottom) {
float h = (float) appBar.getHeight();
if (mBottom < h) mBottom = h;
y = -mBottom - mBottom / 8f;
}
appBar.setTranslationY(y);
} else if (position == hideAppBarAtPosition - 1) {
// Translate the appBar up as the resideReveal slides into view
appBar.setTranslationY(-(positionOffset * mBottom));
} else if (appBar.getTranslationY() != 0f) {
// Reset translation of the appBar
appBar.setTranslationY(0f);
}
}
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
If you need more ideas on how to implement it, view this open source project https://github.com/goody-h/ResidingTab
I got the following exception when trying to change the background of pages inside a ViewPager in the onPageScrolled method. I have edited the question in order to make it more clear.
android.content.res.Resources$NotFoundException: Resource ID #0x0
at android.content.res.Resources.getValue(Resources.java:1245)
at android.content.res.Resources.getColor(Resources.java:899)
at android.support.v4.content.ContextCompat.getColor(ContextCompat.java:413)
at com.noel.material_onboarding.OnboardingActivity.color(OnboardingActivity.java:113)
at com.noel.material_onboarding.OnboardingActivity.access$200(OnboardingActivity.java:29)
at com.noel.material_onboarding.OnboardingActivity$1.onPageScrolled(OnboardingActivity.java:86)
First I create the slider objects, this includes setting up the background color:
addSlide(new SlideFragmentBuilder()
.description("This is a test")
.backgroundColor(R.color.colorPrimary)
.build());
addSlide(new SlideFragmentBuilder()
.description("This is a test 2")
.backgroundColor(R.color.green)
.build());
addSlide(new SlideFragmentBuilder()
.description("This is a test 3")
.backgroundColor(R.color.orange)
.build());
addSlide(new SlideFragmentBuilder()
.description("This is a test 4")
.backgroundColor(R.color.orange)
.build());
Here's a link to the SlideFragmentBuilder on github and the Fragment class itself
Here's my onPageScrolled method:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int colorUpdate = (Integer) evaluator.evaluate(positionOffset, color(mOnboardingAdapter.getItem(position).backgroundColor()), color(mOnboardingAdapter.getItem(position + 1).backgroundColor()));
mViewPager.setBackgroundColor(colorUpdate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(colorUpdate);
}
}
#Override
public void onPageSelected(int position) {
btnFinish.setVisibility(position == mOnboardingAdapter.getLastItemPosition() ? View.VISIBLE : View.GONE);
btnNext.setVisibility(position == mOnboardingAdapter.getLastItemPosition() ? View.GONE : View.VISIBLE);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
The color() method that is used
private int color(#ColorRes int color){
return ContextCompat.getColor(this, color);
}
Basically, I just need the background of one page to fade in as the user swipes to another page.
Ok, so I went through the docs and found an important thing I was missing out on:
int: Position index of the first page currently being displayed. Page position+1 will be visible if positionOffset is nonzero.
Basically the app was crushing on the second screen after the positionOffset went back to zero. See this is how it works:
On the first screen the positionOffset is zero and the position of the page is also zero, however position + 1 is not available since the positionOffset is zero. I solved this by adding the following statement to check whether the Offset is zero or not:
positionOffset != 0.0 ? position + 1 : position
This is how the onPageScrolled method looks like:
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int colorUpdate = (Integer) evaluator.evaluate(positionOffset, color(mOnboardingAdapter.getItem(position).backgroundColor()), color(mOnboardingAdapter.getItem(positionOffset != 0.0 ? position + 1 : position).backgroundColor()));
mViewPager.setBackgroundColor(colorUpdate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(colorUpdate);
}
}
try to use this
Color.parseColor(mOnboardingAdapter.getItem(position + 1));
and don't forgot to remove integer cast
(Integer)
And i think that
mOnboardingAdapter.getItem(position + 1)
is a null value (not initialized)
I'm working on a Circular ViewPager, and i've implemented this exactly solution (https://stackoverflow.com/a/12965787/1083564).
The only thing is missing, is the fact that i need to smoothScroll when i'm using the setCurrentItem(int i, bol b) method, that instantly goes to the pixel limit, without using the smoothScroll.
I already have the access to use this method, using the following code:
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class MyViewPager extends ViewPager {
public MyViewPager(Context context) {
super(context);
}
public MyViewPager(Context context, AttributeSet attr) {
super(context, attr);
}
public void smoothScrollTo(int x, int y, int velocity) {
super.smoothScrollTo(x, y, velocity);
}
}
But i couldn't figure it out where and how to use it. I have the number of pixels that i need to run smoothly by using this code inside the setOnPageChangeListener on my ViewPager:
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.d( "viewpager", positionOffsetPixels+"");
}
Before it goes to 0, instantly, because of the setCurrentItem, i have the value of pixels left to reach 0 (to the left) or x (to the right, depending of screen). I dont know how can i get this x number too.
PS: I think this solution is the exatcly one used by IMDB app. You can see this scrolling from the first to the last but one, without remove your finger (use 2 fingers to do it). You will see that the "white limit" will show from the left side of the ViewPager. The only difference is that they know how to smooth scroll after using the setCurrentItem.
If you need some more information, please, ask! Thanks!
Issue: When you detect circular scrolling has to be perfomed, calling setCurrentItem immediately will cause the ViewPager to scroll to the real fragment immediately without smooth scrolling as it is set to false.
Solution: Instead allow the ViewPager to scroll to the fake fragment smoothly as it does for other fragments and then scroll to the real fragment after some delay with smooth scrolling set to false. User will not notice the change.
When we are performing circular scrolling, call setCurrentItem in a runnable with some delay. Use onPageSelected to know the index of the page selected.
public void onPageSelected(int position) {
// Consider eg. : C' A B C A'
boolean circularScroll = false;
if(position == 0) {
// Position 0 is C', we need to scroll to real C which is at index 3.
position = mPager.getAdapter().getCount() - 2;
circularScroll = true;
}
int lastIndex = mPager.getAdapter().getCount() - 1;
if(position == lastIndex) {
// Last index is A', we need to scroll to real A, which is at index 1.
position = 1;
circularScroll = true;
}
if(circularScroll) {
final int realPosition = position;
mPager.postDelayed(new Runnable() {
#Override
public void run() {
mPager.setCurrentItem(realPosition, false);
}
}, 500L);
}
}
When you set the second parameter of the setCurrentItem to true it should smooth scroll
#Override
public void onPageScrollStateChanged (int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
int curr = viewPager.getCurrentItem();
int lastReal = viewPager.getAdapter().getCount() - 2;
if (curr == 0) {
viewPager.setCurrentItem(lastReal, true);
} else if (curr > lastReal) {
viewPager.setCurrentItem(1, true);
}
}
}
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.