0

How can I detect from event and view that which item was swiped on, so I can delete the specific record from the arrayList and remove it from there followed by an update to the adapter?

ArrayList<User> users = ... // list of user records

adapter = new ListAdapter(getActivity(), users);

ListView listView = getView().findViewById(R.id.listView);
listView.setAdapter(adapter);

listView.setOnTouchListener((view, event) -> { // for deletion
        return false;
});

Once deleted, adapter to be refreshed.

adapter.notifyDataSetChanged();

Edit

private class ListAdapter extends ArrayAdapter<User> {

    public ListAdapter(Context context, ArrayList<User> users) {
        super(context, 0, users);
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup container) {
        if (convertView == null) {
            convertView = getLayoutInflater().inflate(android.R.layout.simple_list_item_1, container, false);
        }
        ((TextView) convertView.findViewById(android.R.id.text1)).setText(getItem(position).toString());
        return convertView;
    }
}

Edit 2

Following discussion, here's what my code looks like, looking forward to progress from this point onwards.

private class UserAdapter extends ArrayAdapter<User> {

    public UserAdapter(Context context, ArrayList<User> users) {
        super(context, 0, users);
    }

    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup container) {
        if (convertView == null) {
            convertView = getLayoutInflater().inflate(android.R.layout.simple_list_item_1, container, false);
        }
        ((TextView) convertView.findViewById(android.R.id.text1)).setText(getItem(position).toString());

        GestureDetector gestureDetector = new GestureDetector(this.getContext(), new GestureListener());

        convertView.setOnTouchListener(((view, event) -> {
            return gestureDetector.onTouchEvent(event);
        }));
        return convertView;
    }
}

private class GestureListener extends GestureDetector.SimpleOnGestureListener {

    private static final int SWIPE_DISTANCE_THRESHOLD = 100;
    private static final int SWIPE_VELOCITY_THRESHOLD = 100;

    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) {
        Log.d("abc", "onSingleTapUp: ");
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityy) {
        float distanceX = e2.getX() - e1.getX();
        float distanceY = e2.getY() - e1.getY();
        if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
            if (distanceX > 0)
                Log.d("abc", "onFling: swipe right");
            else
                Log.d("abc", "onFling: swipe left");
            return true;
        }
        return false;
    }
}

Edit 3

private class UserAdapter extends ArrayAdapter<User> {

    public UserAdapter(Context context, ArrayList<User> users) {
        super(context, 0, users);
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup container) {
        if (convertView == null) {
            convertView = getLayoutInflater().inflate(android.R.layout.simple_list_item_1, container, false);
        }
        ((TextView) convertView.findViewById(android.R.id.text1)).setText(getItem(position).toString());

        MyGestureDetector gestureDetector = new MyGestureDetector(this.getContext(), new GestureListener());
        convertView.setOnTouchListener(((view, event) -> gestureDetector.onTouchEvent(event)));

        return convertView;
    }
}

private class MyGestureDetector extends GestureDetector {

