I am trying to get a parallax effect working. Such as in the sound cloud android app or something like this.
I have a RecyclerView where every list item has a FrameLayout and an ImageView in it and I need every background image in the RecyclerView to be parallaxed.
public class MyRecyclerView {
private static final Dictionary<Integer, Integer> listViewItemHeights = new Hashtable<Integer, Integer>();
...
}
... and in the onBindViewHolder method I have added this:
...
viewHolder.scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
private int getScroll() {
View c = discountSectionLayoutManager.getChildAt(0);
int scrollY = -c.getTop();
listViewItemHeights.put(discountSectionLayoutManager.findFirstVisibleItemPosition(), c.getHeight());
for (int i = 0; i < discountSectionLayoutManager.findFirstVisibleItemPosition(); ++i) {
if (listViewItemHeights.get(i) != null)
scrollY += listViewItemHeights.get(i); //add all heights of the views that are gone
}
return scrollY;
}
#Override
public void onScrollChanged() {
int scrollY = - getScroll();
viewHolder.imageFrameLayout.setTranslationY(scrollY * 0.1f);
}
}
The problem with this is that EVERY item will scroll up, and not just the ones that are visible. My RecyclerView is large with many items, and the items at the bottom will be all scrolled up as well. (for example if I can see item 1-3 and scroll down, even items 4-15 will scroll right as now). Maybe the getScroll() method can be modified somehow to only only scroll the background of the visible items?
Related
I haven't investigated this much yet, however I haven't seen a feature such as this done before or found much information regarding the subject, so I thought it would be best to reach out to SO to see if anyone has toyed with this idea before and could provide any advice. If not, I'll post the solution below when I find it.
Desired Outcome
Currently I have a GridView populated with content. I have a FloatingActionButton on top of the GridView and when the user taps on the FAB, I would like to display a new View. When tapped, I want each individual GridView item to rotate and move towards the edge of the screen resulting in the whole GridView parting to make room for the new View to slide in from the bottom. The RecyclerView will become unscrollable when the secondary View is present, and will stay that way until the user goes back, doing so would result in the opposite animation occurring bringing the ViewHolders back into the centre and closing the gap.
Excuse me for my appauling attempt at drawing my problem. :)
GridView pre-animation
GridView after animation
Each view item should animate independently from one another and the end animation will simulate a kind of exploding RecyclerView effect.
Possible Approach / Attempted so Far
I am currently reading up on the behaviour of the individual elements of the RecyclerView. I also got some pretty good information from this Android Summit video about RecyclerView animations.
Right now I think there are a couple of different approache:
Using two different LayoutManagers and attempting to animate between them.
Using an ItemDecorator to alter each ViewHolders margins and angle.
Any advice or recommendations will be greatly appreciated, I'll update the above list as I work through the problem.
Thanks!
Being able to animate the views within a RecyclerView turned out to be very simple! All you need is a custom RecyclerView.LayoutManager and you then have access to all the views which are visible for your animating pleasure.
The main method here is getChildCount() and getChildAt(int index), with both of these methods as seen below in getVisibleViews() you have access to all of the views which the RecyclerView is currently displaying. I then use a simple View.animate() call to animate each view to the desired X position and rotation. I'm sure this will work with all other types of animations too! I now have exactly what I wanted. :)
Here's the custom LayoutManager, remember if you don't extend a predefined LayoutManager such as GridLayoutManager or LinearLayoutManager you'll need to supply more methods such as generateDefaultLayoutParams().
public class AnimatingGridLayoutManager extends GridLayoutManager {
private static final int PARTED_ANIMATION_DURATION = 200;
private static final int PARTED_ANIMATION_VARIANCE = 20;
private static final int PARTED_ANIMATION_OFFSET = 25;
private static final int PARTED_ANIMATION_ANGLE = 15;
private boolean itemsParted;
...
public void setItemsParted(boolean itemsParted, Activity context) {
Display display = context.getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenWidth = size.x;
if (this.itemsParted != itemsParted) {
this.itemsParted = itemsParted;
if (itemsParted) {
partItems(context, screenWidth);
} else {
closeItems(screenWidth);
}
}
}
private void partItems(Context context, int screenWidth) {
List<View> visibleViews = getVisibleViews();
for (int i = 0; i < visibleViews.size(); i++) {
int viewWidth = visibleViews.get(i).getWidth();
int offset = ViewUtils.getPixelsFromDp(getRandomNumberNearInput(PARTED_ANIMATION_OFFSET), context);
int xPos = (-viewWidth) + offset;
int rotation = getRandomNumberNearInput(-PARTED_ANIMATION_ANGLE);
if (viewPositionIsRight(i)) {
// invert values to send view to end of screen instead of start
xPos = screenWidth - offset;
rotation = -rotation;
}
visibleViews.get(i).animate()
.x(xPos)
.rotation(rotation)
.setDuration(PARTED_ANIMATION_DURATION)
.setInterpolator(new FastOutSlowInInterpolator());
}
}
private void closeItems(int screenWidth) {
List<View> visibleViews = getVisibleViews();
for (int i = 0; i < visibleViews.size(); i++) {
int xPos = 0;
if (viewPositionIsRight(i)) {
xPos = screenWidth / 2;
}
visibleViews.get(i).animate()
.x(xPos)
.rotation(0)
.setDuration(PARTED_ANIMATION_DURATION)
.setInterpolator(new FastOutSlowInInterpolator());
}
}
private boolean viewPositionIsRight(int position) {
// if isn't 2 row grid new logic is needed
return position % 2 != 0;
}
private int getRandomNumberNearInput(int input) {
Random random = new Random();
int max = input + PARTED_ANIMATION_VARIANCE;
int min = input - PARTED_ANIMATION_VARIANCE;
return random.nextInt(max - min) + min;
}
private List<View> getVisibleViews() {
List<View> visibleViews = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
visibleViews.add(getChildAt(i));
}
return visibleViews;
}
}
I'm implementing ViewPager on Android TV to show 3 items like this (background green/purple are debug background to depict whole ViewPager's page):
I archieved that using:
<android.support.v4.view.ViewPager
...
android:paddingLeft="300px"
android:paddingRight="300px"
android:clipToPadding="false"
...
/>
When loosing focus on ViewPager I wanted central item to scale down and pages to come closer together. To achieve that plugged animation:
ValueAnimator animator = ValueAnimator.ofInt(300, 400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator){
Integer paddingHorizontal = (Integer) valueAnimator.getAnimatedValue();
vpScreenshots.setPadding(paddingHorizontal, 0, paddingHorizontal, 0);
}
});
animator.setDuration(300);
animator.start();
The animation works perfect for 1st element:
However when moving to next pages (both left and right) the padding animation starts to behave strange. When moving e.g. to right padding animation doesn't take action on left item but rather central and right ones. The effects goes deeper the more I move to right. The results are:
So switching pages in ViewPager spoils items alignment when padding animation occurs.
Why does it happen? How ti fix it ?
I managed to solve the issue. The only way which seems to work is to manipulate left and right items' "x" parameter.
Here is the code for focus listener:
if (hasFocus) {
Map<Integer, View> neighbors = vpScreenshots.findNeighbors();
View leftNeighbor = neighbors.get(0);
View rightNeighbor = neighbors.get(1);
if (!firstFocus) {
leftNeighbor.animate().xBy(-100).setDuration(FOCUS_ANIMATION_DURATION).start();
rightNeighbor.animate().xBy(100).setDuration(FOCUS_ANIMATION_DURATION).start();
}
firstFocus = false;
} else {
Map<Integer, View> neighbors = vpScreenshots.findNeighbors();
View leftNeighbor = neighbors.get(0);
View rightNeighbor = neighbors.get(1);
leftNeighbor.animate().xBy(100).setDuration(FOCUS_ANIMATION_DURATION).start();
rightNeighbor.animate().xBy(-100).setDuration(FOCUS_ANIMATION_DURATION).start();
}
Looking for neighbor items is attached to MyViewPager class:
public Map<Integer, View> findNeighbors() {
neighbors = new HashMap<>();
int currentPosition = getCurrentItem();
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childPosition = (int) childView.getTag();
if ((currentPosition - childPosition) == 1) {
neighbors.put(0, childView);
}
if ((currentPosition - childPosition) == -1) {
neighbors.put(1, childView);
}
}
return neighbors;
}
I want to scroll up and blur the image above it.I want image to be static and blur only when we scrolling up.
Use https://github.com/kikoso/android-stackblur
_stackBlurManager = new StackBlurManager(getBitmapFromAsset(this, "android_platform_256.png"));
_stackBlurManager.process(progress*5);
_imageView.setImageBitmap(_stackBlurManager.returnBlurredImage() );
Then you have to find the position of the user. Every view can have a scrolling listener, just like that:
view.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int scrollX = rootScrollView.getScrollX(); //for horizontalScrollView
int scrollY = rootScrollView.getScrollY(); //for verticalScrollView
int progress = // DO SOME MATH HERE TO GET THE PROGRESS FOR THE BLUR
}
});
Just mix the two things according to your situation.
normaly scrolling down within a Testcase I could use this peace of code:
onView( withId( R.id.button)).perform( scrollTo(), click());
However in this test i want to scroll down a Listview, where the childelements are loaded dynamically (I dont know how many there are), when the view is scrolled down. Is there anything like scroll to bottom in espresso like in Robotium?
My workarround:
int [] childCount = getListElements();
for(int i = 0;i<childCount[0];i++) {
onData(anything()).inAdapterView(withId(R.id.ScheduleOrderListViewListView)).atPosition(i).perform(click());
childCount = getListElements();
}
with
public int[] getListElements(){
final int[] counts = new int[1];
onView(withId(R.id.ScheduleOrderListViewListView)).check(matches(new TypeSafeMatcher<View>() {
#Override
public boolean matchesSafely(View view) {
ListView listView = (ListView) view;
counts[0] = listView.getCount();
return true;
}
#Override
public void describeTo(Description description) {
}
}));
return counts;
You should use onData() to interact with ListView elements. If you want to scroll to the bottom, just scroll to the last item. For example:
onData(instanceOf(ListData.class))
.atPosition(INITIAL_LIST_SIZE - 1)
.perform(scrollTo());
Actually I don't know how it properly called - overlay, parallax or slideUP, whatever, I have an Activity called "cafe details" which presents a Container(LinearLayout) with header information (name, min price, delivery time min e.t.c) and other container (ViewPager) which contains a ExpandableListView with something information (menus&dishes) and all I want to do is slide up my Viewpager when scrolls listview to scpecific Y position to cover(or overlay) header information.
A similar effect (but with parallax that I don't need to use) looks like this
I can detect when user scrolling listview down or up but how I can move container with ViewPager to overlay other container? Please give me ideas, regards.
UPD
I have tried a huge number of ways how to implement it and all of them unfortunately are not suitable. So now I have come to next variant - add scroll listener to ListView, calculate scrollY position of view and then based on that move the viewpager on y axis by calling setTranslationY();
Here is some code
1) ViewPager's fragment
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (getActivity() != null) {
((MainActivity) getActivity()).resizePagerContainer(absListView);
}
}
});
2) MainActivity
//object fields
int previousPos;
private float minTranslation;
private float minHeight;
<--------somewhere in onCreate
minTranslation = - (llVendorDescHeaders.getMeasuredHeight()+llVendorDescNote.getMeasuredHeight());
//llVendorDescHeaders is linearLayout with headers that should be hidden
//lVendorDescNote is a textView on green background;
minHeight = llVendorDescriptionPagerContainer.getMeasuredHeight();
//llVendorDescriptionPagerContainer is a container which contains ViewPager
--------->
public void resizePagerContainer(AbsListView absListView){
final int scrollY = getScrollY(absListView);
if (scrollY != previousPos) {
final float translationY = Math.max(-scrollY, minTranslation);
llVendorDescriptionPagerContainer.setTranslationY(translationY);
previousPos = scrollY;
}
}
private int getScrollY(AbsListView view) {
View child = view.getChildAt(0);
if (child == null) {
return 0;
}
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = child.getTop();
return -top + firstVisiblePosition * child.getHeight() ;
}
This simple solution unfortunately has a problem - it is blinking and twitching (I don't know how to call it right) when scrolls slowly. So instead setTranslationY() I've used an objectAnimator:
public void resizePagerContainer(AbsListView absListView){
.............
ObjectAnimator moveAnim = ObjectAnimator.ofFloat(llVendorDescriptionPagerContainer, "translationY", translationY);
moveAnim.start();
..............
}
I don't like this solution because 1) anyway it does resize viewpager with delay, not instantly 2) I don't think that is good idea to create many ObjectAnimator's objects every time when I scroll my listView.
Need your help and fresh ideas. Regards.
I'm assuming that you are scrolling the top header (the ImageView is a child of the header) based on the scrollY of the ListView/ScrollView, as shown below:
float translationY = Math.max(-scrollY, mMinHeaderTranslation);
mHeader.setTranslationY(translationY);
mTopImage.setTranslationY(-translationY / 3); // For parallax effect
If you want to stick the header/image to a certain dimension and continue the scrolling without moving it anymore, then you can change the value of mMinHeaderTranslation to achieve that effect.
//change this value to increase the dimension of header stuck on the top
int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height);
mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
mMinHeaderTranslation = -mHeaderHeight + tabHeight;
The code snippets above are in reference to my demo but I think it's still general enough for you.
If you're interested you can check out my demo
https://github.com/boxme/ParallaxHeaderViewPager
Have you tried CoordinatorLayout from this new android's design support library? It looks like it's what you need. Check this video from 3:40 or this blog post.