SwipeableListView.java revision 866d319dd23ec8b7b7d5476c65f7f83469d55d2d
1f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira/*
2f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * Copyright (C) 2012 Google Inc.
3f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * Licensed to The Android Open Source Project.
4f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira *
5f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
6f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * you may not use this file except in compliance with the License.
7f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * You may obtain a copy of the License at
8f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira *
9f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
10f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira *
11f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * Unless required by applicable law or agreed to in writing, software
12f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
13f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * See the License for the specific language governing permissions and
15f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira * limitations under the License.
16f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira */
17f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
18f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereirapackage com.android.mail.ui;
19f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
20f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.content.Context;
21f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.util.AttributeSet;
22f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.view.MotionEvent;
23f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.view.View;
24489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huangimport android.widget.ListAdapter;
25f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport android.widget.ListView;
26f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
27f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.android.mail.R;
28f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.android.mail.browse.ConversationItemView;
29f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.android.mail.providers.Conversation;
30f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.android.mail.ui.SwipeHelper.Callback;
31489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huangimport com.android.mail.utils.LogUtils;
32489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang
33f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport com.google.common.collect.ImmutableList;
34f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
35866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereiraimport java.util.ArrayList;
36f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport java.util.Collection;
37f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
38f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereirapublic class SwipeableListView extends ListView implements Callback {
39f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    private SwipeHelper mSwipeHelper;
40f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    private SwipeCompleteListener mSwipeCompleteListener;
41866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    private boolean ENABLE_SWIPE = true;
42489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    private ListAdapter mDebugAdapter;
43489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    private int mDebugLastCount;
44489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang
45489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    // TODO: remove me and all enclosed blocks when b/6255909 is fixed
46489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    private static final boolean DEBUG_LOGGING_CONVERSATION_CURSOR = true;
47489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang
48489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    public static final String LOG_TAG = new LogUtils().getLogTag();
49f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
50866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    private ConversationSelectionSet mConvSelectionSet;
51866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
52f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public SwipeableListView(Context context) {
53f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        this(context, null);
54f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
55f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
56f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public SwipeableListView(Context context, AttributeSet attrs) {
57f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        this(context, attrs, -1);
58f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
59f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
60f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public SwipeableListView(Context context, AttributeSet attrs, int defStyle) {
61f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        super(context, attrs, defStyle);
62f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        float densityScale = getResources().getDisplayMetrics().density;
63866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        float scrollSlop = context.getResources().getInteger(R.integer.swipeScrollSlop);
64866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, densityScale,
65866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira                scrollSlop);
66f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
67f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
68f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public void setSwipeCompleteListener(SwipeCompleteListener listener) {
69f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        mSwipeCompleteListener = listener;
70f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
71f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
72866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public void setSelectionSet(ConversationSelectionSet set) {
73866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        mConvSelectionSet = set;
74866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
75866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
76866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    @Override
77866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public ConversationSelectionSet getSelectionSet() {
78866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        return mConvSelectionSet;
79866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
80866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
81f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
82f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public boolean onInterceptTouchEvent(MotionEvent ev) {
8319bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira        if (ENABLE_SWIPE) {
8419bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira            return mSwipeHelper.onInterceptTouchEvent(ev)
8519bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira                    || super.onInterceptTouchEvent(ev);
8619bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira        } else {
8719bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira            return super.onInterceptTouchEvent(ev);
8819bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira        }
89f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
90f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
91f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
92f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public boolean onTouchEvent(MotionEvent ev) {
9319bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira        if (ENABLE_SWIPE) {
9419bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira            return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
9519bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira        } else {
9619bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira            return super.onTouchEvent(ev);
9719bf5f5a4944f5bb4e62e1f32c47dce87f662cbdMindy Pereira        }
98f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
99f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
100f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
101489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    public void setAdapter(ListAdapter adapter) {
102489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        super.setAdapter(adapter);
103489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        if (DEBUG_LOGGING_CONVERSATION_CURSOR) {
104489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang            mDebugAdapter = adapter;
105489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        }
106489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    }
107489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang
108489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    @Override
109489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
110489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        if (DEBUG_LOGGING_CONVERSATION_CURSOR) {
111489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang            final int count = mDebugAdapter == null ? 0 : mDebugAdapter.getCount();
112489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang            if (count != mDebugLastCount) {
113489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang                LogUtils.i(LOG_TAG, "Conversation ListView about to change mItemCount to: %d",
114489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang                        count);
115489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang                mDebugLastCount = count;
116489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang            }
117489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        }
118489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
119489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    }
120489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang
121489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    @Override
122489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    protected void layoutChildren() {
123489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        if (DEBUG_LOGGING_CONVERSATION_CURSOR) {
124489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang            LogUtils.i(LOG_TAG, "Conversation ListView may compare last mItemCount to new val: %d",
125489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang                    mDebugAdapter == null ? 0 : mDebugAdapter.getCount());
126489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        }
127489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang        super.layoutChildren();
128489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    }
129489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang
130489dd22c64c718b6953b4bd6acef925e82c53c87Andy Huang    @Override
131f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public View getChildAtPosition(MotionEvent ev) {
132f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        // find the view under the pointer, accounting for GONE views
133f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        final int count = getChildCount();
134f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        int touchY = (int) ev.getY();
135f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        int childIdx = 0;
136f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        View slidingChild;
137f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        for (; childIdx < count; childIdx++) {
138f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira            slidingChild = getChildAt(childIdx);
139f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira            if (slidingChild.getVisibility() == GONE) {
140f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira                continue;
141f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira            }
142866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            if (touchY >= slidingChild.getTop() && touchY <= slidingChild.getBottom()) {
143866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira                return slidingChild;
144866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            }
145f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        }
146f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        return null;
147f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
148f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
149f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
150f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public View getChildContentView(View v) {
151f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        return v;
152f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
153f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
154f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
155f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public boolean canChildBeDismissed(View v) {
1566529023dc8c204bfd83a3805158342d1a5e21f43Mindy Pereira        return v instanceof ConversationItemView;
157f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
158f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
159f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
160f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public void onChildDismissed(View v) {
161866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        dismissChildren(ImmutableList.of(getConversation(v)));
162866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
163866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
164866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    @Override
165866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    public void onChildrenDismissed(Collection<ConversationItemView> views) {
166866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        final ArrayList<Conversation> conversations = new ArrayList<Conversation>();
167866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        for (ConversationItemView view : views) {
168866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            conversations.add(getConversation(view));
169f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        }
170866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        dismissChildren(conversations);
171866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
172866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
173866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    private Conversation getConversation(View view) {
174866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        Conversation c = ((ConversationItemView) view).getConversation();
175866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        c.position = getPositionForView(view);
176866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        return c;
177866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    }
178866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira
179866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira    private void dismissChildren(final Collection<Conversation> conversations) {
180866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        AnimatedAdapter adapter = ((AnimatedAdapter) getAdapter());
181866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        adapter.delete(conversations, new ActionCompleteListener() {
182866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            @Override
183866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            public void onActionComplete() {
184866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira                mSwipeCompleteListener.onSwipeComplete(conversations);
185866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira            }
186866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        });
187f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
188f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
189f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
190f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public void onBeginDrag(View v) {
191f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        // We do this so the underlying ScrollView knows that it won't get
192f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        // the chance to intercept events anymore
193f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        requestDisallowInterceptTouchEvent(true);
194866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        // If there are selected conversations, we are dismissing an entire
195866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        // associated set.
196866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        // Otherwise, the SwipeHelper will just get rid of the single item it
197866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        // received touch events for.
198866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        mSwipeHelper.setAssociatedViews(mConvSelectionSet != null ? mConvSelectionSet.views()
199866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira                : null);
200f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
201f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
202f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    @Override
203f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public void onDragCancelled(View v) {
204866d319dd23ec8b7b7d5476c65f7f83469d55d2dMindy Pereira        mSwipeHelper.setAssociatedViews(null);
205f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
206f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira
207f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    public interface SwipeCompleteListener {
208f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira        public void onSwipeComplete(Collection<Conversation> conversations);
209f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    }
210f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira}
211