I have the following ViewPager:
I am trying to build a cover-flow-view. Which means that the position and the size of the views in the ViewPager change depending on touch input.
Everything works just fine with my implementation except for the drawing order of the views.
I can either call pager.setPageTransformer(false,PageTransformer()) which looks like in the image above, or setPageTransformer(true, PageTransformer()) which would reverse the drawing order.
The problem is, that i need the middle view(green) to be in front of its neighbours(blue,gray).
I read some posts about view.bringToFront() but I couldn't find a solution with that approach.
A middle-view-to-front-solution would be cool, but this would only work if there are three views visible at a time. With five views the fifth view would still overlap the fourth view. (confusing, isn't it?^^)
By the way, my minSdk is 16 to 18 so elevation(Lollipop) is not possible.
Here is my current PageTransformer
class PageTransformer() : ViewPager.PageTransformer {
val MIN_SCALE = 0.75f
override fun transformPage(view: View, position: Float) {
val pageWidth = view.width
val pageHeight = view.height
var vertMargin = pageHeight * (1 - MIN_SCALE) / 2
var horzMargin = pageWidth * (1 - MIN_SCALE) / 2
view.scaleX = MIN_SCALE
view.scaleY = MIN_SCALE
if (position < -2) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.translationX = horzMargin - vertMargin / 2
} else if (position <= 2) { // [-1,1]
// Modify the default slide transition to shrink the page as well
val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position))
vertMargin = pageHeight * (1 - scaleFactor) / 2
horzMargin = pageWidth * (1 - scaleFactor) / 2
if (position < 0) {
view.translationX = horzMargin - vertMargin / 2
} else {
view.translationX = -horzMargin + vertMargin / 2
}
// Scale the page down (between MIN_SCALE and 1)
view.scaleX = scaleFactor
view.scaleY = scaleFactor
view.rotationY = position * -30
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.translationX = -horzMargin + vertMargin / 2
}
}
}
Summing this up, I need a ViewPager which reverses its drawing order after the current Item.
Note: I have already tried every available library, but nothing worked.
I hope there is a genius who can help me :)
you can overwrite the getChildDrawingOrder of the viewpager class:
Like so
#Override
protected int getChildDrawingOrder (int childCount, int i) {
int j = getCurrentItem ();
if (i == j) {
return childCount - 1;
}
if (i == childCount - 1) {
return j;
}
if (j == 0) {
if (i == 1) {
i = 2;
} else if (i == 2) {
i = 1;
}
}
return super.getChildDrawingOrder (childCount, i);
}
Related
I was reading an example on customizing ViewPager's sliding page animation that entails translating the page(View) to a certain amount. The example is from the docs. Specifically, it is about an implementation called ZoomOutPageTransformer that can be set on a ViewPager through setPageTransformer() to customize how the page slides(incorporating a zoom animation in it).
This is how the end result is supposed to look like:
Now, they describe how they are going to do it:
In your implementation of transformPage(), you can then create
custom slide animations by determining which pages need to be
transformed based on the position of the page on the screen, which is
obtained from the position parameter of the transformPage() method.
The position parameter indicates where a given page is located
relative to the center of the screen. It is a dynamic property that
changes as the user scrolls through the pages. When a page fills the
screen, its position value is 0. When a page is drawn just off the
right side of the screen, its position value is 1. If the user scrolls
halfway between pages one and two, page one has a position of -0.5 and
page two has a position of 0.5. Based on the position of the pages on
the screen, you can create custom slide animations by setting page
properties with methods such as setAlpha(), setTranslationX(), or
setScaleY().
And this is the implementation of the PageTransformer that they have provided:
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
Problem:
I am unable to understand the statement,
view.setTranslationX(horzMargin - vertMargin / 2);
My understanding was that a value of 1.0 for position parameter equates to covering the screen width, i.e, w. So, if the center of a page has moved x units of position then the translation in terms of pixels/dp would be, w*x. But they are using some margin calculation for the translation amount calculation.
Can anyone explain how they have done this calculation and what would be wrong with my calculation?
There is one thing you are missing here, the PageTransformer is applied to the views AFTER they have been positioned.
So, with or without a PageTransformer attached to the PageView - when you scroll between pages - you simply doing a scroll (like in a LiseView) with SNAP capabilities.
The PageTransformer only adds effects on top of that.
So the purpose of,
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
is NOT to move the pages - but to compensate the page shrinking.
Without it, the page will have some ugly side effects. TRY IT, remove the lines :) - The views will go right/left but the positioning will be off.
So, the translation X does not move the page, but, in this case, simply manages the pages spacing to improve the animation feel - the above is the Math for it. What it does is to reduce the space between the pages based on the screen height/width. First it negates the space (horzMargin) then it adds a little spacing (- vertMargin / 2)
And that is why your calculation is not good (w*x) - You are trying to move the page - But it is already moving.
GitHub provide a library for animation that you want without any calculations
You don't need to create your custom ViewPager PageTransformer
Simply add below library in your app/build.gradle file.
compile 'com.ToxicBakery.viewpager.transforms:view-pager-transforms:1.2.32#aar'
MainActivity.java
public class MainActivity extends Activity {
private static final String KEY_SELECTED_PAGE = "KEY_SELECTED_PAGE";
private static final String KEY_SELECTED_CLASS = "KEY_SELECTED_CLASS";
private int mSelectedItem;
private ViewPager mPager;
private PageAdapter mAdapter;
#SuppressWarnings("deprecation")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int selectedPage = 0;
if (savedInstanceState != null) {
mSelectedItem = savedInstanceState.getInt(KEY_SELECTED_CLASS);
selectedPage = savedInstanceState.getInt(KEY_SELECTED_PAGE);
}
setContentView(R.layout.activity_main);
mAdapter = new PageAdapter(getFragmentManager());
mPager = (ViewPager) findViewById(R.id.container);
mPager.setAdapter(mAdapter);
try {
mPager.setPageTransformer(true, new TransformerItem(ZoomOutSlideTransformer.class).clazz.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
mPager.setCurrentItem(selectedPage);
}
public static class PlaceholderFragment extends Fragment {
private static final String EXTRA_POSITION = "EXTRA_POSITION";
private static final int[] COLORS = new int[] { 0xFF33B5E5, 0xFFAA66CC, 0xFF99CC00, 0xFFFFBB33, 0xFFFF4444 };
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final int position = getArguments().getInt(EXTRA_POSITION);
final TextView textViewPosition = (TextView) inflater.inflate(R.layout.fragment_main, container, false);
textViewPosition.setText(Integer.toString(position));
textViewPosition.setBackgroundColor(COLORS[position - 1]);
return textViewPosition;
}
}
private static final class PageAdapter extends FragmentStatePagerAdapter {
public PageAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public Fragment getItem(int position) {
final Bundle bundle = new Bundle();
bundle.putInt(PlaceholderFragment.EXTRA_POSITION, position + 1);
final PlaceholderFragment fragment = new PlaceholderFragment();
fragment.setArguments(bundle);
return fragment;
}
#Override
public int getCount() {
return 5;
}
}
private static final class TransformerItem {
final String title;
final Class<? extends PageTransformer> clazz;
public TransformerItem(Class<? extends PageTransformer> clazz) {
this.clazz = clazz;
title = clazz.getSimpleName();
}
#Override
public String toString() {
return title;
}
}
}
activity_main.xml
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="MainActivity" />
fragment_main.xml
<TextView 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:gravity="center"
android:text="#string/app_name"
android:textColor="#android:color/white"
android:textSize="72sp"
tools:context="MainActivity$PlaceholderFragment" />
I hope this may help you.:-)
As I re-read your question and this answer, I'm not sure I've really addressed your question. I think it may be of some help though so I'm posting it.
According to this, position=0 means this page is front & center (& for this example, full screen). We only care about a page with position in the -1 to +1 range, otherwise it is too far out of view to care about. You can see this in the 1st & last conditions where alpha is set to 0, making the view fully transparent.
I am unable to understand the statement,
view.setTranslationX(horzMargin - vertMargin / 2);
As I looked at it, I didn't see much value in this section of code as well. Since the margins are calculated based on the scaleFactor, & that is limited to 0.85 - 1.0, it doesn't make a lot of difference in the appearance of the transition. I'm sure that someone with a much better eye for design than me would disagree. However, I removed this portion of code as an experiment.
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
While I could see small differences depending on setting translationX if I looked closely, I'd never notice with casual use. More of a difference can be seen by setting MIN_SCALE to 0.5 (or smaller).
what would be wrong with my calculation?
Probably nothing as long as you limited it to a small result like the current code does. Keep in mind though that the ViewPager class is the primary controller of the animation rather than setTranslationX().
If you shrink the rectange by scaleFactor,
Assume Original Height is pageHeight and Width is pageWidth;
The new shrink Height will be scaleFactor * pageHeight
shrinkMarginTopBottom = pageHeight - scaleFactor * pageHeight = pageHeight(1 - scaleFactor);
and I assume it will be equally shirnked from bottom and top;
shrinkMarginTop = shrinkMarginTopBottom/2;
which is equal to below mentioned formula
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
So post explaning the margin calculations
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
The x translation should have some offset for page positions 0 and 1 to get the animation effect and some distance hence subtracting the margins for both ... The division happens because +ve and -ve x translation adds up to exact margin
I may be guessing but for me to have a variable x translation and margin which varies with scale factor, vertMargin is used for creating that offset which varies with scale.. if you remove vertMargin code it will still be animating the same but a bit faster but without keeping any margin between two pages.
This is what i must implement. I allready have a vertical viewpager but I lack experience in transformations and I don't know how to do this...
The top part of the next card to show is previewed below the card currently shown. When the next card is touched and dragged to the top the animation begins which that the next card is sliding upand at the same time the current card is zoomed out and then slided up a bit so that it's top part is shown after the transition.
I would greatly appreciate your help as it is a time sensitive matter
Does Below code follow your demands?
Make your original Transformer:
public class SlideUpTransformer implements ViewPager.PageTransformer {
#Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (-1 < position && position < 0) {
float scaleFactor = 1 - Math.abs(position) * 0.1f;
float verticalMargin = pageHeight * (1 - scaleFactor) / 2;
float horizontalMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horizontalMargin - verticalMargin / 2);
} else {
view.setTranslationX(-horizontalMargin + verticalMargin / 2);
}
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
}
view.setTranslationX(view.getWidth() * -position);
if (position > 0) {
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
}
}
}
Use like this:
viewPager.setPageTransformer(false, new SlideUpTransformer());
This works like this:
I'm having trouble understanding the usage of transformPage() method in PageTransformer class.
Take the Official ZoomOut Transformer as an example:
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
I understand we need to consider two views, one is on left(-1--0) and the other on right hand side(0--1)
but i dont understand the view.setTranslationX() usage, seems like even if i delete those lines of code, the difference is just the gap between the two views when swiping, see the pics I upload(also I delete the scaling code, as you can see there are even overlapping)
So my question is why should we use the setTranslationX() method during swiping , the effect on the view's position is like forever.
view.setTranslationX() adds a shift to initial position of the page inside ViewPager. By default pages just go side-by-side. This code:
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
makes them slightly overlaps. Page from the left of the center of ViewPager (position = 0) is shifted to the right and vise versa.
Currently i am using ViewPager to do a carousel effect menu
However i am facing some initialization issue
This is how i want my carousel to look like after the app start, look at the 2nd tile which is the video tile, it is dimmer and smaller than the 1st tile which is the music tile
However what i get is like this after the app start (if i didnt move it or drag it)
To make the carousel to look like the first image, i need to manually move it or drag it first, then everything look what it should be
Below is my code snippet for setting up the viewpage
pageAdapter = new MyPageAdapter(getSupportFragmentManager(), fragments);
pager = (ViewPager) findViewById(R.id.viewpager);
pager.setPageMargin(-700);//so that it look like carousel
pager.setOffscreenPageLimit(Menu.size());
pager.setAdapter(pageAdapter);
pager.setOnPageChangeListener(pageAdapter);
pager.setPageTransformer(true, new ZoomOutPageTransformer());
Below is my code snippet for including the zoomoutpagetransformer for animation between pages
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);// positive
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);// negative
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA));
textview.setText(String.valueOf(scaleFactor) + "\n" + String.valueOf(vertMargin) + "\n"
+ String.valueOf(horzMargin));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
Anyone has any idea how to let it look like what it should be after the app start without i move or drag it first?
Basically the PageTransformer's transformPage() method return the wrong position when you play with padding and margin on the view pager. Seems to be a framework bug as stated here.
Please see my answer in this related question providing a workaround.
I have a ViewPager in which each of it's views is a representation of card on a deck. Each card has a shadow on the border using the ViewPager margin:
cardsViewPager.setPageMargin(getResources().getDisplayMetrics().widthPixels / 20);
cardsViewPager.setPageMarginDrawable(R.drawable.shadow);
And it works as expected.
But, if I add a PageTransformer so that the cards on the right will stack on top of the cards on the left:
public class ScalePageTransformer implements PageTransformer {
private final ViewPager mViewPager;
public ScalePageTransformer(ViewPager viewPager) {
this.mViewPager = viewPager;
}
#Override
public void transformPage(View page, float position) {
if (position <= 0) {
int pageWidth = mViewPager.getWidth();
final float translateValue = position * -pageWidth;
if (translateValue > -pageWidth) {
page.setTranslationX(translateValue);
} else {
page.setTranslationX(0);
}
}
}
}
I do this by:
cardsViewPager.setPageTransformer(false, new ScalePageTransformer(cardsViewPager));
But now, the margin does not appear. If I had a zoom out effect on the PageTransformer, I can see when the current card is being scaled down, that the margin drawable is below the current card on the screen. Here is a pic to describe what's happening:
The blue card is being swiped from the right to left on top of the red card. Since the red card has a scale transformation, we can see the margin drawable in black behind it.
Is there a way to force the margin drawable to be on top of the red card? Shouldn't this be the default behavior?
Change to
cardsViewPager.setPageTransformer(true, new ScalePageTransformer(cardsViewPager));
First argument is the drawing order
Try using this code for the transformation stage of your View Pages :
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
Hope this helps...