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