ViewPagerIndicator and setOnPageChangeListener - android

I'd like to change the background color as the user changes pages, for that I need to use ViewPager's setOnPageChangeListener. But it seems that this brakes ViewPagerIndicator, as the indicator is stuck in the first page. Here's the code
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
ColorDrawable[] colors = {new ColorDrawable(backgroundColors[previousPosition]), new ColorDrawable(backgroundColors[position])};
TransitionDrawable trans = new TransitionDrawable(colors);
viewPager.setBackgroundDrawable(trans);
trans.startTransition(200);
previousPosition = position;
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageScrollStateChanged(int arg0) {}
});

I ended up using ViewPagerIndicator's setOnPageChangeListener instead of ViewPager's method
mIndicator = (IconPageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(viewPager);
The code becomes :
mIndicator.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
ColorDrawable[] colors = {new ColorDrawable(backgroundColors[previousPosition]), new ColorDrawable(backgroundColors[position])};
TransitionDrawable trans = new TransitionDrawable(colors);
viewPager.setBackgroundDrawable(trans);
trans.startTransition(200);
previousPosition = position;
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageScrollStateChanged(int arg0) {}
});

This way of changing colors between tabs is a first approach but I think that is not that difficult to come up with a better one.
In your approach:
I think that in your approach what you are doing is as soon as a new page is selected you trigger a transition in the background color. In that case the transitions are fixed in time (200 milliseconds in your case) but it doesn't feel natural, is even worse when you change pages swiping between them. The problem is that the onPageSelected is a on/off trigger. You are on page 0 or 1 or 2, but there is no such state as "between 0 and 1".
Wouldn't be great if the color would be exactly proportional to the amount of swipe, this is proportional to the real state in the transition between tabs??
How to do it?
In the activity hosting the viewpager, set a layout with as many background layers as tabs you want in the view pager. If you have 3 tabs then set 3 background layers overlapping one to each other.
For example:
<LinearLayout
android:id="#+id/background_main_fragment_activity_bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background_gradient_radial_blue_vivid"
android:orientation="vertical"
tools:visibility="visible">
</LinearLayout>
<LinearLayout
android:id="#+id/background_main_fragment_activity_middle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background_gradient_radial_blue_samknows"
android:orientation="vertical"
tools:visibility="visible">
</LinearLayout>
<LinearLayout
android:id="#+id/background_main_fragment_activity_top"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background_gradient_radial_blue_light"
android:orientation="vertical"
tools:visibility="visible">
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
Each LinearLayout is a background for a particular tab.
The idea is modify the alpha value of the linear layouts making them visible, partially visible or invisible.
Instead of using the onPageSelected method we are gonna use the onPageScrolled, take a look:
// Set a listener that will be invoked whenever the page changes or is incrementally scrolled.
// This listener is used for changing smoothly the colour of the background, this is modifying the visibility of the different layers
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener()
{
// Called when the scroll state changes
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
switch (position)
{
case 0:
layout_ll_background_middle.setAlpha(positionOffset);
break;
case 1:
layout_ll_background_top.setAlpha(positionOffset);
break;
default:
break;
}
}
// This method will be invoked when a new page becomes selected
#Override
public void onPageSelected(int position)
{ // When swiping between pages, select the corresponding tab
getActionBar().setSelectedNavigationItem(position);
// Set the title
actionBar.setTitle(adapter_ViewPager.getPageTitle(position));
}
});
Hope it helps to make it better.
Good luck

