OnDraw() animations only run when the item is moving - java

I want to add animations to the itemView, however they only run if the item is currently moving. For example the item starts to fadeOut until the alpha is 0, but if the item stops moving, the animation pauses and the item only fades out halfway. After that, the item needs to start moving again for the animation to complete.
I know onDraw typically only gets called when something changes, but I guess the OS doesn't realize the style of the View is changed as well, so it doesn't call it.
I tried adding invalidate() at the end so I can force an update but it doesn't do anything.
How can I fix this?
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ACTION_STATE_SWIPE) {
if (optionsState == OptionState.UPVOTE) {
drawUpvoteOption(c, viewHolder.itemView, dX);
} else if (optionsState == OptionState.DOWNVOTE) {
if (!isRightSwipeMaxed(dX)) drawDownvoteOption(c, viewHolder.itemView, dX);
else drawDownvoteOption(c, viewHolder.itemView, MAX_RIGHT_SWIPE_DX);
}
setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
if (dX == 0) {
//This only works if the item is moving
viewHolder.itemView.animate().alpha(1f);
optionsState = OptionState.NONE;
}
if (!isRightSwipeMaxed(dX)) {
viewHolder.itemView.setTranslationX(dX);
} else {
viewHolder.itemView.setTranslationX(MAX_RIGHT_SWIPE_DX);
//This only works if the item is moving
viewHolder.itemView.animate().alpha(0);
}
//Doesn't do anything
viewHolder.itemView.invalidate();
}

I had the same issue, invalidate() on the itemView did nothing.
You have to do the invalidate() on the parent recyclerView for that to work :
viewHolder.animatedRecyclerView.invalidate()

Related

How to use item touch helper with heterogeneous view types?

I am following this tutorial Item touch helper example (well actually just the item touch helper part and that too because I want the red background with the delete icon seen when swiping). But I have two different view types in my recyclerview. How would I go about implementing it? I am stuck on this line
final View foregroundView = ((CartListAdapter.MyViewHolder) viewHolder).viewForeground;
in the RecyclerItemTouchHelper.java on the onChildDraw method
All ViewHolder instances have a method getItemViewType() which will return the same value your adapter originally returned from its own getItemViewType() method when the ViewHolder was created. You can use the value here to choose which of your own ViewHolder subclasses to cast to:
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
int viewType = viewHolder.getItemViewType();
if (viewType == MY_FIRST_TYPE) {
((MyFirstSubclass) viewHolder).foo();
// ...
} else if (viewType == MY_SECOND_TYPE) {
((MySecondSubclass) viewHolder).bar();
// ...
}
// ...
}

Android onScrolled not called when View is set to GONE

I am trying to hide a RelativeLayout when I scroll up and show it when I scroll down. onScroll works fine and is invoked every time until View is set to GONE.
final RelativeLayout placeHeaderMain = findViewById(R.id.place_header_main);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
// Scrolling up
placeHeaderMain.setVisibility(View.GONE);
} else {
// Scrolling down
placeHeaderMain.setVisibility(View.VISIBLE);
}
}
});
I want my listener to continue working after setting the View to Gone in order to make it Visible when scrolled down.
Thanks in advance.
Are there enough items to be scrolled?
That code above won't be triggered if dy == 0. It could be not enough items to make the scroll and it will return dy equal to 0, father more it won't to call onScroll(...)
What dy do you have when RelativeLayout has hidden?
Try to check that method below:
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
Try to set the view to INVISIBLE and not to GONE. when you set any view to View.GONE he is invisible and it doesn't take any space inside your layout , but when you set a view to View.INVISIBLE like before he will be invisible but unlike View.GONE your view still takes up space inside the layout.

RecyclerView not calling onScrolled at the bottom

