Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scrolling effect with multiple viewpagers

I have a concept for a view. Please guide me as to how I can achieve it. Please check the wireframe. enter image description here

I have already looked at FadingActionBar but it does not seem to help. The problem is I have multiple viewpagers in the screen and am going no where trying to achieve the desired result. It would be super-awesome if I am able to achieve a cool transition/parallax effect.

Any inputs will be much appreciated.

Edit1 :

The tabs are put on a PagerTabStrip and hooked up with the Viewpager below it. The attempt here is to scroll the view and dock the PagerTabStrip to the ActionBar and on Scroll down bring it down to reveal the ImageViewPager.

like image 917
Neo Avatar asked Jan 29 '14 23:01

Neo


2 Answers

You can also achieve this effect by using the Android-ParallaxHeaderViewPager a good example of scrolling tab header by kmshack Github page

The Sample code is give in this Here Git Hub link

Here is the Screen shot enter image description here

The solution by @adneal is also very useful one to achieve scrolling Tab header.

Hop this will help you

New update

Please check this answer Google+ profile like scrolling Effect

like image 68
Ramz Avatar answered Oct 20 '22 09:10

Ramz


So, this can be achieved pretty easily, but it requires a little trick, more like an illusion actually. Also, I'm going to be using a ListView instead of a ScrollView for my "scrollable content", mostly because it's easier to work with in this situation and for my tabs I'll be using this open sourced library.

First, you need a View that can store y-coordinates for a given index. This custom View will be placed on top of your bottom ViewPager and appear as the "real" header for each ListView. You need to remember the header's y-coordinate for each page in the ViewPager so you can restore them later as the user swipes between them. I'll expand on this later, but for now here's what that View should look like:

CoordinatedHeader

public class CoordinatedHeader extends FrameLayout {

    /** The float array used to store each y-coordinate */
    private final float[] mCoordinates = new float[5];

    /** True if the header is currently animating, false otherwise */
    public boolean mAnimating;

    /**
     * Constructor for <code>CoordinatedHeader</code>
     * 
     * @param context The {@link Context} to use
     * @param attrs The attributes of the XML tag that is inflating the view
     */
    public CoordinatedHeader(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * Animates the header to the stored y-coordinate at the given index
     * 
     * @param index The index used to retrieve the stored y-coordinate
     * @param duration Sets the duration for the underlying {@link Animator}
     */
    public void restoreCoordinate(int index, int duration) {
        // Find the stored value for the index
        final float y = mCoordinates[index];
        // Animate the header to the y-coordinate
        animate().y(y).setDuration(duration).setListener(mAnimatorListener).start();
    }

    /**
     * Saves the given y-coordinate at the specified index, the animates the
     * header to the requested value
     * 
     * @param index The index used to store the given y-coordinate
     * @param y The y-coordinate to save
     */
    public void storeCoordinate(int index, float y) {
        if (mAnimating) {
            // Don't store any coordinates while the header is animating
            return;
        }
        // Save the current y-coordinate
        mCoordinates[index] = y;
        // Animate the header to the y-coordinate
        restoreCoordinate(index, 0);
    }

    private final AnimatorListener mAnimatorListener = new AnimatorListener() {

        /**
         * {@inheritDoc}
         */
        @Override
        public void onAnimationCancel(Animator animation) {
            mAnimating = false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onAnimationEnd(Animator animation) {
            mAnimating = false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onAnimationRepeat(Animator animation) {
            mAnimating = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onAnimationStart(Animator animation) {
            mAnimating = true;
        }
    };

}

Now you can create the main layout for your Activity or Fragment. The layout contains the bottom ViewPager and the CoordinatedHeader; which consists of, the bottom ViewPager and tabs.

Main layout

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.view.ViewPager
        android:id="@+id/activity_home_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <org.seeingpixels.example.widget.CoordinatedHeader
        android:id="@+id/activity_home_header"
        android:layout_width="match_parent"
        android:layout_height="250dp" >

        <android.support.v4.view.ViewPager
            android:id="@+id/activity_home_header_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.astuetz.viewpager.extensions.PagerSlidingTabStrip
            android:id="@+id/activity_home_tabstrip"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_gravity="bottom"
            android:background="@android:color/white" />
    </org.seeingpixels.example.widget.CoordinatedHeader>

</FrameLayout>

The only other layout you need is a "fake" header. This layout will be added to each ListView, giving the illusion the CoordinatedHeader in the main layout is the real one.

Note It's important that the height of this layout is the same as the CoordinatedHeader in the main layout, for this example I'm using 250dp.

Fake header

<View xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="250dp" />

Now you need to prepare each Fragment that will be displayed in the bottom ViewPager to control the CoordinatedHeader by attaching a AbsListView.OnScrollListener to your ListView. This Fragment should also pass a unique index upon creation using Fragment.setArguments. This index should represent its location in the ViewPager.

Note I'm using a ListFragment in this example.

Scrollable content Fragment

/**
 * {@inheritDoc}
 */
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    final Activity a = getActivity();

    final ListView list = getListView();
    // Add the fake header
    list.addHeaderView(LayoutInflater.from(a).inflate(R.layout.view_fake_header, list, false));

    // Retrieve the index used to save the y-coordinate for this Fragment
    final int index = getArguments().getInt("index");

    // Find the CoordinatedHeader and tab strip (or anchor point) from the main Activity layout
    final CoordinatedHeader header = (CoordinatedHeader) a.findViewById(R.id.activity_home_header);
    final View anchor = a.findViewById(R.id.activity_home_tabstrip);

    // Attach a custom OnScrollListener used to control the CoordinatedHeader 
    list.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                int totalItemCount) {

            // Determine the maximum allowed scroll height
            final int maxScrollHeight = header.getHeight() - anchor.getHeight();

            // If the first item has scrolled off screen, anchor the header
            if (firstVisibleItem != 0) {
                header.storeCoordinate(index, -maxScrollHeight);
                return;
            }

            final View firstChild = view.getChildAt(firstVisibleItem);
            if (firstChild == null) {
                return;
            }

            // Determine the offset to scroll the header
            final float offset = Math.min(-firstChild.getY(), maxScrollHeight);
            header.storeCoordinate(index, -offset);
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // Nothing to do
        }

    });
}

