Imageview parallax as you scroll
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);
}
}
});