There are a lot of questions with regard to crossfading in Android, but they all include animations. My question is about crossfading using the OnPageChangeListener of a ViewPager.
I have a ViewPager which could have an unlimited number of views, but in practice uses about 6 or 7 views. Not much going on there.
Each View in the ViewPager has a background Bitmap which should be fixed and crossfade with the background of the next (or previous) View instead of scrolling along with the rest of the View.
To achieve this I decoupled the backgrounds and add to an ArrayList and assign them to ImageViews later. But since I don't want to risk the chance that my Activity will end up with numerous ImageViews I thought of the following structure:
<FrameLayout
android:id="@+id/backgroundContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/bottomImage"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:scaleType="center" />
<ImageView
android:id="@+id/middleImage"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:scaleType="center" />
<ImageView
android:id="@+id/topImage"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:scaleType="center" />
</FrameLayout>
Then a OnPageChangeListener
is assigned to the ViewPager to assign the backgrounds to the ImageViews.
@Override
public void onPageSelected(int position) {
MyLog.i(TAG, "PAGE SELECTED: " + position);
if(position == 0) {
_bottomBackground.setImageBitmap(null);
_topBackground.setImageBitmap(_backgroundStack.get(position+1));
} else if (position == NUM_ITEMS-1) {
_bottomBackground.setImageBitmap(_backgroundStack.get(position-1));
_topBackground.setImageBitmap(null);
} else {
_bottomBackground.setImageBitmap(_backgroundStack.get(position-1));
_topBackground.setImageBitmap(_backgroundStack.get(position+1));
}
_middleBackground.setImageBitmap(_backgroundStack.get(position));
// Make the top front background transparent
_topBackground.setAlpha(0f);
_currentBackgroundPosition = position;
}
This works fine if I would've liked to just swap the backgrounds. I want the backgrounds to cross fade into each other while the user swipes the ViewPager. I've got the fade for a forward scroll working, but I don't understand why the fade for the backward scroll somehow doesn't give a good result. During a backward scroll the middle background should fade into the bottom background.
I'm afraid I'm missing something. I'm never changing the alpha of the bottom background, but the Log results always show the exact same value for getAlpha()
as for the middle background.
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(_currentBackgroundPosition == position) {
// scroll forward
_topBackground.setAlpha(positionOffset)
} else {
//scroll backward
_middleBackground.setAlpha(positionOffset);
}
MyLog.i(TAG, "Bottom BackgroundAlpha: " + _bottomBackground.getAlpha());
MyLog.i(TAG, "Middle BackgroundAlpha: " + _middleBackground.getAlpha());
MyLog.i(TAG, "Top BackgroundAlpha: " + _topBackground.getAlpha());
}
And wait! There's one more thing I really am not able to figure out how to fix. Although the forward scroll fade is working. There's a super short flickering in the background. I assume this is happening because of way I set up the onPageSelected
method.
Is there another way how I can create/fix this behavior?
ViewPager.PageTransformer
is your friend. I'm going to take a different approach to what you tried, but it results in what I understand to bed your desired result - swiping left/right swipes the content, but fades between two background images that don't move.
Each Fragment
in the ViewPager
will have a layout like so:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:scaleType="center" />
<LinearLayout
android:id="@+id/content_area"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- content goes here -->
</LinearLayout>
</FrameLayout>
And you will create a PageTransformer
that manipulates the layout depending the position it has been swiped:
public class CustomPageTransformer implements ViewPager.PageTransformer {
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
View imageView = view.findViewById(R.id.image_view);
View contentView = view.findViewById(R.id.content_area);
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left
} else if (position <= 0) { // [-1,0]
// This page is moving out to the left
// Counteract the default swipe
view.setTranslationX(pageWidth * -position);
if (contentView != null) {
// But swipe the contentView
contentView.setTranslationX(pageWidth * position);
}
if (imageView != null) {
// Fade the image in
imageView.setAlpha(1 + position);
}
} else if (position <= 1) { // (0,1]
// This page is moving in from the right
// Counteract the default swipe
view.setTranslationX(pageWidth * -position);
if (contentView != null) {
// But swipe the contentView
contentView.setTranslationX(pageWidth * position);
}
if (imageView != null) {
// Fade the image out
imageView.setAlpha(1 - position);
}
} else { // (1,+Infinity]
// This page is way off-screen to the right
}
}
}
And finally hook this PageTransformer
up to your ViewPager
:
mViewPager.setPageTransformer(true, new CustomPageTransformer());
I've tested it in an existing app and it works well as long as the fragment layouts have a transparent background.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With