Since timed transitions don't quite fit the usecase and alpha shouldn't be used (in that way) for transitions/animations on android here is what I came up with:
First of all be sure that no child view has a background set where it shouldn't. In your base layout set the background of the layout to your start color.
In your Activity.onCreate() add this:
final ViewPager mPager = (ViewPager) findViewById(R.id.ofYourPager);
// if you have a page indicator
final YourPageIndicator pageIndicator = (YourPageIndicator) findViewById(R.id.ofYourIndicator);
// change background on swipe
final int[] colors = {
getResources().getColor(R.color.yourFirstColor),
getResources().getColor(R.color.yourSecondColor),
getResources().getColor(R.color.yourThirdColor),
// --- add as many colours as you have pages ---
};
final ArgbEvaluator argbEvaluator = new ArgbEvaluator();
mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (position < (mPagerAdapter.getCount() - 1) && position < (colors.length - 1)) {
Integer color = (Integer) argbEvaluator.evaluate(positionOffset, colors[position], colors[position + 1]);
mPager.setBackgroundColor(color);
pageIndicator.setBackgroundColor(color);
} else {
mPager.setBackgroundColor(colors[colors.length - 1]);
pageIndicator.setBackgroundColor(colors[colors.length - 1]);
}
}
#Override
public void onPageSelected(int position) {}
#Override
public void onPageScrollStateChanged(int state) {}
});
I took the idea from this post I found: http://kubaspatny.github.io/2014/09/18/viewpager-background-transition/

Related

Gradual Transition between Fragments

I recently downloaded the Google Sheets app and I was really interested in the first screen that explained the details of the app so I tried to recreate it.
After some research I decided that it was ViewPager and I implemented it.
But the result was not what I expected.
In the sheets app the color changing was gradual and barely noticeable but in my case the transition is clear(The color scheme used are same as the one in the sheets app).
What is the type of animation applied in the sheets app and how can I replicate it?
The easiest way to do that is to:
Remove backgrounds from Fragments in the ViewPager (I assume you use Fragments inside your ViewPager's Adapter... if not, let me know). And by "remove background" I mean e.g. set it to "#android:color/transparent".
Remove background from the ViewPager itself (also e.g. with setting it's color to transparent).
Put a View that will act as a changing background below (z-wise) your ViewPager. For example like this (without parameters):
<FrameLayout>
<View android:id="#+id/animated_color_view"/>
<android.support.v4.view.ViewPager/>
</FrameLayout>
Create a way to animate background color of the animated_color_view. You can use one of the ways included in the thread linked by REG1 in the comment (edit: the comment has been deleted, but this link points to the same thread). For example like this (the approach was taken from this post):
int[] pageColors = new int[]{Color.RED, Color.GREEN};
int currentColor = pageColors[0];
ValueAnimator colorAnimation;
public void animateToColor(int colorTo) {
if (colorAnimation != null) {
colorAnimation.cancel();
}
colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), currentColor, colorTo);
colorAnimation.setDuration(250); // milliseconds
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
currentColor = (int) animator.getAnimatedValue();
animatedColorView.setBackgroundColor(currentColor);
}
});
colorAnimation.start();
}
Add an OnPageChangeListener that will call this animating method every time a page is selected.
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
animateToColor(pageColors[position]);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
And a little side-note: remember the pageColors array's size must be equal to the number of pages in your ViewPager.

How to have a parallax effect between 2 ViewPagers?