    public MyGestureDetector(Context context, OnGestureListener listener) {
        super(context, listener);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }
}
AppDeveloper
  • 1,816
  • 7
  • 24
  • 49
  • You'd ideally set up the listener in the adapter when you create the row view, not set on the entire listview – OneCricketeer Oct 27 '19 at 07:09
  • There's also libraries that help you implement this rather than doing it yourself – OneCricketeer Oct 27 '19 at 07:09
  • You are right, it should be on the adapter, I added the code on the adapter, can you please help me out from there. I'd like to approach this from simplicity point of view without any library to understand how it's done. Once I understand this part, then I'll advance to understand how they swipe to reveal the hidden delete buttons (I'd also appreciate a direction for that) – AppDeveloper Oct 27 '19 at 07:29
  • `convertView.setOnTouchListener` before you return it. And you already have access to the int position by that getView method to say which view is touched... From there, you need a GestureDetector https://stackoverflow.com/a/12938787/2308683 – OneCricketeer Oct 27 '19 at 07:36
  • Thanks, actually `listView.setOnItemClickListener` confused me, we are setting listener on the `listView` rather than `convertView`, is that ok? – AppDeveloper Oct 27 '19 at 15:31
  • also what's the strategy of UI, as user swipes it moves the top list item and reveals a red icon or red layer underneath it indicating the item will be deleted, wanted to understand that portion of UI. – AppDeveloper Oct 27 '19 at 15:37
  • I'm not sure. Like I said, libraries are made to remove that complexity from your own code https://stackoverflow.com/questions/20797099/swipe-listview-item-from-right-to-left-show-delete-button – OneCricketeer Oct 27 '19 at 15:50
  • I see, but actually it's my assignment to make a library that just does that, has to my own code. – AppDeveloper Oct 27 '19 at 15:52
  • I implemented GestureDetector on the listView, and onFling I receive events, but how do I know which item was swiped on from events or coordinates? – AppDeveloper Oct 27 '19 at 16:23
  • Again, I would implement the swipe in the Adapter, not the entire Listview. That way, you have the index of the item being touched. Either way, have you seen this? https://stackoverflow.com/questions/17384131/get-item-from-listview-only-with-ontouchlistener – OneCricketeer Oct 27 '19 at 16:27
  • I'm good with setting listener on the list item, can you see my edit please – AppDeveloper Oct 27 '19 at 16:35
  • updated `onFling` – AppDeveloper Oct 27 '19 at 16:43
  • If you subclass the GestureDetector or it's listener, it should be possible to pass in `int position` from the `getView` method. – OneCricketeer Oct 27 '19 at 16:48
  • I'm lost here now, if you can please help with some code, that'd help tremendously. Thanks again for all the help so far, much appreciated. – AppDeveloper Oct 27 '19 at 16:50
  • see edit3 please – AppDeveloper Oct 27 '19 at 16:53
  • Add a field `public GestureListener(int position) { this.position = position; }` and maybe also the View so that you can act on it – OneCricketeer Oct 27 '19 at 16:54
  • I'm all mixed up and confused and I'm new to android. Can you please put together an example for – AppDeveloper Oct 27 '19 at 17:00

1 Answers1

0

I'm not sure about "best practices" for handling or animating on gestures, but this would allow you to get at least the view and position being swiped

class GestureListener extends GestureDetector.SimpleOnGestureListener {
    private final int position;
    private View v;

    public GestureListener(int position, View v) {
        this.position = position;
        this.v = v;
    }

When you make the listener, pass in the data

new GestureListener(position, convertView)

Then in the onFling, you have access to the view and the adapter position so that you can at least log them, but access to the Arraylist is lost and doing view Animation in the listener I'm sure isn't the best idea, based on the source code of the libraries I linked in the comments

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • Thanks, and I see what you mean. by the way isn't it easier that I make my `Activity` or `Fragment` class implement `GestureDetector.OnGestureListener`. Overridden methods will have access to `ArrayList` and `Adapter`. I can set `this.currentPosition = position` on a field in `convertView.setOnTouchListener` and notify change from there after deletion in `onFling` – AppDeveloper Oct 27 '19 at 17:53
  • I don't think you should persist the touched position in either the Fragment or activity as a field, but you can define the interface anywhere its most accessible – OneCricketeer Oct 27 '19 at 17:55
  • ok, I'll take the advice but why we shouldn't persist the touched position in either the Fragement or the Activity? – AppDeveloper Oct 27 '19 at 17:58
  • 1
    Because you should only need it while the onTouch event is happening, not for the lifecycle of the application – OneCricketeer Oct 27 '19 at 18:29
  • ok makes sense, I came across this example that doesn't persist the position and uses pointToPosition to convert the coordinates into position, please have a look http://findnerd.com/list/view/Implement-Gesture-listener-on-listview-item/965/ – AppDeveloper Oct 27 '19 at 18:33
  • either way I've got it working, thanks I appreciate your help. My next task is to discover how to reveal a layer beneath the list item (without a library) -> https://i.stack.imgur.com/YJwAK.png. I'll post another question – AppDeveloper Oct 27 '19 at 19:09
  • 1
    Cool. It would at least require a custom layout view rather than your simple list item one with a TextView – OneCricketeer Oct 27 '19 at 19:16
  • yes, how do you move the view with the gesture, I'm looking at `convertView.setX` or `convertView.translateX` to nudge it – AppDeveloper Oct 27 '19 at 19:21