Android new Inbox app style listview with swipe left and right-ThrowExceptions

Exception or error:

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;
    }
How to solve:

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.

###

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

###

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();
}

Leave a Reply

Your email address will not be published. Required fields are marked *