ConversationContainer.java revision c69cee309fb61e071ecae11e72bf2b8621eeb872
1f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/* 2f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Copyright (C) 2012 Google Inc. 3f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Licensed to The Android Open Source Project. 4f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 5f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Licensed under the Apache License, Version 2.0 (the "License"); 6f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * you may not use this file except in compliance with the License. 7f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * You may obtain a copy of the License at 8f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 9f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * http://www.apache.org/licenses/LICENSE-2.0 10f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 11f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Unless required by applicable law or agreed to in writing, software 12f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * distributed under the License is distributed on an "AS IS" BASIS, 13f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * See the License for the specific language governing permissions and 15f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * limitations under the License. 16f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */ 17f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 185ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangpackage com.android.mail.browse; 19f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 20f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.content.Context; 2146dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport android.database.DataSetObserver; 2259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport android.graphics.Canvas; 23f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.util.AttributeSet; 2465fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huangimport android.util.SparseArray; 25bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.MotionEvent; 26f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.View; 27bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.ViewConfiguration; 28f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.ViewGroup; 29f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebView; 30f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.widget.Adapter; 31b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport android.widget.ListView; 32bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.widget.ScrollView; 33f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 34b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport com.android.mail.R; 355ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ScrollNotifier.ScrollListener; 363bcf180f8104bc27319086a9a6ece5a3c2917c37mindypimport com.android.mail.providers.Conversation; 37632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huangimport com.android.mail.ui.ConversationViewFragment; 387bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.utils.DequeMap; 3931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huangimport com.android.mail.utils.InputSmoother; 40f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.utils.LogUtils; 417517e3b61b898a57f19be0671f70d58a82224643Andy Huangimport com.android.mail.utils.Utils; 4247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.google.common.collect.Lists; 4347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 4447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport java.util.List; 45b5078b287b1cec38817e342ff054ea901d199329Andy Huang 46f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/** 47b5078b287b1cec38817e342ff054ea901d199329Andy Huang * A specialized ViewGroup container for conversation view. It is designed to contain a single 48b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link WebView} and a number of overlay views that draw on top of the WebView. In the Mail app, 49b5078b287b1cec38817e342ff054ea901d199329Andy Huang * the WebView contains all HTML message bodies in a conversation, and the overlay views are the 50b5078b287b1cec38817e342ff054ea901d199329Andy Huang * subject view, message headers, and attachment views. The WebView does all scroll handling, and 51b5078b287b1cec38817e342ff054ea901d199329Andy Huang * this container manages scrolling of the overlay views so that they move in tandem. 52b5078b287b1cec38817e342ff054ea901d199329Andy Huang * 53b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>INPUT HANDLING</h5> 54b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Placing the WebView in the same container as the overlay views means we don't have to do a lot of 55b5078b287b1cec38817e342ff054ea901d199329Andy Huang * manual manipulation of touch events. We do have a 56b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link #forwardFakeMotionEvent(MotionEvent, int)} method that deals with one WebView 57b5078b287b1cec38817e342ff054ea901d199329Andy Huang * idiosyncrasy: it doesn't react well when touch MOVE events stream in without a preceding DOWN. 58b5078b287b1cec38817e342ff054ea901d199329Andy Huang * 59b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>VIEW RECYCLING</h5> 60b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Normally, it would make sense to put all overlay views into a {@link ListView}. But this view 61b5078b287b1cec38817e342ff054ea901d199329Andy Huang * sandwich has unique characteristics: the list items are scrolled based on an external controller, 62b5078b287b1cec38817e342ff054ea901d199329Andy Huang * and we happen to know all of the overlay positions up front. So it didn't make sense to shoehorn 63b5078b287b1cec38817e342ff054ea901d199329Andy Huang * a ListView in and instead, we rolled our own view recycler by borrowing key details from 64b5078b287b1cec38817e342ff054ea901d199329Andy Huang * ListView and AbsListView. 65f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 66f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */ 67f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpublic class ConversationContainer extends ViewGroup implements ScrollListener { 68632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang private static final String TAG = ConversationViewFragment.LAYOUT_TAG; 69bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 7047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final int[] BOTTOM_LAYER_VIEW_IDS = { 7147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang R.id.webview 7247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang }; 7347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 7447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final int[] TOP_LAYER_VIEW_IDS = { 7547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang R.id.conversation_topmost_overlay 7647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang }; 7747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 7831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang /** 7931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang * Maximum scroll speed (in dp/sec) at which the snap header animation will draw. 8031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang * Anything faster than that, and drawing it creates visual artifacting (wagon-wheel effect). 8131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang */ 8231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private static final float SNAP_HEADER_MAX_SCROLL_SPEED = 600f; 8331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private ConversationViewAdapter mOverlayAdapter; 852ffaeab36408ccfbd6a375d5d31dc2a15d31a004Andy Huang private int[] mOverlayBottoms; 86bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private ConversationWebView mWebView; 8759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private MessageHeaderView mSnapHeader; 8859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private View mTopMostOverlay; 8959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 9059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang /** 9159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * This is a hack. 9259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 9359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * <p>Without this hack enabled, very fast scrolling can sometimes cause the top-most layers 9459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * to skip being drawn for a frame or two. It happens specifically when overlay views are 9559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * attached or added, and WebView happens to draw (on its own) immediately afterwards. 9659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 9759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * <p>The workaround is to force an additional draw of the top-most overlay. Since the problem 9859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * only occurs when scrolling overlays are added, restrict the additional draw to only occur 9959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * if scrolling overlays were added since the last draw. 10059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang */ 10159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private boolean mAttachedOverlaySinceLastDraw; 102f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 10347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private final List<View> mNonScrollingChildren = Lists.newArrayList(); 10447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 105bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 10623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * Current document zoom scale per {@link WebView#getScale()}. This is the ratio of actual 10723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * screen pixels to logical WebView HTML pixels. We use it to convert from one to the other. 108bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 109bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private float mScale; 110120ea66184c31bdeb729274054ec913afe3872c1Andy Huang /** 111120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * Set to true upon receiving the first touch event. Used to help reject invalid WebView scale 112120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * values. 113120ea66184c31bdeb729274054ec913afe3872c1Andy Huang */ 114120ea66184c31bdeb729274054ec913afe3872c1Andy Huang private boolean mTouchInitialized; 115f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 116bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 117bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * System touch-slop distance per {@link ViewConfiguration#getScaledTouchSlop()}. 118bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 119bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private final int mTouchSlop; 120bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 121bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Current scroll position, as dictated by the background {@link WebView}. 122bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 123f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private int mOffsetY; 124bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 125bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Original pointer Y for slop calculation. 126bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 127bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private float mLastMotionY; 128bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 129bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Original pointer ID for slop calculation. 130bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 131bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private int mActivePointerId; 132bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 133bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Track pointer up/down state to know whether to send a make-up DOWN event to WebView. 134bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * WebView internal logic requires that a stream of {@link MotionEvent#ACTION_MOVE} events be 135bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * preceded by a {@link MotionEvent#ACTION_DOWN} event. 136bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 137bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private boolean mTouchIsDown = false; 138bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 139bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Remember if touch interception was triggered on a {@link MotionEvent#ACTION_POINTER_DOWN}, 140bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * so we can send a make-up event in {@link #onTouchEvent(MotionEvent)}. 141bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 142bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private boolean mMissedPointerDown; 143f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 145c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * A recycler that holds removed scrap views, organized by integer item view type. All views 146c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * in this data structure should be removed from their view parent prior to insertion. 1477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 1487bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private final DequeMap<Integer, View> mScrapViews = new DequeMap<Integer, View>(); 149b5078b287b1cec38817e342ff054ea901d199329Andy Huang 150b5078b287b1cec38817e342ff054ea901d199329Andy Huang /** 15165fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * The current set of overlay views in the view hierarchy. Looking through this map is faster 15265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * than traversing the view hierarchy. 153b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <p> 154b5078b287b1cec38817e342ff054ea901d199329Andy Huang * WebView sometimes notifies of scroll changes during a draw (or display list generation), when 155b5078b287b1cec38817e342ff054ea901d199329Andy Huang * it's not safe to detach view children because ViewGroup is in the middle of iterating over 15665fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * its child array. So we remove any child from this list immediately and queue up a task to 15765fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * detach it later. Since nobody other than the detach task references that view in the 15865fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * meantime, we don't need any further checks or synchronization. 15946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * <p> 16046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * We keep {@link OverlayView} wrappers instead of bare views so that when it's time to dispose 16146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * of all views (on data set or adapter change), we can at least recycle them into the typed 16246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * scrap piles for later reuse. 163b5078b287b1cec38817e342ff054ea901d199329Andy Huang */ 16446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private final SparseArray<OverlayView> mOverlayViews; 165b5078b287b1cec38817e342ff054ea901d199329Andy Huang 166b5078b287b1cec38817e342ff054ea901d199329Andy Huang private int mWidthMeasureSpec; 167b5078b287b1cec38817e342ff054ea901d199329Andy Huang 168c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private boolean mDisableLayoutTracing; 169c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 17031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private final InputSmoother mVelocityTracker; 17131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 17246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private final DataSetObserver mAdapterObserver = new AdapterObserver(); 17346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 174cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang /** 17559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * The adapter index of the lowest overlay item that is above the top of the screen and reports 17659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through 17759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * {@link #positionOverlays(int, int)}. 17859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 17959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang */ 18059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private int mSnapIndex; 18159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 18259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang /** 183cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * Child views of this container should implement this interface to be notified when they are 184cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * being detached. 185cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * 186cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang */ 187cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang public interface DetachListener { 188cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang /** 189cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * Called on a child view when it is removed from its parent as part of 190cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * {@link ConversationContainer} view recycling. 191cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang */ 192cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang void onDetachedFromParent(); 193cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 194cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang 19546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private static class OverlayView { 19646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public View view; 19746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int itemType; 19846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 19946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public OverlayView(View view, int itemType) { 20046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang this.view = view; 20146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang this.itemType = itemType; 20246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 20346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 20446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 205f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public ConversationContainer(Context c) { 206f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang this(c, null); 207f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 208f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 209f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public ConversationContainer(Context c, AttributeSet attrs) { 210f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang super(c, attrs); 211bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 21246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews = new SparseArray<OverlayView>(); 213b5078b287b1cec38817e342ff054ea901d199329Andy Huang 21431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mVelocityTracker = new InputSmoother(c); 21531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 216bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchSlop = ViewConfiguration.get(c).getScaledTouchSlop(); 217bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 218bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // Disabling event splitting fixes pinch-zoom when the first pointer goes down on the 219bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // WebView and the second pointer goes down on an overlay view. 220bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // Intercepting ACTION_POINTER_DOWN events allows pinch-zoom to work when the first pointer 221bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // goes down on an overlay view. 222bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang setMotionEventSplittingEnabled(false); 223bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 224bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 225bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 226bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang protected void onFinishInflate() { 227bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang super.onFinishInflate(); 228bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 2295ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang mWebView = (ConversationWebView) findViewById(R.id.webview); 230bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mWebView.addScrollListener(this); 23147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 23259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mTopMostOverlay = findViewById(R.id.conversation_topmost_overlay); 23359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 23459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapHeader = (MessageHeaderView) findViewById(R.id.snap_header); 23559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapHeader.setSnappy(true); 23659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 23747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (int id : BOTTOM_LAYER_VIEW_IDS) { 23847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNonScrollingChildren.add(findViewById(id)); 23947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 24047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (int id : TOP_LAYER_VIEW_IDS) { 24147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNonScrollingChildren.add(findViewById(id)); 24247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 243f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 244f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 24559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang public MessageHeaderView getSnapHeader() { 24659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang return mSnapHeader; 24759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 24859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 2497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void setOverlayAdapter(ConversationViewAdapter a) { 25046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (mOverlayAdapter != null) { 25146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver); 25246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang clearOverlays(); 25346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 254f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mOverlayAdapter = a; 25546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (mOverlayAdapter != null) { 25646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayAdapter.registerDataSetObserver(mAdapterObserver); 25746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 25851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 25951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 26051067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang public Adapter getOverlayAdapter() { 26151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang return mOverlayAdapter; 262f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 263f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 26446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void clearOverlays() { 26546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang for (int i = 0, len = mOverlayViews.size(); i < len; i++) { 26646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang detachOverlay(mOverlayViews.valueAt(i)); 26746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 26846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews.clear(); 26946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 27046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 27146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void onDataSetChanged() { 27246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang clearOverlays(); 27328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang positionOverlays(0, mOffsetY); 27446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 27546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 276bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private void forwardFakeMotionEvent(MotionEvent original, int newAction) { 277bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang MotionEvent newEvent = MotionEvent.obtain(original); 278bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.setAction(newAction); 279bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mWebView.onTouchEvent(newEvent); 280bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang LogUtils.v(TAG, "in Container.OnTouch. fake: action=%d x/y=%f/%f pointers=%d", 281bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.getActionMasked(), newEvent.getX(), newEvent.getY(), 282bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.getPointerCount()); 283bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 284bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 285bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 286bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Touch slop code was copied from {@link ScrollView#onInterceptTouchEvent(MotionEvent)}. 287bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 288bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 289bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang public boolean onInterceptTouchEvent(MotionEvent ev) { 290120ea66184c31bdeb729274054ec913afe3872c1Andy Huang 291120ea66184c31bdeb729274054ec913afe3872c1Andy Huang if (!mTouchInitialized) { 292120ea66184c31bdeb729274054ec913afe3872c1Andy Huang mTouchInitialized = true; 293120ea66184c31bdeb729274054ec913afe3872c1Andy Huang } 294120ea66184c31bdeb729274054ec913afe3872c1Andy Huang 295632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang // no interception when WebView handles the first DOWN 296632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (mWebView.isHandlingTouch()) { 297632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang return false; 298632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 299632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 300bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang boolean intercept = false; 301bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang switch (ev.getActionMasked()) { 302bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_POINTER_DOWN: 303632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "Container is intercepting non-primary touch!"); 304bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang intercept = true; 305bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mMissedPointerDown = true; 306632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang requestDisallowInterceptTouchEvent(true); 307bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 308bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 309bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_DOWN: 310bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mLastMotionY = ev.getY(); 311bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mActivePointerId = ev.getPointerId(0); 312bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 313bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 314bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_MOVE: 315bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int pointerIndex = ev.findPointerIndex(mActivePointerId); 316bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final float y = ev.getY(pointerIndex); 317bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int yDiff = (int) Math.abs(y - mLastMotionY); 318bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang if (yDiff > mTouchSlop) { 319bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mLastMotionY = y; 320bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang intercept = true; 321bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 322bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 323bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 324bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 325632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// LogUtils.v(TAG, "in Container.InterceptTouch. action=%d x/y=%f/%f pointers=%d result=%s", 326632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount(), intercept); 327bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang return intercept; 328bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 329bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 330bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 331bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang public boolean onTouchEvent(MotionEvent ev) { 332bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int action = ev.getActionMasked(); 333bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 334632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 335bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchIsDown = false; 336bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } else if (!mTouchIsDown && 337bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_DOWN)) { 338bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 339bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang forwardFakeMotionEvent(ev, MotionEvent.ACTION_DOWN); 340bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang if (mMissedPointerDown) { 341bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang forwardFakeMotionEvent(ev, MotionEvent.ACTION_POINTER_DOWN); 342bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mMissedPointerDown = false; 343bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 344bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 345bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchIsDown = true; 346bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 347bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 348bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final boolean webViewResult = mWebView.onTouchEvent(ev); 349bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 350632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// LogUtils.v(TAG, "in Container.OnTouch. action=%d x/y=%f/%f pointers=%d", 351632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount()); 352bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang return webViewResult; 353f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 354f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 355f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 356b5078b287b1cec38817e342ff054ea901d199329Andy Huang public void onNotifierScroll(final int x, final int y) { 35731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mVelocityTracker.onInput(y); 358c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mDisableLayoutTracing = true; 3597bdc3750454efe59617b7df945eadd7e59bee954Andy Huang positionOverlays(x, y); 360c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mDisableLayoutTracing = false; 361b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 362b5078b287b1cec38817e342ff054ea901d199329Andy Huang 3637bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private void positionOverlays(int x, int y) { 364f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mOffsetY = y; 36507f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang 36607f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang /* 367120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * The scale value that WebView reports is inaccurate when measured during WebView 368120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * initialization. This bug is present in ICS, so to work around it, we ignore all 36923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * reported values and use a calculated expected value from ConversationWebView instead. 37023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * Only when the user actually begins to touch the view (to, say, begin a zoom) do we begin 37123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * to pay attention to WebView-reported scale values. 37207f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang */ 373120ea66184c31bdeb729274054ec913afe3872c1Andy Huang if (mTouchInitialized) { 37407f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang mScale = mWebView.getScale(); 37523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang } else if (mScale == 0) { 37623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mScale = mWebView.getInitialScale(); 37707f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang } 378c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(), 379c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mScale); 380f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 381e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang if (mOverlayBottoms == null || mOverlayAdapter == null) { 38251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang return; 38351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 38451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 385b5078b287b1cec38817e342ff054ea901d199329Andy Huang // recycle scrolled-off views and add newly visible views 386b5078b287b1cec38817e342ff054ea901d199329Andy Huang 3877bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // we want consecutive spacers/overlays to stack towards the bottom 3887bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // so iterate from the bottom of the conversation up 3897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // starting with the last spacer bottom and the last adapter item, position adapter views 3907bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // in a single stack until you encounter a non-contiguous expanded message header, 3917bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // then decrement to the next spacer. 392b5078b287b1cec38817e342ff054ea901d199329Andy Huang 393c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayBottoms.length, 394c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mOverlayAdapter.getCount()); 395c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 39659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = -1; 39759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 3987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang int adapterIndex = mOverlayAdapter.getCount() - 1; 399c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang int spacerIndex = mOverlayBottoms.length - 1; 400c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang while (spacerIndex >= 0 && adapterIndex >= 0) { 401c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 4027bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int spacerBottomY = getOverlayBottom(spacerIndex); 4037bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4047bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // always place at least one overlay per spacer 40546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang ConversationOverlayItem adapterItem = mOverlayAdapter.getItem(adapterIndex); 4067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4077bdc3750454efe59617b7df945eadd7e59bee954Andy Huang int overlayBottomY = spacerBottomY; 4087bdc3750454efe59617b7df945eadd7e59bee954Andy Huang int overlayTopY = overlayBottomY - adapterItem.getHeight(); 4097bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 410c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex, 411c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang overlayTopY, overlayBottomY, adapterItem); 4127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang positionOverlay(adapterIndex, overlayTopY, overlayBottomY); 4137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // and keep stacking overlays as long as they are contiguous 4157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang while (--adapterIndex >= 0) { 4167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang adapterItem = mOverlayAdapter.getItem(adapterIndex); 4177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (!adapterItem.isContiguous()) { 4187bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // advance to the next spacer, but stay on this adapter item 4197bdc3750454efe59617b7df945eadd7e59bee954Andy Huang break; 420b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 4217bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4227bdc3750454efe59617b7df945eadd7e59bee954Andy Huang overlayBottomY = overlayTopY; // stack on top of previous overlay 4237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang overlayTopY = overlayBottomY - adapterItem.getHeight(); 4247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 425c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, 426c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang adapterIndex, overlayTopY, overlayBottomY, adapterItem); 4277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang positionOverlay(adapterIndex, overlayTopY, overlayBottomY); 428b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 429c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 430c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang spacerIndex--; 431b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 43259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 43331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang positionSnapHeader(mSnapIndex); 434b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 435b5078b287b1cec38817e342ff054ea901d199329Andy Huang 436b5078b287b1cec38817e342ff054ea901d199329Andy Huang /** 4379875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * Executes a measure pass over the specified child overlay view and returns the measured 4389875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * height. The measurement uses whatever the current container's width measure spec is. 4399875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * This method ignores view visibility and returns the height that the view would be if visible. 4409875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * 4419875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * @param overlayView an overlay view to measure. does not actually have to be attached yet. 4429875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * @return height that the view would be if it was visible 443b5078b287b1cec38817e342ff054ea901d199329Andy Huang */ 4449875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang public int measureOverlay(View overlayView) { 4459875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 4469875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang return overlayView.getMeasuredHeight(); 4479875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 448c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 4499875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang /** 4509875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * Copied/stolen from {@link ListView}. 4519875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang */ 4529875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang private void measureOverlayView(View child) { 45347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams(); 454b5078b287b1cec38817e342ff054ea901d199329Andy Huang if (p == null) { 45547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang p = (MarginLayoutParams) generateDefaultLayoutParams(); 456b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 457b5078b287b1cec38817e342ff054ea901d199329Andy Huang 458b5078b287b1cec38817e342ff054ea901d199329Andy Huang int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 45947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang getPaddingLeft() + getPaddingRight() + p.leftMargin + p.rightMargin, p.width); 460b5078b287b1cec38817e342ff054ea901d199329Andy Huang int lpHeight = p.height; 461b5078b287b1cec38817e342ff054ea901d199329Andy Huang int childHeightSpec; 462b5078b287b1cec38817e342ff054ea901d199329Andy Huang if (lpHeight > 0) { 463b5078b287b1cec38817e342ff054ea901d199329Andy Huang childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); 464b5078b287b1cec38817e342ff054ea901d199329Andy Huang } else { 465b5078b287b1cec38817e342ff054ea901d199329Andy Huang childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 466b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 467b5078b287b1cec38817e342ff054ea901d199329Andy Huang child.measure(childWidthSpec, childHeightSpec); 468b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 469b5078b287b1cec38817e342ff054ea901d199329Andy Huang 47046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void onOverlayScrolledOff(final int adapterIndex, final OverlayView overlay, 47146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int overlayTop, int overlayBottom) { 47265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang // detach the view asynchronously, as scroll notification can happen during a draw, when 47365fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang // it's not safe to remove children 474b5078b287b1cec38817e342ff054ea901d199329Andy Huang 47565fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang // but immediately remove this view from the view set so future lookups don't find it 47665fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang mOverlayViews.remove(adapterIndex); 477b5078b287b1cec38817e342ff054ea901d199329Andy Huang 478b5078b287b1cec38817e342ff054ea901d199329Andy Huang post(new Runnable() { 479b5078b287b1cec38817e342ff054ea901d199329Andy Huang @Override 480b5078b287b1cec38817e342ff054ea901d199329Andy Huang public void run() { 48146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang detachOverlay(overlay); 482b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 483b5078b287b1cec38817e342ff054ea901d199329Andy Huang }); 484b5078b287b1cec38817e342ff054ea901d199329Andy Huang 485b5078b287b1cec38817e342ff054ea901d199329Andy Huang // push it out of view immediately 486b5078b287b1cec38817e342ff054ea901d199329Andy Huang // otherwise this scrolled-off header will continue to draw until the runnable runs 48746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang layoutOverlay(overlay.view, overlayTop, overlayBottom); 4887bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 4897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4908fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang /** 491c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * Returns an existing scrap view, if available. The view will already be removed from the view 4928fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang * hierarchy. This method will not remove the view from the scrap heap. 4938fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang * 4948fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang */ 4957bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public View getScrapView(int type) { 4968fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang return mScrapViews.peek(type); 4977bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 4987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void addScrapView(int type, View v) { 5007bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mScrapViews.add(type, v); 501b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 502b5078b287b1cec38817e342ff054ea901d199329Andy Huang 50346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void detachOverlay(OverlayView overlay) { 504c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang // Prefer removeViewInLayout over removeView. The typical followup layout pass is unneeded 505c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang // because removing overlay views doesn't affect overall layout. 506c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang removeViewInLayout(overlay.view); 50746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mScrapViews.add(overlay.itemType, overlay.view); 50846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (overlay.view instanceof DetachListener) { 50946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang ((DetachListener) overlay.view).onDetachedFromParent(); 510cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 511cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 512cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang 513cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang @Override 514f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 515f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang super.onMeasure(widthMeasureSpec, heightMeasureSpec); 516632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 517632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%s/%s", 518632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang MeasureSpec.toString(widthMeasureSpec), 519632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang MeasureSpec.toString(heightMeasureSpec)); 520632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 521f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 52247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (View nonScrollingChild : mNonScrollingChildren) { 52347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (nonScrollingChild.getVisibility() != GONE) { 52447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang measureChildWithMargins(nonScrollingChild, widthMeasureSpec, 0 /* widthUsed */, 52547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang heightMeasureSpec, 0 /* heightUsed */); 52647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 5273233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 528b5078b287b1cec38817e342ff054ea901d199329Andy Huang mWidthMeasureSpec = widthMeasureSpec; 5297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 5309875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang // onLayout will re-measure and re-position overlays for the new container size, but the 5319875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang // spacer offsets would still need to be updated to have them draw at their new locations. 532f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 533f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 534f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 535f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang protected void onLayout(boolean changed, int l, int t, int r, int b) { 536632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "*** IN header container onLayout"); 537f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 53847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (View nonScrollingChild : mNonScrollingChildren) { 53947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (nonScrollingChild.getVisibility() != GONE) { 54047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int w = nonScrollingChild.getMeasuredWidth(); 54147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int h = nonScrollingChild.getMeasuredHeight(); 54247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 54347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final MarginLayoutParams lp = 54447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang (MarginLayoutParams) nonScrollingChild.getLayoutParams(); 54547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 54647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childLeft = lp.leftMargin; 54747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childTop = lp.topMargin; 54847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang nonScrollingChild.layout(childLeft, childTop, childLeft + w, childTop + h); 54947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 55047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 5515ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 552e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang if (mOverlayAdapter != null) { 553e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang // being in a layout pass means overlay children may require measurement, 554e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang // so invalidate them 555e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) { 556e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang mOverlayAdapter.getItem(i).invalidateMeasurement(); 557e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang } 5588778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang } 5598778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang 5608778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang positionOverlays(0, mOffsetY); 5619875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 5629875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang 56347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 5647517e3b61b898a57f19be0671f70d58a82224643Andy Huang public void requestLayout() { 5657517e3b61b898a57f19be0671f70d58a82224643Andy Huang Utils.checkRequestLayout(this); 5667517e3b61b898a57f19be0671f70d58a82224643Andy Huang super.requestLayout(); 5677517e3b61b898a57f19be0671f70d58a82224643Andy Huang } 5687517e3b61b898a57f19be0671f70d58a82224643Andy Huang 5697517e3b61b898a57f19be0671f70d58a82224643Andy Huang @Override 57059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang protected void dispatchDraw(Canvas canvas) { 57159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang super.dispatchDraw(canvas); 57259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 57359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (mAttachedOverlaySinceLastDraw) { 57459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang drawChild(canvas, mTopMostOverlay, getDrawingTime()); 57559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mAttachedOverlaySinceLastDraw = false; 57659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 57759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 57859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 57959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang @Override 58047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected LayoutParams generateDefaultLayoutParams() { 58147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 58247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 58347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 58447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 58547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public LayoutParams generateLayoutParams(AttributeSet attrs) { 58647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(getContext(), attrs); 58747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 58847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 58947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 59047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 59147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(p); 59247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 59347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 59447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 59547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 59647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return p instanceof MarginLayoutParams; 59747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 59847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 5997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private int getOverlayBottom(int spacerIndex) { 6007bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // TODO: round or truncate? 6017bdc3750454efe59617b7df945eadd7e59bee954Andy Huang return (int) (mOverlayBottoms[spacerIndex] * mScale); 602b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 603b5078b287b1cec38817e342ff054ea901d199329Andy Huang 6047bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private void positionOverlay(int adapterIndex, int overlayTopY, int overlayBottomY) { 60546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final OverlayView overlay = mOverlayViews.get(adapterIndex); 60646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(adapterIndex); 6079875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang 60831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // save off the item's current top for later snap calculations 60931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang item.setTop(overlayTopY); 61031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 611c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // is the overlay visible and does it have non-zero height? 612c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY 613c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang && overlayTopY < mOffsetY + getHeight()) { 61446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang View overlayView = overlay != null ? overlay.view : null; 6157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // show and/or move overlay 6167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (overlayView == null) { 6177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang overlayView = addOverlayView(adapterIndex); 6189875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 6199875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang item.markMeasurementValid(); 6209875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("show/measure overlay %d", adapterIndex); 621c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } else { 622c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("move overlay %d", adapterIndex); 6239875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang if (!item.isMeasurementValid()) { 6249875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 6259875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang item.markMeasurementValid(); 6269875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex, 6279875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang overlayView.getHeight(), overlayView.getMeasuredHeight()); 6289875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 6297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 6309875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("laying out overlay %d with h=%d", adapterIndex, 6319875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang overlayView.getMeasuredHeight()); 63265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang layoutOverlay(overlayView, overlayTopY, overlayTopY + overlayView.getMeasuredHeight()); 6337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } else { 6347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // hide overlay 63546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (overlay != null) { 636c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("hide overlay %d", adapterIndex); 63746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang onOverlayScrolledOff(adapterIndex, overlay, overlayTopY, overlayBottomY); 638c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } else { 639c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("ignore non-visible overlay %d", adapterIndex); 6407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 6417bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 64259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 64359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) { 64459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (mSnapIndex == -1) { 64559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = adapterIndex; 64659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } else if (adapterIndex > mSnapIndex) { 64759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = adapterIndex; 64859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 64959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 65059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 651b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 652b5078b287b1cec38817e342ff054ea901d199329Andy Huang 6537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // layout an existing view 6547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // need its top offset into the conversation, its height, and the scroll offset 655c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private void layoutOverlay(View child, int childTop, int childBottom) { 6567bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int top = childTop - mOffsetY; 657c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang final int bottom = childBottom - mOffsetY; 65847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 65947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 66047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childLeft = getPaddingLeft() + lp.leftMargin; 66147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 66247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang child.layout(childLeft, top, childLeft + child.getMeasuredWidth(), bottom); 663b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 664b5078b287b1cec38817e342ff054ea901d199329Andy Huang 6657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private View addOverlayView(int adapterIndex) { 6667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int itemType = mOverlayAdapter.getItemViewType(adapterIndex); 6677bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final View convertView = mScrapViews.poll(itemType); 6687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 669256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang final View view = mOverlayAdapter.getView(adapterIndex, convertView, this); 67046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews.put(adapterIndex, new OverlayView(view, itemType)); 671f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 67259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang final int index = BOTTOM_LAYER_VIEW_IDS.length; 67347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 674c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang if (convertView == view) { 675c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view); 6767bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } else { 677c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view); 678f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 679c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang addViewInLayout(view, index, view.getLayoutParams(), true /* preventRequestLayout */); 6807bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 68159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mAttachedOverlaySinceLastDraw = true; 68259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 6837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang return view; 684f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 685f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 68631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // render and/or re-position snap header 68731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private void positionSnapHeader(int snapIndex) { 68831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang ConversationOverlayItem snapItem = null; 68931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (snapIndex != -1) { 69031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(snapIndex); 69131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (item.canBecomeSnapHeader()) { 69231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang snapItem = item; 69331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 69431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 69531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (snapItem == null) { 69631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.setVisibility(GONE); 69731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.unbind(); 69831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return; 69931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 70031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 70131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang snapItem.bindView(mSnapHeader, false /* measureOnly */); 70231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.setVisibility(VISIBLE); 70331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 7040a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang // overlap is negative or zero; bump the snap header upwards by that much 70531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang int overlap = 0; 70631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 70731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem next = findNextPushingOverlay(snapIndex + 1); 70831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (next != null) { 70931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang overlap = Math.min(0, next.getTop() - mSnapHeader.getHeight() - mOffsetY); 71031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 71131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // disable overlap drawing past a certain speed 71231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (overlap < 0) { 71331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final Float v = mVelocityTracker.getSmoothedVelocity(); 71431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (v != null && v > SNAP_HEADER_MAX_SCROLL_SPEED) { 71531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang overlap = 0; 71631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 71731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 71831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 7190a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang 7200a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang mSnapHeader.setTranslationY(overlap); 72131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 72231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 72331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // find the next header that can push the snap header up 72431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private ConversationOverlayItem findNextPushingOverlay(int start) { 72531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang int value = -1; 72631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang for (int i = start, len = mOverlayAdapter.getCount(); i < len; i++) { 72731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem next = mOverlayAdapter.getItem(i); 72831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (next.canPushSnapHeader()) { 72931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return next; 73031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 73131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 73231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return null; 73331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 73431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 735c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang /** 736c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * Prevents any layouts from happening until the next time {@link #onGeometryChange(int[])} is 737c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * called. Useful when you know the HTML spacer coordinates are inconsistent with adapter items. 738c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * <p> 739c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * If you call this, you must ensure that a followup call to {@link #onGeometryChange(int[])} 740c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * is made later, when the HTML spacer coordinates are updated. 741c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * 742c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang */ 743c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang public void invalidateSpacerGeometry() { 744c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mOverlayBottoms = null; 745c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } 746c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 7477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void onGeometryChange(int[] overlayBottoms) { 748c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("*** got overlay spacer bottoms:"); 7497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang for (int offsetY : overlayBottoms) { 750c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("%d", offsetY); 751f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 752f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 7537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mOverlayBottoms = overlayBottoms; 7547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang positionOverlays(0, mOffsetY); 755f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 756f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 757c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private void traceLayout(String msg, Object... params) { 758c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang if (mDisableLayoutTracing) { 759c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang return; 760c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } 7617f9ef60c5e42470814f6b4bb8ed771c17fdfea22Andy Huang LogUtils.d(TAG, msg, params); 762c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } 763c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 76446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private class AdapterObserver extends DataSetObserver { 76546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang @Override 76646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public void onChanged() { 76746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang onDataSetChanged(); 76846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 76946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 770f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang} 771