In my activity I have a BottomNavigationView and a RecyclerView, and what I want to do is to hide the BottomNavigationView when I scroll the view, I'm using this code:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
BottomNavigationView navigationView = requireActivity().findViewById(R.id.bottom_navigation_view);
if (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING && dy > 0){
navigationView.animate()
.translationY(navigationView.getHeight());
}
else if(recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING && dy < 0)
navigationView.animate()
.translationY(0);
}
});
This works fine except for the fact that it still shows the bounds of the view, so after hiding the BottomNavigationView I get an empty rectangle. What is the best way to make the height of the view follow the translation?
I don't see how your "hiding" BottomNavigationView
but if your using
BottomNavigationView.setVisibility(View.INVISIBLE);
which hides the view but keeps its width and height.
it needs to be
BottomNavigationView.setVisibility(View.GONE);
which hides the view and makes the width = 0 and height = 0
I have a horizontal RecyclerView and am attempting to programatically scroll by an x value.
This has so far been achieved with smoothScrollBy(x, y), however, I can't for the life of me find a solution where I can set a scroll duration, e.g. 1000ms.
Any help would me much appreciated, thanks.
The code is as follows:
private void focus() {
View focusedRecyclerViewItem = getFocusedRecyclerViewItem();
TextView focusedTextView = getFocusedTextView(focusedRecyclerViewItem);
focusedTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 64);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecyclerView.clearOnScrollListeners();
countdown();
}
});
int x = (int) focusedRecyclerViewItem.getX() - mRecyclerView.getWidth() / 2;
mRecyclerView.smoothScrollBy(x, 0);
}
To clarify as the question was not initially clear - what I am looking for is a custom duration for the smoothScrollBy() method when it is called, not a duration before the smoothScrollBy() method is called.
Currently, if the 2nd item in the recyclerview is not visible to the user, a specific layout will be hidden/gone.
So this is my current code:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, final int dy) { final float test = dy;
if (layoutManager.findFirstCompletelyVisibleItemPosition() > 2) {
if(mAccountLayout.isShown() && mDateLayout.isShown()) {
mAccountLayout.setVisibility(View.GONE);
mDateLayout.setVisibility(View.GONE);
Log.d("SCROLLINGDOWN", "SCROLL");
Log.d("SCROLLdown",""+dy);
}
} else {
if(!mAccountLayout.isShown() && !mDateLayout.isShown()) {
mAccountLayout.setVisibility(View.VISIBLE);
mDateLayout.setVisibility(View.VISIBLE);
Log.d("SCROLLINGUP", "SCROLL");
Log.d("SCROLLUP",""+dy);
}
}
}
});
What i want is to have atleast an animation when the layout is being hidden/gone. Would likely similar to this: Sample animation without using coordinator layout.
If the user scroll slowly down, then the layout should be also slowly be hidden until the 2nd item in the recyclerview is no longer visible.
I create parallax effect moving the image background depends on location of first element of recycler view only.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if ((holder = recyclerView.findViewHolderForAdapterPosition(0)) != null) {
int offset = recyclerView.findViewHolderForAdapterPosition(0).itemView.getTop() / 10;
backgroundPhoto.setTop(offset);
}
The problem is : when the first item of recycler(header) scroll off the screen, background picture somehow jump ot initial position.
Once the view is scrolled off the screen the RecyclerView might still be able to access it but it's getTop() value will have some random value or 0 which will cause your parallax effect to jump.
You could keep a field in your class which saves the currently "scrolled distance" and add dx to it in the onScrolled(...) callback and use this value to calculate the parallax offset.
I've got a remove on swipe, that draws a background (much like the Inbox app), implemented by an ItemTouchHelper - by overriding the onChilDraw method and drawing a rectangle on the provided canvas:
ItemTouchHelper mIth = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
remove(viewHolder.getAdapterPosition());
}
public boolean onMove(RecyclerView recyclerview, RecyclerView.ViewHolder v, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
Drawable d = ContextCompat.getDrawable(context, R.drawable.bg_swipe_item_right);
d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
d.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
});
The remove method called above is in the Adapter:
public void remove(int position) {
items.remove(position);
notifyItemRemoved(position);
}
The background draws out nicely, but when notifyItemRemoved is called (according to Mr. Debugger), the RecyclerView first deletes my pretty green background, and then pushes the two adjacent items together.
I would like it to keep the background there while it does that (just like the Inbox app). Is there any way to do that?
I had the same issue and I didn't wanna introduce a new lib just to fix it. The RecyclerView is not deleting your pretty green background, it's just redrawing itself, and your ItemTouchHelper is not drawing anymore. Actually it's drawing but the dX is 0 and is drawing from the itemView.getLeft() (which is 0) to dX (which is 0) so you see nothing. And it's drawing too much, but I'll come back to it later.
Anyway back to the background while rows animate: I couldn't do it within ItemTouchHelper and onChildDraw. In the end I had to add another item decorator to do it. It goes along these lines:
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getItemAnimator().isRunning()) {
// find first child with translationY > 0
// draw from it's top to translationY whatever you want
int top = 0;
int bottom = 0;
int childCount = parent.getLayoutManager().getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getLayoutManager().getChildAt(i);
if (child.getTranslationY() != 0) {
top = child.getTop();
bottom = top + (int) child.getTranslationY();
break;
}
}
// draw whatever you want
super.onDraw(c, parent, state);
}
}
This code takes into account only rows animating up, but you should also consider rows coming down. That happens if you swipe delete the last row, rows above are gonna animate down to that space.
When I said your ItemTouchHelper is drawing too much what I meant was: Looks like ItemTouchHelper keeps ViewHolders of removed rows in case they need to be restored. It's also calling onChildDraw for those VHs in addition to the VH being swiped. Not sure about memory management implications of this behavior but I needed an additional check in the start of onChildDraw to avoid drawing for "fantom" rows.
if (viewHolder.getAdapterPosition() == -1) {
return;
}
In your case it's drawing from left=0 to right=0 so you don't see anything but the overhead is there. If you start seeing previously swiped away rows drawing their backgrounds that is the reason.
EDIT: I had a go at this, see this blog post and this github repo.
I managed to get it to work by using Wasabeefs's recyclerview-animators library.
My ViewHolder now extends the library's provided AnimateViewHolder:
class MyViewHolder extends AnimateViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
this.textView = (TextView) itemView.findViewById(R.id.item_name);
}
#Override
public void animateAddImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
#Override
public void preAnimateAddImpl() {
ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
ViewCompat.setAlpha(itemView, 0);
}
#Override
public void animateRemoveImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
}
The overrided function implementations are identical to what is in recyclerview-animators' readme on github.
It also seems necessary to change the ItemAnimator to a custom one and set the removeDuration to 0 (or another low value - this is to prevent some flickering):
recyclerView.setItemAnimator(new SlideInLeftAnimator());
recyclerView.getItemAnimator().setRemoveDuration(0);
This doesn't cause any problems as the normal (non-swiping) remove animation used is the one in the AnimateViewHolder.
All other code was kept the same as in the question. I haven't had the time to figure out the inner workings of this yet, but if anyone feels like doing it feel free to update this answer.
Update: Setting recyclerView.getItemAnimator().setRemoveDuration(0); actually breaks the "rebind" animation of the swipe. Fortunately, removing that line and setting a longer duration in animateRemoveImpl (500 works for me) also solves the flickering problem.
Update 2: Turns out that ItemTouchHelper.SimpleCallback uses ItemAnimator's animation durations, which is why the above setRemoveDuration(0) breaks the swipe animation. Simply overriding it's method getAnimationDuration to:
#Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
return animationType == ItemTouchHelper.ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
: DEFAULT_SWIPE_ANIMATION_DURATION;
}
solves that problem.
Just update the adapter position and then remove the animation
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT) {
remove(position);
} else {
mAdapter.notifyItemChanged(position);
}
}