I have an horizontal recycler view snapping to center position. I want to set a bottom margin to centered items gradually. It's like os x dock grow effect, but i don't want to scale up, i want to push up.
I've done it with animations but it's not a good result and they are fired on recycler view scroll state changes.
Someone can indicate a solution to me?
Thanks in advance
After a lot of time I'm here to share my solution, more simple than I believed. I have extended RecyclerView and overriden onDraw method to set margin to RecyclerView's children based on their distance from center. It's not much clear, but now I have no time to make it better, and if someone need it, he can take a look to take inspiration
#Override
public void onDraw(Canvas c) {
super.onDraw(c);
int size = getChildCount();
for (int i = 0; i < size; i++) {
View v = getChildAt(i);
int x = DisplayHelper.pxToDp(act, (int) v.getX());
topMargin = x - startXCenter;
if (topMargin < -100 || topMargin > 100) {
topMargin = MAX_MARGIN;
} else if (topMargin < 0) {
topMargin *= -1;
}
topMargin = topMargin - MAX_MARGIN;
if (null != v) {
v.setY(topMargin);
}
}
}
Related
I need to set up a ViewPager with the following requirements:
- at any given time, 3 pages must be visible in the screen
- the center page isn't scaled and the behind pages are 60% of the center page;
- center page, must overlap a bit the behind pages.
From what I saw in other questions I've tried to do this:
viewPager.setClipToPadding(false);
int padding = (metrics.widthPixels - centerViewSize) / 2;
viewPager.setPadding(padding, 0, padding, 0);
viewPager.setPageMargin(-(padding / 2));
viewPager.setCurrentItem(10);
viewPager.setOffscreenPageLimit(3);
Doing this got me here:
This is what I was expecting. Just need to bring to front the current selected item.
I've then tried to scale the view with:
public static final float SCALED_SIZE = 0.6f;
viewPager.setPageTransformer(false, new PageTransformer() {
#Override
public void transformPage(final View page, final float position) {
if (position < -1) {
page.setScaleX(SCALED_SIZE);
page.setScaleY(SCALED_SIZE);
} else if (position <= 1) {
float scaleFactor = Math.max(SCALED_SIZE, 1 - Math.abs(position));
page.setScaleX(scaleFactor);
page.setScaleY(scaleFactor);
} else {
page.setScaleX(SCALED_SIZE);
page.setScaleY(SCALED_SIZE);
}
}});
But that gives me some problems.
The first item that should be selected is not the correct one.
Animation chopy.
At some points, there are more than 3 pages on the screen.
Scale is from the bottom and I would like to be from the center.
Any idea how I can solve my issues?
use Library UltraViewPager. It have very nice effects.
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 have some basic item decoration which draws some stuff in ItemDecoration.onDrawOver method.
This RecyclerView also has DefaultItemAnimator set on it.
Animations are working, all is great. Except one thing.
When all existing items are swapped with a new item set in this adapter, the decorations are being shown while animation is running.
I need a way to hide them.
When animation finishes, they need to be shown, but while it is running, they must be hidden.
I tried the following:
public void onDrawOver(..., RecyclerView.State state) {
if(state.willRunPredictiveAnimations() || state.willRunSimpleAnimations()) {
return;
}
// else do drawing stuff here
}
but this isn't helping. Decoration is only removed for the short period of animation, but then appears again while it is still running.
Also setup includes a RecyclerView.Adapter which hasStableIds() (in case that bit matters).
It may depend somewhat on the type of animation you're using, but at least for DefaultItemAnimator you need to account for the X/Y translation being done during the animation. You can get these values with child.getTranslationX() and child.getTranslationY().
For example, for the vertical case of onDraw/onDrawOver:
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
final int dividerHeight = mDivider.getIntrinsicHeight();
for (int i = 1; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int ty = (int)(child.getTranslationY() + 0.5f);
final int top = child.getTop() - params.topMargin + ty;
final int bottom = top + dividerHeight;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
(You may prefer to use ViewCompat.getTranslationY(child) if you need to support < API 11.)
Note: for other types of animations, additional adjustments may need to be made. (For example, horizontal translation might also need to be accounted for.)
Found an answer myself:
To hide item decorations during an item animation one can simply use this check in onDraw/onDrawOver:
public void onDrawOver(..., RecyclerView parent, ...) {
if(parent.getItemAnimator() != null && parent.getItemAnimator().isRunning()) {
return;
}
// else do drawing stuff here
}
You can try to check child alpha (only for case default animation). If alpha 0 then do nothing
I am working on an app and I have something similar to the Hangouts app where there is the sliding tabs under the ToolBar but I wanted to integrate it into the ToolBar instead of existing in the Activity/Fragment layout.
I have tried increasing the height of the ToolBar and setting a custom view, but if I do that, the custom view is in the middle of the ToolBar and not underneath the Title and Menu Overflow Button.
I have tried several things. Latest is:
toolBar.setCustomView(customView, layoutParams);
Any help would be great.
EDIT: Should have been more clear...
I want the tabs like this to be in the ToolBar not in the Fragment Layout like this,
https://developer.android.com/samples/SlidingTabsColors/res/layout/fragment_sample.html
You will have to use the OnScrollChanged function in your ScrollView. ActionBar doesn't let you set the opacity , so set a background drawable on the actionbar and you can change its opacity based on the amount of scroll in the scrollview. I have given an example workflow
The function sets gives the appropriate alpha for the view locationImage based on its position WRT window .
this.getScrollY() gives you how much the scrollView has scrolled
public void OnScrollChanged(int l, int t, int oldl, int oldt) {
// Code ...
locationImage.setAlpha(getAlphaForView(locationImageInitialLocation- this.getScrollY()));
}
private float getAlphaForView(int position) {
int diff = 0;
float minAlpha = 0.4f, maxAlpha = 1.f;
float alpha = minAlpha; // min alpha
if (position > screenHeight)
alpha = minAlpha;
else if (position + locationImageHeight < screenHeight)
alpha = maxAlpha;
else {
diff = screenHeight - position;
alpha += ((diff * 1f) / locationImageHeight)* (maxAlpha - minAlpha); // 1f and 0.4f are maximum and min
// alpha
// this will return a number betn 0f and 0.6f
}
// System.out.println(alpha+" "+screenHeight +" "+locationImageInitialLocation+" "+position+" "+diff);
return alpha;
}
An example working sample at https://github.com/ramanadv/fadingActionBar , you can have a look at it.
When I enlarge the size of the content of a scrollview, the scrollview takes a while to get to "know" this size change of it's child. How can I order the ScrollView to check it's child immediately?
I have an ImageView in a LinearLayout in a ScrollView.
In my ScaleListener.onScale, I change the size of my LinearLayout. I then try to order a scroll on the scrollview. In the ScaleListener.onScale:
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) imageView.getLayoutParams();
params.width = (int) (startX * scaleFactor);
params.height = (int) (startY * scaleFactor);
imageView.setLayoutParams(params);
(...)
scrollView.scrollBy(scrollX, scrollY);
However, no scrolling occurs when in the situation before the scaling scrolling was not possible because the view was too small to scroll. After the setLayoutParams, the view should be larger, but no scrolling occurs because the scrollview thinks the child is still small.
When a fes ms later the onScroll is called again, it does scroll fine, it somehow found out that the child is larger and scrollable.
How can I notify the scrollview immediately, that the child's size has changed? So that scrollBy will work right after setLayoutParams on it's child?
I found a solution after trying just about every onXXX() method. onLayout can be used. You can plan the scroll and do it later in onLayout().
Extend your scrollview, and add:
private int onLayoutScrollByX = 0;
private int onLayoutScrollByY = 0;
public void planScrollBy(int x, int y) {
onLayoutScrollByX += x;
onLayoutScrollByY += y;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
doPlannedScroll();
}
public void doPlannedScroll() {
if (onLayoutScrollByX != 0 || onLayoutScrollByY != 0) {
scrollBy(onLayoutScrollByX, onLayoutScrollByY);
onLayoutScrollByX = 0;
onLayoutScrollByY = 0;
}
}
Now, to use this in your code, instead of scrollBy(x,y) use planScrollBy(x,y). It will do the scroll at a time when the new size of the child is "known", but not displayed on screen yet.
When you use a horizontal or vertical scrollview, of course you can only scroll one way, so you will have to change this code it a bit (or not, but it will ignore the scroll on the other axis). I used a TwoDScrollView, you can find it on the web.
You can call:
scrollView.updateViewLayout(childView, childLayout)