Android new Inbox app style listview with swipe left and right
Asked Answered
A

3

20

m trying to build android new inbox style listview with swipe left and right as shown in this image , i have tried 47deg swipelistview but its not that stable , is there any other library available?!

right swipe left swipe

Tried so far with 47 deg

 public class MainActivity extends Activity {

        Listview pullToRefreshListView;
        SwipeListView swipelistview;
        ItemAdapter adapter;
        List<ItemRow> itemData;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            pullToRefreshListView = (ListView) findViewById(R.id.example_swipe_lv_list);
            swipelistview = pullToRefreshListView.getRefreshableView();
            itemData = new ArrayList<ItemRow>();
            adapter = new ItemAdapter(this, R.layout.custom_row, itemData);

            swipelistview.setSwipeListViewListener(new BaseSwipeListViewListener() {
                @Override
                public void onOpened(int position, boolean toRight) {
                    if (toRight) {
                        adapter.remove(position);
                        Toast.makeText(MainActivity.this, "Open to dismiss",
                                Toast.LENGTH_SHORT).show();
                    } // swipelistview.dismiss(position);
                    else {
                        Toast.makeText(MainActivity.this, "Open to edit",
                                Toast.LENGTH_SHORT).show();
                    }
                }

                @Override
                public void onClosed(int position, boolean fromRight) {
                }

                @Override
                public void onListChanged() {
                }

                @Override
                public void onMove(int position, float x) {
                }

                @Override
                public void onStartOpen(int position, int action, boolean right) {
                    if (right) {
                        // adapter.onRight();
                        swipelistview.getChildAt(position).findViewById(R.id.back)
                                .setBackgroundColor(Color.GREEN);

                        swipelistview.getChildAt(position)
                                .findViewById(R.id.imageViewLeft)
                                .setVisibility(View.VISIBLE);
                        swipelistview.getChildAt(position)
                                .findViewById(R.id.imageViewRight)
                                .setVisibility(View.GONE);
                    } else {
                        // adapter.onLeft();
                        swipelistview.getChildAt(position).findViewById(R.id.back)
                                .setBackgroundColor(Color.RED);
                        swipelistview.getChildAt(position)
                                .findViewById(R.id.imageViewLeft)
                                .setVisibility(View.GONE);
                        swipelistview.getChildAt(position)
                                .findViewById(R.id.imageViewRight)
                                .setVisibility(View.VISIBLE);
                    }
                }

                @Override
                public void onStartClose(int position, boolean right) {
                    Log.d("swipe", String.format("onStartClose %d", position));
                }

                @Override
                public void onClickFrontView(int position) {
                    Log.d("swipe", String.format("onClickFrontView %d", position));

                    // swipelistview.openAnimate(position); //when you touch front
                    // view it will open

                }

                @Override
                public void onClickBackView(int position) {
                    Log.d("swipe", String.format("onClickBackView %d", position));

                    // swipelistview.closeAnimate(position);//when you touch back
                    // view it will close
                }

                @Override
                public void onDismiss(int[] reverseSortedPositions) {

                }

            });

            // These are the swipe listview settings. you can change these
            // setting as your requirement
            swipelistview.setSwipeMode(SwipeListView.SWIPE_MODE_BOTH); // there are
                                                                        // five
                                                                        // swiping
                                                                        // modes
            swipelistview.setSwipeActionRight(SwipeListView.SWIPE_ACTION_REVEAL); // there
                                                                                    // are
                                                                                    // four
                                                                                    // swipe
                                                                                    // actions
            swipelistview.setSwipeActionLeft(SwipeListView.SWIPE_ACTION_REVEAL);
            swipelistview.setOffsetRight(convertDpToPixel(0f)); // left side
                                                                // offset
            swipelistview.setOffsetLeft(convertDpToPixel(0f)); // right side
                                                                // offset
            swipelistview.setAnimationTime(60); // Animation time
            swipelistview.setSwipeOpenOnLongPress(false); // enable or disable
                                                            // SwipeOpenOnLongPress
            swipelistview.setSwipeCloseAllItemsWhenMoveList(true);
            swipelistview.setAdapter(adapter);

            for (int i = 0; i < 10; i++) {
                itemData.add(new ItemRow("Swipe Item" + i, getResources()
                        .getDrawable(R.drawable.ic_launcher)));

            }

            adapter.notifyDataSetChanged();
        }
 public int convertDpToPixel(float dp) {
    DisplayMetrics metrics = getResources().getDisplayMetrics();
    float px = dp * (metrics.densityDpi / 160f);
    return (int) px;
}
    }

Adapter class

public class ItemAdapter extends ArrayAdapter<ItemRow> {

    List<ItemRow> data;
    Context context;
    int layoutResID;

    public ItemAdapter(Context context, int layoutResourceId, List<ItemRow> data) {
        super(context, layoutResourceId, data);

        this.data = data;
        this.context = context;
        this.layoutResID = layoutResourceId;

        // TODO Auto-generated constructor stub
    }

    NewsHolder holder = null;
    View row = null;

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        row = convertView;
        holder = null;

