OnPageChangeListener alpha crossfading

2020-02-20 05:09发布

问题:

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?

回答1:

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.