I'm trying to create a RecyclerView with pagination and I have a problem with showing progress bar when I try to scroll down being already at the very end of the list. There's a callback RecyclerView.OnScrollListener which has a method onScrolled for handling scroll events, but it's not working when no actual scrolling has happened.
There's onScrollStateChanged method that works when I try to scroll down from the bottom, but my logic requires me to know direction of the gesture (up/down), so this method is not helpfull in my situation.
I currently trying to do it somewhat like this (which is obviously not working):
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
fetchDataFromServer();
}
});
EDIT: I tried to detect if the end is reached in onScrolled and it kinda worked, but sometimes when I try to fetch the data from the server I don't receive anything, so after such a request I'm still at the end of the list and I want to scroll down to try to update the list again, but I can't since onScrolled is not getting called.
Any ideas on how to fix that? Is there another callback that I can use?
I guess to show progress at the end of the recyclerview, you just need to make sure that end has been reached.
This is how it goes -
private int visibleThreshold = 1; // trigger just one item before the end
private int lastVisibleItem, totalItemCount;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = mLayoutManager.getItemCount();
lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
if (totalItemCount <= (lastVisibleItem + visibleThreshold) {
// ... end has been reached...
//... do your stuff over here...
}
}
});
}
EDIT:
sometimes when I try to fetch the data from the server I don't receive
anything
Simple! If your network request fails, try to catch an exception. If it is SocketTimeOutException, Retry doing the same request again. If there is an another exception(could be from the back-end or front-end), Get it fixed.
Check if your recycler view is nested inside NestedScrollView or something...
Remove the nested scroll view and it will work.
You can achieve any kind of feed by just using recycler view and use the VIEW_TYPE concept of recycler view
If you developing for min sdk level 23 then you can use setOnScrollChangeListener
recyclerView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
}
});

How to restrict ItemtouchHelper to swipe only left to right

Hi I am using recycler view and apply ITEMTOUCHHELPER its working.
I apply Logic (direction == ItemTouchHelper.LEFT) then delete item.
All of these things work correctly.
But when I swipe right side and then swipe left side. It give dX value >0.which means swiping done on right side.
If I delete an item no issue If I leave it as it is and swipe again then this strange behavior start.
When I swipe multiple times from left side then it gives dX<0 means then it starts again working.
Here is my Implementation
private void initSwipe(){
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT){
Cursor cursor = ((BookRecyclerAdapter)recyclerView.getAdapter()).getCursor();
cursor.moveToPosition(position);
int pageNo = cursor.getInt(cursor.getColumnIndex(BookMarkContract.AddsEntry.COLUMN_NAME_PAGE_NO));
dbHelper.deletePageNo(pageNo);
bookRecyclerAdapter.swapCursor(dbHelper.getAllBookMarks());
bookRecyclerAdapter.notifyItemRemoved(position);
bookRecyclerAdapter.notifyDataSetChanged();
}
else
return;
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
Bitmap icon;
Log.d("dX",""+dX);
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
if(dX>0)
{
dX=0;
}
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() - (float) itemView.getTop();
float width = height / 3;
if(dX>0)
return;
else {
p.setColor(Color.parseColor("#D32F2F"));
RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
c.drawRect(background, p);
icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_rub);
RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBottom() - width);
c.drawBitmap(icon, null, icon_dest, p);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
Here is my log cat Snippet of when i Swipping left but it gives me dX >0
PROBLEM IN ONE LINE
SWIPPING LEFT SIDE GIVES dX >0
QUESTION
Why I am getting this strange behavior ? it feels like an app is hanging
But it is not hanging it is swiping right side even When I swipe LEFT.
If you only want to allow leftswipe, this is your solution:
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START;
return makeMovementFlags(dragFlags, swipeFlags);
}
Here is what I think it is a more cleaner and simpler solution.
When you extend ItemTouchHelper.SimpleCallback in order to get the custom behavior, you should pass the directions for your items to be swiped or dragged, you don't need to override getMovementFlags since this method uses the drag and swipe directions we are about to pass. For example:
public class RecyclerItemTouchHelper extends ItemTouchHelper.SimpleCallback {
public RecyclerItemTouchHelper(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
}
// ...
}
Now, in your activity or fragment, when you setup your recycler view you can specify the swiping and dragging behavior like this:
// 0 means no dragging at all and the 2nd parameters means only swipe left is allowed
RecyclerItemTouchHelper helper = new RecyclerItemTouchHelper(0, ItemTouchHelper.LEFT);
new ItemTouchHelper(helper).attachToRecyclerView(yourRecyclerView);
Now you can configure your dragging and swiping however you want. I hope it helps!
For more information you can take a look at the official documentation, there's pretty much the exact example you need:
https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback

How to get the Scrollposition in the Recyclerview/Layoutmanager?

How to get the scrollposition in Recyclerview or the Layoutmanager?
I can measure the scrollposition by adding an OnScrollListener, but when the Orientation changes scrollDy is 0.
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
int scrollDy = 0;
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
scrollDy += dy;
}
});
For future use, If you are switching between Fragments within the same activity and all you want to do is save scroll-position for recyclerview and then restore recyclerview to the same scroll-position, you can do as follows:
In your onStop()/onDestroyView()/onPause() whatever callback is more appropriate, do this:
Parcelable recylerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
And In your onStart()/onCreateView()/onResume() whatever callback is more appropriate, do this:
recyclerView.getLayoutManager().onRestoreInstanceState(recylerViewState);
This way you can successfully keep your recyclerView's state in a parcelable and restore it whenever you want.
You cannot get it because it does not really exist. LayoutManager only knows about the views on screen, it does not know the views before, what their size is etc.
The number you can count using the scroll listener is not reliable because if data changes, RecyclerView will do a fresh layout calculation and will not try to re-calculate a real offset (you'll receive an onScroll(0, 0) if views moved).
RecyclerView estimates this value for scrollbars, you can use the same methods from View class.
computeHorizontalScrollExtent
computeHorizontalScrollRange
computeHorizontalScrollOffset
These methods have their vertical counterparts as well.
I came to the question just wanting to get the item position index that is currently scrolled to. For others who want to do the same, you can use the following:
LinearLayoutManager myLayoutManager = myRecyclerView.getLayoutManager();
int scrollPosition = myLayoutManager.findFirstVisibleItemPosition();
You can also get these other positions:
findLastVisibleItemPosition()
findFirstCompletelyVisibleItemPosition()
findLastCompletelyVisibleItemPosition()
Thanks to this answer for help with this. It also shows how to save and restore the scroll position.
recyclerView.computeVerticalScrollOffset() does the trick.
Found a work around to getting last scrolled position within the RecyclerView with credits to Suragch solution...
Declare a globally accessible LinearLayoutManager so a to be able to access it within inner methods...
When ever the populateChatAdapter method is called, if its the first call the scroll to position will be the last sent message and if the user had scrolled to view previous messages and method is called again to update new messages then they will retain their last scrolled to position...
LinearLayoutManager linearLayoutManager;
int lastScrollPosition = 0;
RecyclerView messages_recycler;
Initiate them...
messages_recycler = findViewById(R.id.messages_recycler);
linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setStackFromEnd(true);
messages_recycler.setLayoutManager(linearLayoutManager);
On your populate adapter method
private void populateChatAdapter(ObservableList<ChatMessages> msgs) {
if (lastScrollPosition==0) lastScrollPosition = msgs.size()-1;
ChatMessagesRecycler adapter = new ChatMessagesRecycler(msgs);
messages_recycler.setAdapter(adapter);
messages_recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastScrollPosition = linearLayoutManager.findLastCompletelyVisibleItemPosition();
}
});
messages_recycler.scrollToPosition(lastScrollPosition);
}
You have to read scrollDy in
public void onScrollStateChanged(RecyclerView recyclerView, int newState)
then you can get recyclerView's scroll position
To get ScroolPosition try this:
You will need this:
mLayoutManager = new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.HORIZONTAL, false);
And then get the position of the mLayoutManager (like this):
recyclerView
.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
int pos = mLayoutManager.getPosition(v);
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(pos);
}
});
With this "pos" you get the position, startin with the 0.
int pos = mLayoutManager.getPosition(v);

Categories

Resources