        if (row == null) {
            LayoutInflater inflater = ((Activity) context).getLayoutInflater();
            row = inflater.inflate(layoutResID, parent, false);

            holder = new NewsHolder();

            holder.itemName = (TextView) row
                    .findViewById(R.id.example_itemname);
            holder.icon = (ImageView) row.findViewById(R.id.example_image);
            holder.imageViewRight = (ImageView) row
                    .findViewById(R.id.imageViewRight);
            holder.imageViewLeft = (ImageView) row
                    .findViewById(R.id.imageViewLeft);

            row.setTag(holder);
        } else {
            holder = (NewsHolder) row.getTag();
        }

        ItemRow itemdata = data.get(position);
        holder.itemName.setText(itemdata.getItemName());
        holder.icon.setImageDrawable(itemdata.getIcon());

        return row;

    }
    public void remove(int pos){
        data.remove(pos);

    }

    public void onLeft() {

        holder.imageViewLeft.setVisibility(View.VISIBLE);
        holder.imageViewRight.setVisibility(View.GONE);
    }

    public void onRight() {
        holder.imageViewRight.setVisibility(View.VISIBLE);
        holder.imageViewLeft.setVisibility(View.GONE);
    }

    static class NewsHolder {

        TextView itemName;
        ImageView icon;
        ImageView imageViewLeft, imageViewRight;

        RelativeLayout mRelativeLayout;
    }
Astrogation answered 6/11, 2014 at 4:19 Comment(12)
only this listview is available for this particular typr i.e someone swipes and buttons appear at the back. and i dont know y it isnt stable for u! i mean i am using it and it is working fineArnoldarnoldo
@HirakChhatbar can you please read the issues on that stack , dont close the question if dont have answer or if u really found it stable you need to your code as there sample its self not stableAstrogation
first, i havent closed the question. second, if it is stable for me (i am getting what i want), then y shld i revisit my code :pArnoldarnoldo
why this question is been down vote ??Astrogation
fyi: one of the reasons, probably evident, for close votes Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve itCultivation
in other words, what @user2450263 is telling you, could you please explain why the 47deg SwipeListview is not stable for you?Minelayer
@MicheleLaFerla updated wot i tried and not stableAstrogation
@user2450263 updated wot i triedAstrogation
#17858275 Check the example given by raghunandan,hope this will help you.Carola
what about his library baoyongzhang.github.io/SwipeMenuListView ?Audiophile
@rom4ek it doesnt have a directional swipe :(Astrogation
@Astrogation - Were you able to find a working solution?Misguided
E
12

Instead of using a custom ListView you can simply support "swipe" gesture on list items onTouch, like the following:

private static final int DEFAULT_THRESHOLD = 128;

row.setOnTouchListener(new View.OnTouchListener() {

    int initialX = 0;
    final float slop = ViewConfiguration.get(context).getScaledTouchSlop();

    public boolean onTouch(final View view, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            initialX = (int) event.getX();
            view.setPadding(0, 0, 0, 0);
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            int currentX = (int) event.getX();
            int offset = currentX - initialX;
            if (Math.abs(offset) > slop) {
                view.setPadding(offset, 0, 0, 0);

                if (offset > DEFAULT_THRESHOLD) {
                    // TODO :: Do Right to Left action! And do nothing on action_up.
                } else if (offset < -DEFAULT_THRESHOLD) {
                    // TODO :: Do Left to Right action! And do nothing on action_up.
                }
            }
        } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
            // Animate back if no action was performed.
            ValueAnimator animator = ValueAnimator.ofInt(view.getPaddingLeft(), 0);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    view.setPadding((Integer) valueAnimator.getAnimatedValue(), 0, 0, 0);
                }
            });
            animator.setDuration(150);
            animator.start();
        }
};

I also use reverse animation if no action was performed.

This solution is lightweight so you should not experience any lags.

Expulsive answered 10/11, 2014 at 12:38 Comment(2)
view.setPadding(offset, 0, -offset, 0); -offset for right side paddingAlvira
can we hold it? until we swipe again?Cammycamomile
M
12

Check out: SwipeActionAdapter

It's a great library that does exactly what you're asking for. It allows Swipe in both directions with an underlying Layout or Color. It's easy to implement and looks nice!

Left Swipe Right Swipe

Misguided answered 31/12, 2014 at 0:37 Comment(1)
can you share this project's source code please. I have looked up example that is mentioned in libaray but that isn't helping much.Embrangle
S
2

Updated Answer

As I mentioned previously, I took the same approach and it seems to work as expected. I have added 3 layers to a RelativeLayout. Top layer is what you want to show. Second layer is a plain background with delete icon at the left. Third layer is another plain background with share icon at the right. I implemented a swipe detector class which extends View.OnTouchListener.

public class SwipeDetector implements View.OnTouchListener {

    private static final int MIN_DISTANCE = 300;
    private static final int MIN_LOCK_DISTANCE = 30; // disallow motion intercept
    private boolean motionInterceptDisallowed = false;
    private float downX, upX;
    private ObjectHolder holder;
    private int position;

    public SwipeDetector(ObjectHolder h, int pos) {
        holder = h;
        position = pos;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            return true; // allow other events like Click to be processed
        }

