I need to be able to programmatically dismiss an item inside a RecyclerView without the user actually swiping (instead I want to dismiss the item when they tap a button in the card). A lot of libraries I've seen only seem to support actual swipes.
I've tried using an existing library and just simulate a MotionEvent by creating a swipe on my own programmatically, but this interferes with another horizontal-swipe listener, so I'm wondering how else this could be done, ideally for a RecyclerView but if anyone knows how to for a ListView instead I can try to adapt that.
I've looked at this library as well as others for inspiration but I can't figure out how to trigger the swipes programmatically instead.
Use a ListView or RecyclerView with custom adapter, and call notifyDataSetChanged after removing an item from the datalist:
private void removeListItem(View rowView, final int position) {
Animation anim = AnimationUtils.loadAnimation(this,
android.R.anim.slide_out_right);
anim.setDuration(500);
rowView.startAnimation(anim);
new Handler().postDelayed(new Runnable() {
public void run() {
values.remove(position); //Remove the current content from the array
adapter.notifyDataSetChanged(); //Refresh list
}
}, anim.getDuration());
}
Use one of the libraries that offer the swipe to dissmis funcionality ad extract the animation part, if im not mistaken its at the action_up at the onTouch(). Then call it from your onClick of the button.
Related
I have a listview with 150+ items, I need to make one visible from code. I currently use smoothscrolltoposition but when the desired item is far away from the current visible item it takes several seconds to arrive.
Is there anyway to simply get rid of the smooth scrolling and simply make the item visible directly?
Thanks,
Ignacio
You can use postdelayed for smooth scroll
listview.postDelayed(new Runnable() {
#Override
public void run() {
// smoothscrolltoposition
}
}, 100);
After several testing and reading the thread suggested by audi , I got this solution:
Strangely, the trick is in reassign the adapter to the listview, not even it is needed to recreate it, just reassign.
listView.Adapter = adapter;
listView.FastScrollEnabled = true;
listView.SetSelection(index);
adapter.NotifyDataSetChanged();
In iOS when data changes, i.e. a record is being deleted, calling UITableView, deleteRowsAtIndexPaths method, will show a quick animation and that GUI row will dismiss at the end.
Is it any similar approach in Android? So I do not want just 'rerender' the RecycleView, but 'rerender' only certain rows, and show an 'insert' or 'delete', or 'update' animation on it.
Put this method inside your adapter.
public void removeAt(int position) {
YOUR_LIST.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, YOUR_LIST.size());
}
and call removeAt(POSITION_TO_REMOVE) from where you want to perform delete operation.
It will remove view from RecycleView and animate with slideUp animation.
I need to set the firstVisibleItem. right now when I use smoothScrollToPositionFromTop there seems to be some visible scrolling (of course). I am drawing a blank on what the alternative method is. I have used it before, but I just can't seem to find it. Basically instead of smooth scrolling, it "instantaneously" sets the position that I select plus whatever offset I pass it. Does anyone remember which method does this? It's something like
listView.setAsTop(position, offset)
Use AbsListView#setSelection(int position):
new Handler().post(new Runnable() {
#Override
public void run() {
listView.setSelection(position);
}
});
Make sure listView and position are member variables or declared as final.
The problem:
(1) add a touch listener to rows in a listview so that on swipe.
(2) swipe animation plays
(3) rows get deleted in the backend, and
(4) the animation plays without any flicker or jerkiness. By "flicker" I mean that the deleted row briefly shows after the animation finished.
I suspect that something funky was happening with the animation listener, so I ended up doing the following (done in the order given):
Do the animation and make it persist by setting setFillAfter and setFillenabled to true
Make the view invisible when the animation ends
Delete the row in the database
Reset the animation
Reload the listview
Make the view visible (but wait an additional 300 ms)
The result deletes the row without jerkiness or flicker BUT it now feels sluggish because of extra 300 ms wait. (I'm also not sure if this delay works across all devices.)
Update: I should point out that the 300 ms delay is what makes it work. That's weird because by that point the animation was reset and the listview has the latest data. There should be no reason why making the view visible makes the old row briefly show, right?
I also tried using a ViewPropertyAnimator (as per Using animation on a ViewPager and setFillAfter) but for some reason the onAnimationEnd listener was called at every step of the animation.
I also read that we should implement a custom view and override its onAnimationEnd listener. (However, I haven't tried that approach yet.)
Update: just tried to add an extra dummy animation at the end (as per Android Animation Flicker). However, that doesn't work
My test phone runs Ice Cream Sandwich. My app is targeting Gingerbread and after.
So what's the proper solution? Am I doing this the wrong way?
Here's the code:
#Override
public boolean onTouch(final View view, MotionEvent event)
{
//...
switch(action) {
//...
case MotionEvent.ACTION_MOVE:
// ...
if (//check for fling
{
view.clearAnimation();
//animation = standard translate animation
animation.setAnimationListener(new AnimationListener() {
//
#Override
public void onAnimationEnd(Animation animation)
{
view.setVisibility(View.INVISIBLE);
flingListener.onFling(cursorPosition, view, velocity);
}
//...
});
view.startAnimation(animation);
}
break;
//
}
The "fling listener":
#Override
public void onFling(int position, final View view, float velocity)
{
//delete row -- its actually a Loader
//the following code runs in the Loader's onLoadFinished
view.clearAnimation();
adapter.notifyDataSetChanged();
adapter.swapCursor(null);
//reload listview -- it's actually a Loader
//the following code runs in the Loader's onLoadFinished
adapter.swapCursor(cursor);
view.postDelayed(new Runnable() {
#Override
public void run()
{
view.setVisibility(View.VISIBLE);
}
}, 300);
}
Update: After comparing Chet Haase's code, we are doing similar things with some important differences: (1) he uses a onPreDraw listener with the ListView tree observer to do the actual deletion, (2) he removes the row not only from the array but also from the listview. After mimicking his code, it still didn't work. The problem is now the Loader---I use a Loader to delete rows asynchronously. The Loader seems to force an additional draw call to the ListView...before the row has been deleted in the backend. And that's (another) cause of the flicker. Still haven't figured out a workaround though.
Chet Haase (Google Engineer) put together a really good DevBytes on this topic I'd suggest watching/taking ideas from his source. If you use NineOldAndroids, I think this'll be backwards compatible to GB.
Check it out here:
http://graphics-geek.blogspot.com/2013/06/devbytes-animating-listview-deletion.html
As I pointed out in the comments, the problem with Chet's code is that its designed for synchronous data access. Once you start asynchronously deleting rows, his code fails.
I found the solution to the flicker problem by combining Chet's code with this answer: CursorAdapter backed ListView delete animation "flickers" on delete
The solution to correctly do a row deletion aynchronously is:
Create a onPreDraw listener for the ListView tree observer to prevent flicker. All code in this listener runs before the list view re-draws itself, preventing flicker.
Find a way to delete the row in the listview (but not yet in the database). There are two approaches (see CursorAdapter backed ListView delete animation "flickers" on delete):
Create a AbstractCursor wrapper that ignores the row to be deleted and swap it in for the real cursor. OR
Mark the row to be deleted as "stained" and act on it appropriately when redrawing the row.
Remove the row for real in the database (asynchronously).
Some pseudo-code using the AbstractCursor wrapper (it's technically called a "proxy"):
//Called when you swipe a row to delete
#Override
public void onFling(final int positionToRemove, final View view)
{
final ViewTreeObserver observer = listView.getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
{
public boolean onPreDraw()
{
observer.removeOnPreDrawListener(this);
//remove the row from the matrix cursor
CursorProxy newCursor = new CursorProxy(cursor,
positionToRemove);
swapCursor(newCursor);
//delete row in database
}
}
}
As mentioned here, Android's GridView.scrollTo() doesn't work. The method the solution mentioned, setSelectedPosition, doesn't seem to exist in GridView
smoothScrollToPosition does work, but I really don't want the animation.
For context, I have a CursorAdapter-backed GridView, and I want the view to "reset", i.e. scroll to the top, when I change the cursor.
I've been using setSelection(int position) for this, and it seems to be working just fine. To scroll to the top, just use 0 for position.
From the docs:
If in touch mode, the item will not be selected but it will still be positioned appropriately.
Edit:
Added code to post setSelection as a Runnable:
albumsView.post(new Runnable() {
#Override
public void run() {
albumsView.setSelection(0);
}
});
I have found that in order to reliably use gridView.setSelection(index), I have to first call gridView.setAdapter() (even if the adapter has already been set and has not changed). Like so:
gridView.setAdapter(gridAdapter);
gridView.setSelection(index);