Background
Consider the following scenario:
There are 2 viewPagers, each of different width and height, but both have the exact same number of pages.
dragging on one should drag the other, so the effect is like a "parallax" effect.
same as #2, but for the other viewPager.
there is also an auto-switching mechanism between the viewpagers, so every 2 seconds it will auto-switch to the next page (and if on the last page, switch back to the first).
The problem
I think I got some functionality working, but it has some issues:
This is just simple XML. nothing special here.
I've used 'OnPageChangeListener' and there, in 'onPageScrolled' , I've used fake-dragging. Problem is, if the dragging of the user stops near the middle of the page (thus activates auto-scrolling the viewPager left/right), the fake dragging loses its sync, and weird things can occur: wrong page or even staying between pages...
For now, I don't handle the other viewPager with the dragging of the user, but it might be an issue too.
this can be an issue when I succeed solving #2 (or #3). For now, I'm using a handler that use a runnable and calls this command inside :
mViewPager.setCurrentItem((mViewPager.getCurrentItem() + 1) % mViewPager.getAdapter().getCount(), true);
What I've tried
I've tried this post, but it didn't work well, as it got the same issues when the user-dragging stops before finished swithching to another page.
Only similar solution that works, is with a single ViewPager (here), but I need to work with 2 ViewPagers, each affects the other.
So I've tried doing it myself: suppose one viewPager is "viewPager", and the other (that is supposed to follow "viewPager") is "viewPager2" , this is what I've done:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int lastPositionOffsetPixels=Integer.MIN_VALUE;
#Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
if (!viewPager2.isFakeDragging())
viewPager2.beginFakeDrag();
if(lastPositionOffsetPixels==Integer.MIN_VALUE) {
lastPositionOffsetPixels=positionOffsetPixels;
return;
}
viewPager2.fakeDragBy((lastPositionOffsetPixels - positionOffsetPixels) * viewPager2.getWidth() / viewPager.getWidth());
lastPositionOffsetPixels = positionOffsetPixels;
}
#Override
public void onPageSelected(final int position) {
if (viewPager2.isFakeDragging())
viewPager2.endFakeDrag();
viewPager2.setCurrentItem(position,true);
}
#Override
public void onPageScrollStateChanged(final int state) {
}
});
I've chosen to use fake-dragging because even the docs say it could be useful for this exact same scenario :
A fake drag can be useful if you want to synchronize the motion of the
ViewPager with the touch scrolling of another view, while still
letting the ViewPager control the snapping motion and fling behavior.
(e.g. parallax-scrolling tabs.) Call fakeDragBy(float) to simulate the
actual drag motion. Call endFakeDrag() to complete the fake drag and
fling as necessary.
To make it easy to test, here's more code:
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
final ViewPager viewPager2 = (ViewPager) findViewById(R.id.viewPager2);
viewPager.setAdapter(new MyPagerAdapter());
viewPager2.setAdapter(new MyPagerAdapter());
//do here what's needed to make "viewPager2" to follow dragging on "viewPager"
}
private class MyPagerAdapter extends PagerAdapter {
int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff};
#Override
public int getCount() {
return 3;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public boolean isViewFromObject(final View view, final Object object) {
return (view == object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
TextView textView = new TextView(MainActivity.this);
textView.setText("item" + position);
textView.setBackgroundColor(colors[position]);
textView.setGravity(Gravity.CENTER);
final LayoutParams params = new LayoutParams();
params.height = LayoutParams.MATCH_PARENT;
params.width = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;
textView.setLayoutParams(params);
textView.setTextColor(0xff000000);
container.addView(textView);
return textView;
}
}
activity_main
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="viewPager:"/>
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="viewPager2:"/>
<android.support.v4.view.ViewPager
android:id="#+id/viewPager2"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</LinearLayout>
The question
The code almost works well. I just need to know, what is missing to avoid the issues of stopping the dragging (by the user)?
The other issues might have easier solutions.
A possible solution
After a lot of work, we've found a solution that almost has no bugs. There is a rare bug that while scrolling, the other viewPager flashes. Weird thing is that it happens only when the user scrolls the second viewPager.
All other tries had other issues, like empty page or "jumpy"/"rubber" effect when finishing the scrolling to a new page.
Here's the code:
private static class ParallaxOnPageChangeListener implements ViewPager.OnPageChangeListener {
private final AtomicReference<ViewPager> masterRef;
/**
* the viewpager that is being scrolled
*/
private ViewPager viewPager;
/**
* the viewpager that should be synced
*/
private ViewPager viewPager2;
private float lastRemainder;
private int mLastPos = -1;
public ParallaxOnPageChangeListener(ViewPager viewPager, ViewPager viewPager2, final AtomicReference<ViewPager> masterRef) {
this.viewPager = viewPager;
this.viewPager2 = viewPager2;
this.masterRef = masterRef;
}
#Override
public void onPageScrollStateChanged(int state) {
final ViewPager currentMaster = masterRef.get();
if (currentMaster == viewPager2)
return;
switch (state) {
case ViewPager.SCROLL_STATE_DRAGGING:
if (currentMaster == null)
masterRef.set(viewPager);
break;
case ViewPager.SCROLL_STATE_SETTLING:
if (mLastPos != viewPager2.getCurrentItem())
viewPager2.setCurrentItem(viewPager.getCurrentItem(), false);
break;
case ViewPager.SCROLL_STATE_IDLE:
masterRef.set(null);
viewPager2.setCurrentItem(viewPager.getCurrentItem(), false);
mLastPos = -1;
break;
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (masterRef.get() == viewPager2)
return;
if (mLastPos == -1)
mLastPos = position;
float diffFactor = (float) viewPager2.getWidth() / this.viewPager.getWidth();
float scrollTo = this.viewPager.getScrollX() * diffFactor + lastRemainder;
int scrollToInt = scrollTo < 0 ? (int) Math.ceil(scrollTo) : (int) Math.floor(scrollTo);
lastRemainder = scrollToInt - scrollTo;
if (mLastPos != viewPager.getCurrentItem())
viewPager2.setCurrentItem(viewPager.getCurrentItem(), false);
viewPager2.scrollTo(scrollToInt, 0);
}
#Override
public void onPageSelected(int position) {
}
}
usage:
/**the current master viewPager*/
AtomicReference<ViewPager> masterRef = new AtomicReference<>();
viewPager.addOnPageChangeListener(new ParallaxOnPageChangeListener(viewPager, viewPager2, masterRef));
viewPager2.addOnPageChangeListener(new ParallaxOnPageChangeListener(viewPager2, viewPager, masterRef));
For the auto-switching, the original code works fine.
Github Project
Since it's quite hard to test it, and I want to make it easier for you guys to try it out, here's a Github repo:
[https://github.com/AndroidDeveloperLB/ParallaxViewPagers][4]
Do note that as I've mentioned, it still has issues. Mainly the "flashing" effect from time to time, but for some reason, only when scrolling on the second ViewPager.
I don't think 2 ViewPagers are apt for this. Both encapsulate their own gesture and animation logic, and were not built to be synchronized externally.
You should have a look at the CoordinatorLayout. It is designed specifically to co-ordinate transitions and animations of its child views. Each child view can have a Behaviour and can monitor changes to its dependent sibling views and update its own state.
Report the first ViewPager's scroll changes to the second ViewPager:
viewPager.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
viewPager2.scrollTo(viewPager.getScrollX(), viewPager2.getScrollY());
}
});
With complex pages, this method might make the second pager lag behind.
Edit:
Instead of waiting for the scroll to change, you can report to the method calls directly with a custom class:
public class SyncedViewPager extends ViewPager {
...
private ViewPager mPager;
public void setSecondViewPager(ViewPager viewPager) {
mPager = viewPager;
}
#Override
public void scrollBy(int x, int y) {
super.scrollBy(x, y);
if (mPager != null) {
mPager.scrollBy(x, mPager.getScrollY());
}
}
#Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
if (mPager != null) {
mPager.scrollTo(x, mPager.getScrollY());
}
}
}
And set the pager.
viewPager.setSecondViewPager(viewPager2);
Note: neither of these methods will invoke the page change listeners on the second pager.You can just add a listener to the first one and account for both pagers there.
You can use Paralloid. I have an application with parallex effect. Not a pager but a horizontalschrollbar which is quite the same in your context. This library runs well. Alternatively use parallaxviewpager which I didn't use but looks more towards your direction.

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.
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.
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);
}
});
}
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
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
I use fragment container layout instead viewpager with FadingActionBar. Also i use MaterialTabs library.
Content layout
<xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
android:orientation="vertical">
<it.neokree.materialtabs.MaterialTabHost
android:id="#+id/materialTabHost"
android:layout_width="match_parent"
android:layout_height="48dp"
app:accentColor="#color/second"
app:primaryColor="#color/actionbar_background"
app:textColor="#FFFFFF" />
<FrameLayout
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FadingActionBarHelper helper = new FadingActionBarHelper()
.actionBarBackground(R.color.actionbar_background)
.headerLayout(R.layout.actionbar_header)
.contentLayout(R.layout.content_layout);
final View root = helper.createView(this);
setContentView(root);
helper.initActionBar(this);
mTabHost = (MaterialTabHost) view.findViewById(R.id.materialTabHost);
//.......
}
#Override
public void onTabSelected(MaterialTab materialTab) {
int position = materialTab.getPosition();
switch (position) {
case 0:
switchFragment(new ProfileFragment(), position);
break;
case 1:
switchFragment(new NotificationFragment(), position);
break;
}
mTabHost.setSelectedNavigationItem(position);
}
Finally a have this result: link(gif)

