I'm trying to emulate a parallax effect when sliding through my fragments, I've read about beginFakeDrag and fakeDragBy but to be honest, I don't know even if it's the best approach to my problem.
Anyone has done something similar with the ViewPager or have a hint about how should I approach this?
Thank you
An easy way to do this without using a custom library is to implement ViewPager.PageTransformer
I created my layouts to use the same id for their background element. For my example all background images will use the id background
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/background"
android:src="#drawable/img12"
android:scaleType="fitXY"/>
Then when I go in to implement ViewPager.PageTransformer I can reference that image like so:
public class Transformer implements ViewPager.PageTransformer{
#Override
public void transformPage(View page, float position) {
if(position >= -1 && position <= 1){
page.findViewById(R.id.background).setTranslationX(-position * page.getWidth() / 2);
} else {
page.setAlpha(1);
}
}
}
Finally, I assign my ViewPager.PageTransformer to my ViewPager like so.
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setPageTransformer(false, new Transformer());
This question is a bit old, but I found it when I was trying to do exactly that...
I implemented my own solution, basically extending ViewPager and overriding onDraw.
You can find all the code with a simple example here
I know that it's a bit old but take a look to that https://github.com/xgc1986/ParallaxPagerLibrary
It not overrides the onDraw method, and the effect not only with images, it works with every kind of view
mPager.setPageTransformer(false, new ParallaxTransformer(R.id.parallaxContent));
R.id.paraallaxContent is the id of the View you want to have this effect
unless the other solutions, don't need any any concrete structure to work, and also is layout independant
demo: youtube
Maybe this library can help you:
https://github.com/garrapeta/ParallaxViewPager
You can take a look at this :
https://github.com/luxsypher/ParallaxViewPager
you can apply a parallax effect on any element of your view and gave them all different speed
This is a ViewPager subclass I wrote, pretty easy to use. You don't need to do anything different with it, just include the dependency and use it as a standard ViewPager. Available on GitHub.
This library is fully customizable in x and y directions and includes alpha effects:
https://github.com/prolificinteractive/ParallaxPager
Installation (as of v0.7, check README for updates):
Add as a Maven Central dependency with Gradle
Use a ParallaxContainer in layout XML instead of ViewPager
Create a layout XML file for each page (the x/y/alpha attributes can be set separately for each object moving in/out of the page)
There are a few copy/paste lines to add to onCreate of your Activity
Maybe this library could be useful:
https://github.com/matrixxun/ProductTour
?
it uses "ViewPager.PageTransformer" for making things move differently inside.
Related
Please see the two screenshots :
The top part seems to be using a ViewPager. The current screen on the ViewPager shows partial of the previous and next screens.
Question : How do we achieve this animation ? i.e. where partial of the previous ane next screens can be seen ??
Just use margins/paddings in your case:
int leftRightPadding = ... ;
int margin = .... ;
viewPager.setClipToPadding(false);
viewPager.setPadding(leftRightPadding, 0, leftRightPadding, 0);
viewPager.setPageMargin(margin);
Also check JazzyViewPager or equivalent libs for zoom/fade animations.
We've all been advised against nesting views that contain a scrolling mechanism. However, in the latest Android release (5.0), the Phone app caught my attention with what seems to be a ListView inside of a ScrollView.
What really intrigued me was that the scrolling mechanism switched from the ScrollView to the ListView seamlessly.
Notice the content above the tabs is pushed out of view before the actual ListView begins scrolling.
I've tried duplicating this myself, but ended up unsuccessful. Here is the basic approach I was taking...
With a single, continuous touch event (no lifting of the finger) ...
As user scrolls, the ListView slowly covers up the ImageView. Once the ImageView is 100% covered and the ListView takes up the entire screen, the ListView begins to scroll.
I'm currently listening to touch events on the ListView and if the top has been reached, call requestDisallowInterceptTouchEvent on the ListView, i.e.
#Override
public boolean onTouch(View v, MotionEvent event) {
if (listViewAtTop) {
v.requestDisallowInterceptTouchEvent(true);
} else {
v.requestDisallowInterceptTouchEvent(false);
}
return false;
}
The switching scrolling context works, only if you lift your finger and continue scrolling.
Is there a different approach that will achieve the desired effect?
Android 5.0 Lollipop (API 21) added nested scrolling support.
From what I can tell, both ListView (AbsListView) and ScrollView support this now (if running on API 21), but it must be enabled on the scrolling views.
There are two ways, by calling
setNestedScrollingEnabled(true) or with the layout attribute android:nestedScrollingEnabled="true" (which is undocumented)
To learn about how it works, or to support this for a custom widget, the key methods are these:
onStartNestedScroll
onNestedScrollAccepted
onNestedPreScroll
onNestedScroll
onStopNestedScroll
Unfortunately, there is no guide or training which explains how this works other than the JavaDoc itself which is rather light and there are no examples other than ScrollView.
Add latest support package 'com.android.support:support-v4:22.1.1' to your project. And try this:
<android.support.v4.widget.NestedScrollView
android:id="#+id/nScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<FrameLayout ...>
<ListView ... />
</FrameLayout >
</android.support.v4.widget.NestedScrollView>
By default nested scrolling is Enabled.
While trying to figure out how to solve this issue myself, I found this question first; however, the answer didn't really go into too much detail. I did find a lot of good resources, so if anyone else finds themselves looking for this, I'll link them below. A term for this effect is "Sticky Scrolling".
An article talking about "Synchronized Scrolling".
http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
A good video showcasing some Android scrolling tricks, "Quick Return" and "Sticky Scrolling".
https://www.youtube.com/watch?v=PL9s0IJ9oiI
Code:
https://code.google.com/p/romannurik-code/source/browse/misc/scrolltricks
And lastly, here is another one showcasing the same effect using a listView instead of a ScrollView.
https://www.youtube.com/watch?v=rk-tLisxSgM
Code:
https://github.com/jatago/list_sticky_scroll_trick
I found an alternative 'trick' which is quite simple... Use only a ListView with an added transparent header.
I have been wanting to achieve the same effect as well. I came up finding a relevant library called ObservableScrollView in GitHub and it requires more work on the back-end via a TouchInterceptFramework but at least it did the job even for pre-lollipop devices. It also supports not only child scrollviews and listviews but also recyclerviews. Here's the link:
https://github.com/ksoichiro/Android-ObservableScrollView
I hope they consider nested scrolling for both lollipop and pre-lollipop devices as a part of their design standard soon. This is a good sign.
This is classic example of dummy layouts. Something not entirely obvious at first look. Basically the scenario is something like this.
Grey Area->FrameLayout
Followed by a listview that fills up the entire framelayout and followed by a imageview that overlaps the top half of a listview. The listview's first item is a dummy item and has a height identical to that of the imageview.
(Note: The actual data starts from the second element)
Next Step is easy
Translate the Imageview as per the scroll of the listview.
I suppose this is the best way to do that whilst avoiding nested scrolling
You can use the following combination of attributes on your ListView to achieve this:
<ImageView ... /> <!-- must be before ListView -->
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="..." <!-- height of imageView -->
android:clipToPadding="false"
...
/>
You don't have to manage any scrolling in your code at all, and it requires no header/dummy views in your list adapter.
I am using something like this and it works ok I think
scrollView.onScroll { x, y ->
Timber.d("ScrollView offset: ($x, $y)")
val height = dashboardChart.measuredHeight
val recyclerView = viewPager.findViewById<RecyclerView>(R.id.recyclerView)
if(y >= height) {
Timber.d("ScrollView enable nested scrolling!")
recyclerView.isNestedScrollingEnabled = true
} else {
Timber.d("ScrollView disable nested scrolling!")
recyclerView.isNestedScrollingEnabled = false
}
}
Where scrollView is parent I am listening onScroll event (it is extension underneath it is viewTreeObserver.addOnScrollListener). Then depending whether I've scrolled initial offset or not I am enabling/disabling child recyclerView (similary ListView or other scrollView) scrolling.
I have a nested layout like the following:
<LinearLayout> <!----Parent layout--->
<LinearLayout> <!-----child 1--->
...
</LinearLayout> <!----child 1 ended--->
<LinearLayout> <!-----child 2--->
...
</LinearLayout> <!----child 2 ended--->
</LinearLayout> <!----Parent endded--->
The problem I am having now is that since all my data items are within child 1 or child 2 Linearlayout, if I add or delete a item the child linearlayout will animated with the effect of animateLayoutChanges but the parent layout will not do any animation. (I have android:animateLayoutChanges set to true for all linear layouts). Especially when I delete an item within child 1 the animation effect becomes weird (basically child 2 will jump up while child 1 is still doing its animation).
Does anybody have any idea how to solve this?
Thanks
UPDATE
Shortly after I posted this question, I found this on android developer's site in the LayoutTransition API.
Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the interrelationship of the various levels of layout.
So does anyone have any work around suggestions for this issue?
The animateLayoutChanges property makes use of LayoutTransitions, which animate both the layout's children and, from Android 4.0 onward, ancestors in the layout hierarchy all the way to the top of the tree. In Honeycomb, only the layout's children will be animated. See this Android Developers Blog post for details.
Unfortunately, it seems that there's currently no simple way to have the siblings of a layout react to its LayoutTransitions. You could try using a TransitionListener to get notified when the layout's bounds are being changed, and move the sibling views accordingly using Animators. See Chet Haase's second answer in this Google+ post.
EDIT - Turns out there is a way. In Android 4.1+ (API level 16+) you can use a layout transition type CHANGING, which is disabled by default. To enable it in code:
ViewGroup layout = (ViewGroup) findViewById(R.id.yourLayout);
LayoutTransition layoutTransition = layout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
So, in your example, to have child 2 layout animated, you'd need to enable the CHANGING layout transformation for it. The transformation would then be applied when there is a change in the bounds of its parent.
See this DevBytes video for more details.
Ok, after digesting the first answer, I make it simple here, for those who don't get proper animation result when using android:animateLayoutChanges="true" in NESTED layout:
Make sure you add android:animateLayoutChanges="true" to the will-be-resized ViewGroup (LinearLayout/RelativeLayout/FrameLayout/CoordinatorLayout).
Use setVisibility() to control the visibility of your target View.
Listen carefully from here, add android:animateLayoutChanges="true" to the outer ViewGroup of your will-be-resized ViewGroup, this outer ViewGroup must be the one who wraps all the position-changing View affected by the animation.
Add following code in your Activity before the setVisibility(), here the rootLinearLayout is the outer ViewGroup I mentioned above:
LayoutTransition layoutTransition = rootLinearLayout.getLayoutTransition();
layoutTransition.enableTransitionType(LayoutTransition.CHANGING);
Before:
After:
Reminder: If you miss the 3rd step, you will get null pointer exception.
Good luck!
As a Kotlin Extension
fun ViewGroup.forceLayoutChanges() {
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
}
Usage
someContainer.forceLayoutChanges()
Notes:
In my experience, this happens when the container is a deep nested layout. For some reason android:animateLayoutChanges="true" just doesn't work, but using this function will force it to work.
We had added the android:animateLayoutChanges attribute to our LinearLayout but the change didn’t trigger an animation. To fix that, use this code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((ViewGroup) findViewById(R.id.llRoot)).getLayoutTransition()
.enableTransitionType(LayoutTransition.CHANGING);
}
More details.
It seems that a delayed transition on the parent also works for animating. At least for me the following code gives a proper expand/collapse animation.
expandTrigger.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(parentLayout);
expanded = !expanded;
child1.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
});
For deeply nested layouts you sometimes might need to use a parent higher up in the hierarchy in the call to the TransitionManager.
I had a similar issue:
I was using a animatelayoutchanges in one of my activity with a recycler view, I also added some custom layout transition because I wanted to increase speed of the animation while an item disappears in the list. It was working fine when it was not in a nested layout.
I had used the same adapter for another recyclerview which was in a nested layout. It was not working and I tried all the above solutions, None worked for me.
The real reason was, I forgot to set
mTicketsListAdapter.setHasStableIds(true);
in the nested layout activity. And after setting setHasStableIds to true, the animations was working perfectly in the nested layout.
Is there a possibility to display two pages at the same time, when using a ViewPager? I'm not looking for an edge effect, but rather for two full pages at the same time.
Please have a look at the getPageWidth Method in the corresponding PagerAdapter. Override it and return e.g. 0.8f to have all child pages span only 80% of the ViewPager's width.
More info:
http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getPageWidth(int)
See my more up-to-date answer here: Can ViewPager have multiple views in per page?
I discovered that a perhaps even simpler solution through specifying a negative margin for the ViewPager. I've created the MultiViewPager project on GitHub, which you may want to take a look at:
https://github.com/Pixplicity/MultiViewPager
Although this question specifically asks for a solution without edge effect, some answers here propose the workaround by CommonsWare, such as the suggestion by kaw.
There are various problems with touch handling and hardware acceleration with that particular solution. A simpler and more elegant solution, in my opinion, is to specify a negative margin for the ViewPager:
ViewPager.setPageMargin(
getResources().getDimensionPixelOffset(R.dimen.viewpager_margin));
I then specified this dimension in my dimens.xml:
<dimen name="viewpager_margin">-64dp</dimen>
To compensate for overlapping pages, each page's content view has the opposite margin:
android:layout_marginLeft="#dimen/viewpager_margin_fix"
android:layout_marginRight="#dimen/viewpager_margin_fix"
Again in dimens.xml:
<dimen name="viewpager_margin_fix">32dp</dimen>
(Note that the viewpager_margin_fix dimension is half that of the absolute viewpager_margin dimension.)
We implemented this in the Dutch newspaper app De Telegraaf Krant:
You have to override the getPageWidth() method on the viewpager's Adapter. For Example:
#Override
public float getPageWidth(int position) {
return 0.9f;
}
Try that code in your Adapter and then you'll understand.
See that blog entry.PagerContainer technique is solved my problem.
EDIT:
I found same answer.
How to migrate from Gallery to HorizontalScrollView & ViewPager?
You can solve this problem by using getPageWidth in PagerAdapter class.
Be sure about whether your JAR is up-to-date.Since previously ViewPager was not supporting multiple pages at the same time.
public float getPageWidth(){
return 0.4f;(You can choose it .For full screen per page you should give 1f)
}
create your layout file: my_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="#+id/pager_container"
android:layout_width="match_parent"
android:layout_height="240dp"
android:clipChildren="false">
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="200dp"
android:clipToPadding="false"
android:layout_height="200dp"
android:layout_marginLeft="70dp"
android:layout_marginRight="70dp"
android:layout_gravity="center" />
</FrameLayout>
</layout>
the viewpager is about as small as you want your pages to be. With android:clipToPadding="false" the pages outside the pager are visible as well. But now dragging outside the viewpager has no effect This can be remedied by picking touches from the pager-container and passing them on to the pager:
MyLayoutBinding binding = DataBindingUtil.<MyLayoutBinding>inflate(layoutInflater, R.layout.my_layout, parent, false)
binding.pager.setOffscreenPageLimit(3);
binding.pager.setPageMargin(15);
binding.pagerContainer.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return chatCollectionLayoutBinding.pager.onTouchEvent(event);
}
});
I don't think that's possible with the limitations of a View Pager. It only allows a user to view pages 1 at a time. You might be able to work something out with the use of fragments, and have 2 ViewPager fragments side by side and use buttons to work out the page flipping you want to implement, but the code may become very complex.
You can try something like a ViewFlipper - where you can do a lot more customizations (including animations).
Hope this helps.
It can be don in the Item Layout of the Viewpager. Assume that, you want two pages at a time.
This is the Item Layout (photo_slider_item.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" style="#style/photosSliderItem">
<ImageView android:id="#id/photoBox1" style="#style/photoBox"/>
<ImageView android:id="#id/photoBox2" style="#style/photoBox"/>
</LinearLayout>
And in your PagerAdapter:
#Override
public View instantiateItem(ViewGroup container, int position){
View sliderItem = LayoutInflater.from(container.getContext()).inflate(photo_slider_item, container, false);
ImageView photoBox1 = (ImageView) sliderItem.findViewById(R.id.photoBox1);
ImageView photoBox2 = (ImageView) sliderItem.findViewById(R.id.photoBox2);
photoBox1.setImageResource(photosIds[position]);
if(position < photosIds.length-1){
photoBox2.setImageResource(photosIds[position+1]);
} else {photoBox2.setImageResource(photosIds[0]);}
container.addView(sliderItem);
return sliderItem;
}
Edited version for more than two items
if you want more than two items, first add your items to the LinearLayout then use following algorithm:
photoBox1.setImageResource(photosIds[position]);
if(position < photosIds.length-i-1){
photoBox2.setImageResource(photosIds[position+1]);
photoBox3.setImageResource(photosIds[position+2]);
.
.
.
photoBox(i).setImageResource(photosIds[position+i-1]);
} else if(position < photosIds.length-i-2){
photoBox2.setImageResource(photosIds[position+1]);
photoBox3.setImageResource(photosIds[position+2]);
.
.
.
photoBox(i-1).setImageResource(photosIds[position+i-2]);
photoBox(i).setImageResource(photosIds[0]);
} . . . else if(position < photosIds.length-1){
photoBox2.setImageResource(photosIds[position+1]);
photoBox3.setImageResource(photosIds[0]);
photoBox4.setImageResource(photosIds[1]);
.
.
.
photoBox(i-1).setImageResource(photosIds[i-2-2]);
photoBox(i).setImageResource(photosIds[i-2-1]);
} else {
photoBox2.setImageResource(photosIds[0]);
photoBox3.setImageResource(photosIds[1]);
.
.
.
photoBox(i-1).setImageResource(photosIds[i-1-2]);
photoBox(i).setImageResource(photosIds[i-1-1]);
}
i : number of your items
[i-2-2] : number 2 in the middle is number of items in the last page of the viewpager.
Suppose I have 2 XML files and my activity will setContentView the appropriate one based on some button press from the user. Is it possible to change the transition animation for the changing of content view?
So far I see super.overridePendingTransition() which is suitable for starting new activities, however my example does not start a new activity, it just changes the layout in the current one.
Mathias Lin has explained it very well.
You can always use default stock animations supplied by Android framework.
Heres an example code:
boolean isFirstXml=evaluatingConditionFunction();
LayoutInflater inflator=getLayoutInflater();
View view=inflator.inflate(isFirstXml?R.layout.myfirstxml:R.layout.myseconxml, null, false);
view.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_out_right));
setContentView(view);
Call this from any of your activity which holds your Parent View.
For custom animations you can visit developer docs. Heres the documentation link.
Yes, you can apply an animation on almost any view you like. Just via view.startAnimation(animation);
Take the outer viewgroup of your respective layout (content view) and apply the animation to it. Depending what kind of animation you want to do, it might make sense to inflate/load both layouts but hide one of them and then swap. Please specify what kind of transition you have in mind.
For example: if you do an alpha transition, you would run the alphaAnimation on the current layout, when when the animation ends (AnimationListener), you set the content view to the new layout, and fade the content back in, via another alphaAnimation.
A better solution is using ViewFlipper: it is a FrameLayout, that can do animations when changing the views.
<ViewFlipper
android:id="#+id/[your_id_here]"
android:inAnimation="..."
android:outAnimation="..."
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RelativeLayout
<!--Your first layout -->
</RelativeLayout>
<RelativeLayout
<!--Your second layout -->
</RelativeLayout>
</ViewFlipper>
Then, switch the views with setDisplayedChild(int) or showNext() or showPrevious. If you want to have different animation for left and right movement, you have to set inAnimation and outAnimation in the code before transition.
More complete example is here.