        case MotionEvent.ACTION_MOVE: {
            upX = event.getX();
            float deltaX = downX - upX;

            if (Math.abs(deltaX) > MIN_LOCK_DISTANCE && listView != null && !motionInterceptDisallowed) {
                listView.requestDisallowInterceptTouchEvent(true);
                motionInterceptDisallowed = true;
            }

            if (deltaX > 0) {
                holder.deleteView.setVisibility(View.GONE);
            } else {
                // if first swiped left and then swiped right
                holder.deleteView.setVisibility(View.VISIBLE);
            }

            swipe(-(int) deltaX);
            return true;
        }

        case MotionEvent.ACTION_UP:
            upX = event.getX();
            float deltaX = upX - downX;
            if (Math.abs(deltaX) > MIN_DISTANCE) {
                // left or right
                swipeRemove();
            } else {
                swipe(0);
            }

            if (listView != null) {
                listView.requestDisallowInterceptTouchEvent(false);
                motionInterceptDisallowed = false;
            }

            holder.deleteView.setVisibility(View.VISIBLE);
            return true;

        case MotionEvent.ACTION_CANCEL:
            holder.deleteView.setVisibility(View.VISIBLE);
            return false;
        }

    return true;
    }

    private void swipe(int distance) {
        View animationView = holder.mainView;
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) animationView.getLayoutParams();
        params.rightMargin = -distance;
        params.leftMargin = distance;
        animationView.setLayoutParams(params);
    }

    private void swipeRemove() {
        remove(getItem(position));
        notifyDataSetChanged();
    }
}


public static class ObjectHolder {
    public LinearLayout mainView;
    public RelativeLayout deleteView;
    public RelativeLayout shareView;

    /* other views here */
}

I have also added requestDisallowInterceptTouchEvent so that ListView (which is parent) doesn't intercept the touch event when there's some amount of vertical scrolling involved.

I have written a blogpost about it which you can find it here. I have also added a Youtube video for demo.


Old Answer

I implemented one of these myself, but it's a bit different. I use just touch instead of swiping. Touch to open, touch to close. Here's youtube demo.

I created custom ArrayAdapter. To set the layout, I created a custom layout like this.

<RelativeLayout>
     <RelativeLayout>
          <Stuff that you want at the back of your list/>
     </RelativeLayout>
     <RelativeLayout>
          <Stuff that you want at the front of your list/>
     </RelativeLayout>
</RelativeLayout>

Using RelativeLayout, I am putting the top view over the bottom view. Both have same sizes. You can use different layouts for inner layouts.

In Custom ArrayAdapter,

@Override
public view getView(int position, View convertView, ViewGroup parent) {
    // get holder and entry
    // set each element based on entry preferences



    holder.topView.setOnClickListener(new View.OnClickListener() {

          @Override
          public void onClick(View v) {
              if (entry.isSwiped()) {
                  swipeWithAnimationValue(holder.topView, 1);
                  entry.setSwiped(false);
              } else {
                  closeOtherSwipes(entry);  // if you want to keep only one entry open at a time
                  swipeWithAnimationValue(holder.topView, 0);
                  entry.setSwiped(true);
              }
         }
   });
}

Normal Animation would not work as it just shifts the view, but it's still there so if you try to click, the click still occurs on the top view. Hence I have used valueAnimator and actually shifted those lists.

public void swipeWithAnimationValue(final View view, final int direction) {
    final int width = view.getWidth();
    Log.i(TAG, "view width = " + String.valueOf(width));
    ValueAnimator animationSwipe;
    int duration = 300;
    if (direction == 0) {
         animationSwipe = ValueAnimator.ofInt(0, view.getWidth() - 200);
    } else {
         animationSwipe = ValueAnimator.ofInt(view.getWidth() - 200, 0);
    }

    animationSwipe.setDuration(duration);
    AnimatorUpdateListener maringUpdater = new AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {

             RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
             params.rightMargin = -(Integer)animation.getAnimatedValue();
             params.leftMargin = (Integer)animation.getAnimatedValue();
             view.setLayoutParams(params);
        }
    };

    animationSwipe.addUpdateListener(maringUpdater);
    animationSwipe.setRepeatCount(0);
    animationSwipe.start();
}
Sacrament answered 17/11, 2014 at 7:14 Comment(5)
dude there is no swipe happening i need swipe not not clickAstrogation
One thing you could do is, use onTouchListener. Get the coordinates of TOUCH_DOWN. mark it as your origin. And now, based on where the user moves the finger, change margin of the layouts to make it shift.Sacrament
thx for telling me how to do it :) but i dont want to take this approach coz keeping such coordinate this is more poor than current 47 deg lib i need a better solution not a work aroundAstrogation
Be cautious with this answer. It has the right idea, but setting LayoutParams is very intensive causing CPU spikes and lag (see answer from lead Google UI engineer Chet Haase). Instead, use a setX property of the View. This also allows for a lot prettier animations using ViewPropertyAnimator.Asthenic
Yeah, that is correct. But I didn't know about it back thenSacrament

© 2022 - 2024 — McMap. All rights reserved.