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.
Related
I have two layouts (green on top, red on bottom) in a vertical LinearLayout (parent) looking similar to this:
.
When focus goes from the green to red, I would like the green to slide up off the screen and have the red simultaneously slide up with it and fill the whole screen. And when focus moves from red back up I want the green to slide back into the screen and return to the original configuration. I have tried looking at many other questions but none have had the solution I need. I tried just changing visibility between gone and visible but I want it to be a smooth animation. I've tried using parentLayout.animate().translationY(greenLayout.getHeight()) on the outer LinearLayout and that does give the animation I want but then the red does not expand to fill the screen, like this:
.
I know this question is similar to this one but that question is really old and only had one answer which didn't work for me.
My solution has a lot of different pieces, so I'll start with the full XML and java code, and then talk about the important bits:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="#+id/green"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#0f0" />
<View
android:id="#+id/red"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f00"/>
</LinearLayout>
In the XML, the only really important part is that the red view uses a height of 0dp and weight of 1. This means it takes up all extra vertical space, which will be important when we get rid of the green view.
public class MainActivity extends AppCompatActivity {
private int originalHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View green = findViewById(R.id.green);
final View red = findViewById(R.id.red);
green.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
green.getViewTreeObserver().removeGlobalOnLayoutListener(this);
originalHeight = green.getHeight();
}
});
green.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, originalHeight, 0);
}
});
red.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, 0, originalHeight);
}
});
}
private void animateHeightOfView(final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int height = (int) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = height;
view.setLayoutParams(params);
}
});
animator.start();
}
}
In the Java, the two main parts are the ViewTreeObserver.OnGlobalLayoutListener and the animateHeightOfView() method.
The OnGlobalLayoutListener exists to capture the green view's original height. We have to use a listener to do this instead of just writing originalHeight = green.getHeight() inside onCreate() because the green view isn't actually laid out at that point, so getHeight() would return 0 if we tried that.
The animateHeightOfView() method leverages the ValueAnimator class to animate the height of whatever view you pass to it. Since there's no direct setter for a view's height, we can't use simpler methods like .animate(). We set up the ValueAnimator to produce int values on every frame, and then we use a ValueAnimator.AnimatorUpdateListener to modify the view's LayoutParams to set the height.
Feel free to play with it. I'm using click listeners to trigger the animation, and you mentioned focus, but you should be able to call animateHeightOfView() in a different way if it suits you.
I have a list with userEntries. Each of these entries contains one block where I have some dynamic content, in this case a viewpager containing 1-3 images. My problem comes when I want to these images to have the correct Height. If I set my ViewPager height to for example 100dp the image is shown and the ViewPager works as expected. However if I rotate the phone or use a screen that is abit larger the image stays the same width as the 100dp is limiting it from getting the correct width. As this happends I want to use wrap_content so it resizes its height in order to fill the image to its full width, but when I do that the layout is minimized. See screens and code below.
CustomViewHolder.java <- This holds the userentry
public void populateContentArea(int mListType, Context context){
switch (mListType){
case Constants.ListType.DATE_LIST:
DatingEntryModel dateUser = (DatingEntryModel)mUser;
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View datingContent = layoutInflater.inflate(R.layout.dating_picture_container, llUserEntryContent, true);
ViewPager datingPager = (ViewPager) datingContent.findViewById(R.id.datingPager);
datingPager.setAdapter(new DatingPictureAdapter(context, dateUser.activities));
TabLayout datingIndicator = (TabLayout) datingContent.findViewById(R.id.datingTabDots);
datingIndicator.setupWithViewPager(datingPager);
break;
default:
break;
}
}
dating_picture_container.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v4.view.ViewPager
android:id="#+id/datingPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.design.widget.TabLayout
android:id="#+id/datingTabDots"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_width="70dp"
android:layout_height="20dp"
app:tabIndicatorHeight="0dp"
app:tabPaddingBottom="10dp"
app:tabBackground="#drawable/tab_dot_selector"
app:tabGravity="center"/>
</RelativeLayout>
DatingPictureAdapter.java
public class DatingPictureAdapter extends PagerAdapter {
private Context mContext;
List<DatingActivity> mActivities;
public DatingPictureAdapter(Context context, List<DatingActivity> activities) {
mContext = context;
mActivities = activities;
}
#Override
public Object instantiateItem(ViewGroup collection, int position) {
DatingActivity activity = mActivities.get(position);
final ImageView datingBanner = new ImageView(mContext);
datingBanner.setAdjustViewBounds(true);
datingBanner.setScaleType(ImageView.ScaleType.FIT_CENTER);
UrlManager.loadDatingBannerGlide(mContext, activity.id).into(datingBanner);
collection.addView(datingBanner, 0);
return datingBanner;
}
#Override
public int getCount() {
return mActivities.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
collection.removeView((View) view);
}
}
At the end of the day, setting the height static to 10dp and then resize depending on the image width was the best solution for me. Thanks for all responses :)
I think it's because of you set your ViewPager's height "wrap_content" wrap_content does not work for ViewPager. If you change it to match_parent or to a static dp your issue'll be fixed.
<android.support.v4.view.ViewPager
android:id="#+id/datingPager"
android:layout_width="match_parent"
android:layout_height="144dp"/>
I think you have 2 ways to do that:
1- if you want to use differents layouts with different orientation you must create one layout for each orientation
2- you can get progammatically get the height of display and put inside layout dinamically proportional height
i hope this help you
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.
I saw many examples demonstrative parallax background as you scroll or listview parallax but I cannot find a clear example how to implement a parallax effect on images as you scroll in the activity.
An example implementation can be found in Airbnb app. As you scroll down you can see more of the image's bottom and as you scroll up you can see more of the image's top.
Any hints and tips on how to create such effect?
There are a few libraries that to a parallax effect, it depends on your app if they are useful for your particular case, for example:
ParallaxScroll
Paralloid
Google is your friend pal ;) if none of these suits your needs then you have to create a custom ScrollView but that's a longer story, first give them a try and post your results.
Edit
If none of these fit your requeriments then this is what you have to do:
First, create a custom ScrollView so you can listen to scroll changes.
public class ObservableScrollView extends ScrollView {
public interface OnScrollChangedListener {
public void onScrollChanged(int deltaX, int deltaY);
}
private OnScrollChangedListener mOnScrollChangedListener;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(l - oldl, t - oldt);
}
}
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
}
Obviously you need to use this in your layout instead of the default ScrollView:
<your.app.package.ObservableScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
Also you need to wrap your ImageView inside a container to make the parallax work:
<FrameLayout
android:id="#+id/img_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</FrameLayout>
Finally set your Activity as a listener for your brand new ObservableScrollView and let the parallax begin:
public class MyActivity extends Activity implements ObservableScrollView.OnScrollChangedListener {
private ObservableScrollView mScrollView;
private View imgContainer;
#Override
public void onCreate(Bundle savedInstanceState) {
// Init your layout and set your listener
mScrollView = (ObservableScrollView)findViewById(R.id.scroll_view);
mScrollView.setOnScrollChangedListener(this);
// Store the reference of your image container
imgContainer = findViewById(R.id.img_container);
}
#Override
public void onScrollChanged(int deltaX, int deltaY) {
int scrollY = mScrollView.getScrollY();
// Add parallax effect
imgContainer.setTranslationY(scrollY * 0.5f);
}
}
You can modify the 0.5 value depending on how much parallax you want.
Edit
The above answer works fine if your ImageView is in the top of the activity. I am posting some code below to add the functionality to have the ImageView anywhere in the activity layout which I successfully made to work. These are generic calculations (might have some mistakes) and with a little tweak you can have it working for your own case.
For this example I have a fix height for the image container 200dp and for the image 240dp. The main purpose is when the image container is in the middle of the screen have no parallax effect and as the user scroll up or down to apply the effect. So as the image container get closer to the top of the screen or closer to the bottom of the screen the more of the parallax effect will be applied. The following calculations are a little hard to understand by reading them so try to make an example with real numbers in paper.
public class MyActivity extends Activity implements ObservableScrollView.OnScrollChangedListener {
private ObservableScrollView mScrollView;
private View imgContainer;
private ImageView mImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
// Init your layout and set your listener
mScrollView = (ObservableScrollView)findViewById(R.id.scroll_view);
mScrollView.setOnScrollChangedListener(this);
// Store the reference of your image container
imgContainer = findViewById(R.id.img_container);
// Store the reference of your image
mImageView = findViewById(R.id.img);
}
#Override
public void onScrollChanged(int deltaX, int deltaY) {
// Get scroll view screen bound
Rect scrollBounds = new Rect();
mScrollView.getHitRect(scrollBounds);
// Check if image container is visible in the screen
// so to apply the translation only when the container is visible to the user
if (imgContainer.getLocalVisibleRect(scrollBounds)) {
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics ();
display.getMetrics(outMetrics);
// Get screen density
float density = getResources().getDisplayMetrics().density;
// Get screen height in pixels
float dpHeight = outMetrics.heightPixels / density;
int screen_height_pixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpHeight, getResources().getDisplayMetrics());
int half_screen_height = screen_height_pixels/2;
// Get image container height in pixels
int container_height_pixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
// Get the location that consider a vertical center for the image (where the translation should be zero)
int center = half_screen_height - (container_height_pixels/2);
// get the location (x,y) of the image container in pixels
int[] loc_screen = {0,0};
imgContainer.getLocationOnScreen(loc_screen);
// trying to transform the current image container location into percentage
// so when the image container is exaclty in the middle of the screen percentage should be zero
// and as the image container getting closer to the edges of the screen should increase to 100%
int final_loc = ((loc_screen[1]-center)*100)/half_screen_height;
// translate the inner image taking consideration also the density of the screen
mImageView.setTranslationY(-final_loc * 0.4f * density);
}
}
}
I hope it can help someone that is looking for similar functionality.
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int top = scrollView.getScrollY(); // Increases when scrolling up ^
if(top != 0) {
int newTop = (int) (top * .5f);
imageFrame.setTop(newTop < 0 ? 0 : newTop);
}
}
});
Im new in Android world. I want to put some parallax background effects in my app.
How can I do it? How to approach to this in Android?
Is there any productive way to create 2-3 layer parallax background? Is there some tool, or class in android API?
Or maybe I have to modify background image location or margins "manually" in code?
Im using API level 19.
I have tried to understand Paralloid library, but this is too big to understand without any explanation. Im new to Android and Java, im not familiar with all Layouts and other UI objects, however I'm familiar with MVC.
I started bounty, maybe someone can explain step by step how that library works.
This is what you can do:
In your activity/fragment layout file specify 2 ScrollView's (say background_sv and content_sv).
<?xml version="1.0" encoding="utf-8"?>
<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" >
<com.example.parallax.MyScrollView
android:id="#+id/background_sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/parallax_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="..." />
</com.example.parallax.MyScrollView>
<com.example.parallax.MyScrollView
android:id="#+id/content_sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</com.example.parallax.MyScrollView>
</RelativeLayout>
Add a dummy view in the content scrollview of the height of the background and make it transparent. Now, attach a scroll listener to the content_sv. When the content scrollview is scrolled, call
mBgScrollView.scrollTo(0, (int)(y /*scroll Of content_sv*/ / 2f));
The existing API's doesn't have the support to get the scroll events.
Hence, we need to create a Custom ScrollView, to provide the ScrollViewListener.
package com.example.parallax;
// imports;
public class MyScrollView extends ScrollView {
public interface ScrollViewListener {
void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy);
}
private ScrollViewListener scrollViewListener = null;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if(scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
}
Here is the activity which hosts both the content ScrollView and background ScrollView
package com.example.parallax;
// imports;
public class ParallaxActivity extends Activity implements ScrollViewListener {
private MyScrollView mBgScrollView;
private MyScrollView mContentScrollView;
#Override
public void onCreate(Bundle savedInstanceState) {
mBgScrollView = findViewById(R.id.background_sv);
mContentScrollView = findViewById(R.id.content_sv);
mContentScrollView.setOnScrollListener(this);
}
// this is method for onScrollListener put values according to your need
#Override
public void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy) {
super.onScrollChanged(scrollView, x, y, oldx, oldy);
// when the content scrollview will scroll by say 100px,
// the background scrollview will scroll by 50px. It will
// look like a parallax effect where the background is
// scrolling with a different speed then the content scrollview.
mBgScrollView.scrollTo(0, (int)(y / 2f));
}
}
I think the question is unclear, so this is not really an answer so much as an attempt to clarify with more detail than I could include in a comment.
My question is about what kind of parallax effect you want to achieve. Given these three examples (they are demo apps you can install from the Play Store), which if any has the type of parallax effect you want? Please answer in a comment.
Paralloid Demo
Parallax Scroll Demo
Google IO App
Given an answer, we all will find it easier to help out. If you edit your question to include this information, it will be improved.
The following contains an example application published by the author of Paralloid:
https://github.com/chrisjenx/Paralloid/tree/master/paralloidexample
From the GitHub page under the 'Getting Started' section:
Layout
ScrollView
This is an example, please refer to the paralloidexample App for full
code.
<FrameLayout ..>
<FrameLayout
android:id="#+id/top_content"
android:layout_width="match_parent"
android:layout_height="192dp"/>
<uk.co.chrisjenx.paralloid.views.ParallaxScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="#+id/scroll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="192dp"/>
</uk.co.chrisjenx.paralloid.views.ParallaxScrollView>
</FrameLayout>
Fragment
Inside your onViewCreated() or onCreateView().
//...
FrameLayout topContent = (FrameLayout) rootView.findViewById(R.id.top_content);
ScrollView scrollView = (ScrollView) rootView.findViewById(R.id.scroll_view);
if (scrollView instanceof Parallaxor) {
((Parallaxor) scrollView).parallaxViewBy(topContent, 0.5f);
}
// TODO: add content to top/scroll content
Thats it!
Have a look at the Parallaxor interface for applicable Parallax
methods.
Hope this helps!
Also, here is a link to Google's 'getting started' page for android.
Also, here is a link to a 'java tutorial for complete beginners'.
As well as link to some documentation about layouts, which 'define the visual structure for a user interface'.
That being said, you would use the layout to define what the interface looks like and use the subsequent example code to define what happens when you interact with it.
P.S. You can see the application in action here
I use the ParallaxScroll library. Very easy to use, good samples and well documented.
Here is how it can be done using ScrollView and it's background image. I've committed the code in github.
You need to extend the ScrollView and Drawable classes.
By default the ScrollView background height will be same as viewport height. To achieve the parallax effect, the background height should be larger and should be based on the ScrollView child height and the background scrolling factor we want to impose.
Background scroll factor of 1 indicates, background height is same as ScrollView child height and hence background will scroll with same offset as the child scrolls.
0.5 indicates, background height is 0.5 times ScrollView child extended height and will scroll 50% slower compared to the child contents. This effectively brings the parallax scrolling effect.
Call following method from ScrollView constructor:
void init() {
// Calculate background drawable size before first draw of scrollview
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
// Remove the listener
getViewTreeObserver().removeOnPreDrawListener(this);
mDrawable = (ParallaxDrawable) getBackground();
if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
// Get the only child of scrollview
View child = getChildAt(0);
int width = child.getWidth();
// calculate height of background based on child height and scroll factor
int height = (int) (getHeight() + (child.getHeight() - getHeight()) * mScrollFactor);
mDrawable.setSize(width, height);
}
return true;
}
});
}
When ScrollView is scrolled, take into consideration the scroll offset while drawing the background. This basically achieves the parallax effect.
ParallaxScrollView:
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
// set the scroll offset for the background drawable.
mDrawable.setScrollOffset(x*mScrollFactor, y*mScrollFactor);
}
}
ParallaxDrawable:
#Override
public void draw(Canvas canvas) {
// To move the background up, translate canvas by negative offset
canvas.translate(-mScrollXOffset, -mScrollYOffset);
mDrawable.draw(canvas);
canvas.translate(mScrollXOffset, mScrollYOffset);
}
protected void onBoundsChange(Rect bounds) {
// This sets the size of background drawable.
mDrawable.setBounds(new Rect(bounds.top, bounds.left, bounds.left + mWidth, bounds.top + mHeight));
}
Usage of ParallaxScrollView and ParallaxDrawable:
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parallax_layout);
final ParallaxScrollView scrollView = (ParallaxScrollView) findViewById(R.id.sv);
ParallaxDrawable drawable = new ParallaxDrawable(getResources().getDrawable(R.drawable.bg));
scrollView.setBackground( drawable, 0.2f );
}
}
parallax_layout.xml:
<manish.com.parallax.ParallaxScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:text="#string/text" />
<View
android:layout_width="match_parent"
android:layout_height="5dp" />
...
</LinearLayout>
</manish.com.parallax.ParallaxScrollView>
The Android API does not support much concrete tools for it as you probably noticed. In API 20 they added elevation which is an attribute for depth. This does not support parallax layouts itself but I would say it's a step by Google to make this kind of work easier. If you want a wild guess on if and when, I would say that parallax utilities could be added before API 25 is released, based on the latest update and the progress in battery efficiency.
For now all you need is to listen for some kind of movement and change the views positions based on a value representing elevation.
Your question made me upgrade my own project and this is how I did it using ViewDragHelper inside a Fragment.
public class MainFragment extends Fragment implements View.OnTouchListener {
private ImageView mDecor, mBamboo, mBackgroundBamboo;
private RelativeLayout mRootLayout;
private ViewDragHelper mDragHelper;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRootLayout = (RelativeLayout) inflater.inflate(R.layout.fragment_main, container, false);
mRootLayout.setOnTouchListener(this);
mDecor = (ImageView) mRootLayout.findViewById(R.id.decor);
mBamboo = (ImageView) mRootLayout.findViewById(R.id.bamboo);
mBackgroundBamboo = (ImageView) mRootLayout.findViewById(R.id.backround_bamboo);
mDragHelper = ViewDragHelper.create(mRootLayout, 1.0f, new ViewDragHelper.Callback() {
private final float MAX_LEFT = -0;
private final float MAX_TOP = -20;
private final float MAX_RIGHT = 50;
private final float MAX_BOTTOM = 10;
private final float MULTIPLIER = 0.1f;
private final int DECOR_ELEVATION = 3;
private final int FRONT_BAMBOO_ELEVATION = 6;
private final int BACKGROUND_BAMBOO_ELEVATION = 1;
private float mLeft = 0;
private float mTop = 0;
#Override
public boolean tryCaptureView(View view, int i) {
return true;
}
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
mTop += dy * MULTIPLIER;
mTop = mTop > MAX_BOTTOM ? MAX_BOTTOM : mTop < MAX_TOP ? MAX_TOP : mTop;
mDecor.setTranslationY(mTop * DECOR_ELEVATION);
mBamboo.setTranslationY(mTop * FRONT_BAMBOO_ELEVATION);
mBackgroundBamboo.setTranslationY(mTop * BACKGROUND_BAMBOO_ELEVATION);
return 0;
}
#Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
mLeft += dx * MULTIPLIER;
mLeft = mLeft < MAX_LEFT ? MAX_LEFT : mLeft > MAX_RIGHT ? MAX_RIGHT : mLeft;
mDecor.setTranslationX(mLeft * DECOR_ELEVATION);
mBamboo.setTranslationX(mLeft * FRONT_BAMBOO_ELEVATION);
mBackgroundBamboo.setTranslationX(mLeft * BACKGROUND_BAMBOO_ELEVATION);
return 0;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy){
mRootLayout.requestLayout();
}
});
return mRootLayout;
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mDragHelper.processTouchEvent(motionEvent);
// you can still use this touch listener for buttons etc.
return true;
}
}
Hi You can go with the below-given code for ParallaxView class
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
public class ParallaxView extends SurfaceView implements Runnable {
private volatile boolean running;
private Thread gameThread = null;
// For drawing
private Paint paint;
private Canvas canvas;
private SurfaceHolder ourHolder;
// Holds a reference to the Activity
Context context;
// Control the fps
long fps =60;
// Screen resolution
int screenWidth;
int screenHeight;
ParallaxView(Context context, int screenWidth, int screenHeight) {
super(context);
this.context = context;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
// Initialize our drawing objects
ourHolder = getHolder();
paint = new Paint();
}
#Override
public void run() {
while (running) {
long startFrameTime = System.currentTimeMillis();
update();
draw();
// Calculate the fps this frame
long timeThisFrame = System.currentTimeMillis() - startFrameTime;
if (timeThisFrame >= 1) {
fps = 1000 / timeThisFrame;
}
}
}
private void update() {
// Update all the background positions
}
private void draw() {
if (ourHolder.getSurface().isValid()) {
//First we lock the area of memory we will be drawing to
canvas = ourHolder.lockCanvas();
//draw a background color
canvas.drawColor(Color.argb(255, 0, 3, 70));
// Draw the background parallax
// Draw the rest of the game
paint.setTextSize(60);
paint.setColor(Color.argb(255, 255, 255, 255));
canvas.drawText("I am a plane", 350, screenHeight / 100 * 5, paint);
paint.setTextSize(220);
canvas.drawText("I'm a train", 50, screenHeight / 100*80, paint);
// Draw the foreground parallax
// Unlock and draw the scene
ourHolder.unlockCanvasAndPost(canvas);
}
}
// Clean up our thread if the game is stopped
public void pause() {
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
// Error
}
}
// Make a new thread and start it
// Execution moves to our run method
public void resume() {
running = true;
gameThread = new Thread(this);
gameThread.start();
}
}// End of ParallaxView
To know more you can go **
here
**: http://gamecodeschool.com/android/coding-a-parallax-scrolling-background-for-android/