Align the child views in center of the ViewPager android

I need to set the child view as center of the ViewPager and also I would like to show some part of the next and previous views to the current view sides(like current screen below 1). But currently the current view is starting at left side of the ViewPager(like expected screen below 2). How can I achieve that?
Here is my code..
MyViewPagerAdapter
public class MyViewPagerAdapter extends PagerAdapter {
private Activity mActivity;
private int mPageCount;
public MyViewPagerAdapter(Activity activity,int pageCount) {
mActivity = activity;
mPageCount = pageCount;
}
#Override
public int getCount() {
return mPageCount;
}
#Override
public boolean isViewFromObject(View view, Object obj) {
return (view ==(View)obj);
}
#Override
public Object instantiateItem(ViewGroup container,final int position) {
ViewGroup viewGroup = (ViewGroup)mActivity.getLayoutInflater().inflate(
R.layout.item_view, null);
viewGroup.setBackgroundColor(randomColor());
TextView textView = (TextView)viewGroup.findViewById(R.id.textView1);
textView.setText("Page: "+(position+1));
Button button = (Button) viewGroup.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mActivity, "Hey, Its clicked!!! at page "+(position+1), Toast.LENGTH_LONG).show();
}
});
container.addView(viewGroup);
return viewGroup;
}
Random rnd = new Random();
private int randomColor(){
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
//must be overridden else throws exception as not overridden.
Log.d("Tag", collection.getChildCount()+"");
collection.removeView((View) view);
}
#Override
public float getPageWidth(int position) {
return 0.8f;
}
}
MainActivity
public class MainActivity extends Activity {
private ViewPager viewPager;
LinearLayout linearLayout;
private int ID = 100;
private final int count = 8;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewPager);
linearLayout = (LinearLayout) findViewById(R.id.indicator_layout);
generateIndicators(count);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(this, count);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
int oldPosition = 0;
#Override
public void onPageSelected(int position) {
//this changes the old position's view state image
((TextView)linearLayout.getChildAt(oldPosition)).setText("");
oldPosition = position;
//this changes the current position's view state image
((TextView)linearLayout.getChildAt(position)).setText((position+1)+"");
}
//this method will be called repeatedly upto another item comes as front one(active one)
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
//this will be called as per scroll state
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
viewPager.setOffscreenPageLimit(4);
}
private void generateIndicators(int count) {
/// Converts 14 dip into its equivalent px
int padd = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics());
for(int i=0;i<count;i++){
TextView textView = new TextView(this);
textView.setId(ID+i);
final int currentItem = i;
textView.setBackgroundResource(R.drawable.white_cell);
textView.setPadding(padd,padd,padd,padd);
/// Converts 14 dip into its equivalent px
int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
textView.setTextSize(size);
textView.setGravity(Gravity.CENTER);
/// Converts 14 dip into its equivalent px
int px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(px, px);
linearLayout.addView(textView,params);
}
((TextView)linearLayout.getChildAt(0)).setText("1");
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</android.support.v4.view.ViewPager>
<LinearLayout
android:id="#+id/indicator_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="19dp" >
</LinearLayout>
</RelativeLayout>
item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="match_parent"
android:id="#+id/root_view"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="click me" />
</LinearLayout>
Current screen
expected screen
For one app I implemented similar the following way, with standard ViewPager:
Make pages full-screen with the actual content in an inner layout. For example, make the full-screen layout a RelativeLayout with transparent background and the actual content another RelativeLayout centered in the parent. If I remember right, the reason for this was that with just the inner layout as a page, the ViewPager would not have taken all the screen width on some devices such as Galaxy Nexus.
Use ViewPager.setPageMargin() to set up a negative page margin i.e. how much of the next/previous page you want to show. Make sure it only overlaps the transparent region of the parent full-screen layout.
Call ViewPager.setOffscreenPageLimit() to adjust the off-screen page count to at least 2 from the default 1 to ensure smooth paging by really creating the pages off-screen. Otherwise you will see next/previous pages being drawn while already partially showing on screen.
For anyone upset that the OP didn't update his question with the solution here is a link that explains, with minimal effort, how to pull this off in XML: http://blog.neteril.org/blog/2013/10/14/android-tip-viewpager-with-protruding-children/
Basically when you declare your viewpager in XML, give it the same left and right padding and set android:clipToPadding="false". (The clipToPadding is missing in his xml sample and necessary to achieve this effect)
Finally, I have added my solution for this question in GitHub. I have done some pretty tricks to get the workaround solution. You can get the project from the below link(Actually I have planned to create a blog with the explanation , but I dint have that much time to do).
Here is the link(https://github.com/noundla/Sunny_Projects/tree/master/CenterLockViewPager)
You have to copy the files from com.noundla.centerviewpagersample.comps package to your project. And you can see the usage of that Viewpager in MainActivity class.
Please let me know if anyone has problems with this.
I found solution in this post, below the code i used:
// Offset between sibling pages in dp
int pageOffset = 20;
// Visible part of sibling pages at the edges in dp
int sidePageVisibleWidth = 10;
// Horizontal padding will be
int horPadding = pageOffset + sidePageVisibleWidth;
// Apply parameters
viewPager.setClipToPadding(false);
viewPager.setPageMargin(UIUtil.dpToPx(pageOffset, getContext()));
viewPager.setPadding(UIUtil.dpToPx(horPadding, getContext()), 0, UIUtil.dpToPx(horPadding, getContext()), 0);
dpToPx code:
public static int dpToPx(int dp, Context context) {
float density = context.getResources().getDisplayMetrics().density;
return Math.round((float) dp * density);
}
This is all you need
You can use padding for viewPager and set clipToPadding false
Java
viewPager.setClipToPadding(false);
viewPager.setPadding(50, 0, 50, 0);
Kotlin
viewPager.clipToPadding = false
viewPager.setPadding(50, 0, 50, 0)
I had to center current page in view pager with different page widths, so solution with paddings was not suitable. Also user scrolling was disabled (it was tab bar view pager, scrolled by another view pager). Here is a very simple solution to do that - just override ViewPager.ScrollTo method just like this (C# code, Xamarin):
public override void ScrollTo(int x, int y)
{
x -= (int) (MeasuredWidth * (1 - Adapter.GetPageWidth(CurrentItem)) / 2);
base.ScrollTo(x, y);
}
And if you calculate page width for each fragment don't forget to cache them in array.
Extend HorizontalScrollView class as the parent for the scrolling view. In the onMeasure() method you can specify the width and height of each child. Little cumbersome way but the effect will be good and you can have a good hold on your child view.

Is margin animation slow?

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.

Categories

Resources