Finally, you'll need to setup the Coordinated header to restore its y-coordinates when the user swipes between pages using a ViewPager.OnPageChangeListener.

Note When attaching your PagerAdapter to your bottom ViewPager, it's important to call ViewPager.setOffscreenPageLimit and set that amount to the total amount of pages in your PagerAdapter. This is so the CoordinatedHeader can store the y-coordinate for each Fragment right away, otherwise you'll run into trouble with it being out of sync.

Main Activity

/**
 * {@inheritDoc}
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);

    // Setup the top PagerAdapter
    final PagerAdapter topAdapter = new PagerAdapter(getFragmentManager());
    topAdapter.buildData(DummyColorFragment.newInstance(Color.RED));
    topAdapter.buildData(DummyColorFragment.newInstance(Color.WHITE));
    topAdapter.buildData(DummyColorFragment.newInstance(Color.BLUE));

    // Setup the top pager
    final ViewPager topPager = (ViewPager) findViewById(R.id.activity_home_header_pager);
    topPager.setAdapter(topAdapter);

    // Setup the bottom PagerAdapter
    final PagerAdapter bottomAdapter = new PagerAdapter(getFragmentManager());
    bottomAdapter.buildData(DummyListFragment.newInstance(0));
    bottomAdapter.buildData(DummyListFragment.newInstance(1));
    bottomAdapter.buildData(DummyListFragment.newInstance(2));
    bottomAdapter.buildData(DummyListFragment.newInstance(3));
    bottomAdapter.buildData(DummyListFragment.newInstance(4));

    // Setup the bottom pager
    final ViewPager bottomPager = (ViewPager) findViewById(R.id.activity_home_pager);
    bottomPager.setOffscreenPageLimit(bottomAdapter.getCount());
    bottomPager.setAdapter(bottomAdapter);

    // Setup the CoordinatedHeader and tab strip
    final CoordinatedHeader header = (CoordinatedHeader) findViewById(R.id.activity_home_header);
    final PagerSlidingTabStrip psts = (PagerSlidingTabStrip) findViewById(R.id.activity_home_tabstrip);
    psts.setViewPager(bottomPager);
    psts.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageScrollStateChanged(int state) {
            if (state != ViewPager.SCROLL_STATE_IDLE) {
                // Wait until the pager is idle to animate the header
                return;
            }
            header.restoreCoordinate(bottomPager.getCurrentItem(), 250);
        }
    });
}
like image 15
adneal Avatar answered Oct 20 '22 09:10

adneal