ConversationContainer.java revision 2fd167d6131482da984768b5ee75cefa32ed3e32
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; 218f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport android.content.res.Configuration; 2246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport android.database.DataSetObserver; 232fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sappersteinimport android.support.v4.view.ViewCompat; 24f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.util.AttributeSet; 2565fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huangimport android.util.SparseArray; 26adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport android.view.Gravity; 27bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.MotionEvent; 28f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.View; 29bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.ViewConfiguration; 30f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.ViewGroup; 31f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebView; 32f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.widget.Adapter; 33b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport android.widget.ListView; 34bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.widget.ScrollView; 35f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 36b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport com.android.mail.R; 375ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ScrollNotifier.ScrollListener; 388f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.providers.UIProvider; 39632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huangimport com.android.mail.ui.ConversationViewFragment; 407bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.utils.DequeMap; 4131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huangimport com.android.mail.utils.InputSmoother; 42f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.utils.LogUtils; 4347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.google.common.collect.Lists; 4447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 4547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport java.util.List; 46b5078b287b1cec38817e342ff054ea901d199329Andy Huang 47f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/** 48b5078b287b1cec38817e342ff054ea901d199329Andy Huang * A specialized ViewGroup container for conversation view. It is designed to contain a single 49b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link WebView} and a number of overlay views that draw on top of the WebView. In the Mail app, 50b5078b287b1cec38817e342ff054ea901d199329Andy Huang * the WebView contains all HTML message bodies in a conversation, and the overlay views are the 51b5078b287b1cec38817e342ff054ea901d199329Andy Huang * subject view, message headers, and attachment views. The WebView does all scroll handling, and 52b5078b287b1cec38817e342ff054ea901d199329Andy Huang * this container manages scrolling of the overlay views so that they move in tandem. 53b5078b287b1cec38817e342ff054ea901d199329Andy Huang * 54b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>INPUT HANDLING</h5> 55b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Placing the WebView in the same container as the overlay views means we don't have to do a lot of 56b5078b287b1cec38817e342ff054ea901d199329Andy Huang * manual manipulation of touch events. We do have a 57b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link #forwardFakeMotionEvent(MotionEvent, int)} method that deals with one WebView 58b5078b287b1cec38817e342ff054ea901d199329Andy Huang * idiosyncrasy: it doesn't react well when touch MOVE events stream in without a preceding DOWN. 59b5078b287b1cec38817e342ff054ea901d199329Andy Huang * 60b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>VIEW RECYCLING</h5> 61b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Normally, it would make sense to put all overlay views into a {@link ListView}. But this view 62b5078b287b1cec38817e342ff054ea901d199329Andy Huang * sandwich has unique characteristics: the list items are scrolled based on an external controller, 63b5078b287b1cec38817e342ff054ea901d199329Andy Huang * and we happen to know all of the overlay positions up front. So it didn't make sense to shoehorn 64b5078b287b1cec38817e342ff054ea901d199329Andy Huang * a ListView in and instead, we rolled our own view recycler by borrowing key details from 65bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * ListView and AbsListView.<br/><br/> 66f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 67bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * There is one additional constraint with the recycling: since scroll 68bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * notifications happen during the WebView's draw, we do not remove and re-add views for recycling. 69bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * Instead, we simply move the views off-screen and add them to our recycle cache. When the views 70bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * are reused, they are simply moved back on screen instead of added. This practice 71bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * circumvents the issues found when views are added or removed during draw (which results in 72bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * elements not being drawn and other visual oddities). See b/10994303 for more details. 73f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */ 74f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpublic class ConversationContainer extends ViewGroup implements ScrollListener { 75632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang private static final String TAG = ConversationViewFragment.LAYOUT_TAG; 76bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 7747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final int[] BOTTOM_LAYER_VIEW_IDS = { 7814f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein R.id.webview, 7914f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein R.id.conversation_side_border_overlay 8047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang }; 8147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 8247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final int[] TOP_LAYER_VIEW_IDS = { 8347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang R.id.conversation_topmost_overlay 8447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang }; 8547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 8631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang /** 8731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang * Maximum scroll speed (in dp/sec) at which the snap header animation will draw. 8831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang * Anything faster than that, and drawing it creates visual artifacting (wagon-wheel effect). 8931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang */ 9031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private static final float SNAP_HEADER_MAX_SCROLL_SPEED = 600f; 9131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 928f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang private ConversationAccountController mAccountController; 937bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private ConversationViewAdapter mOverlayAdapter; 94adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private OverlayPosition[] mOverlayPositions; 95bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private ConversationWebView mWebView; 96a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein private SnapHeader mSnapHeader; 97f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 9847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private final List<View> mNonScrollingChildren = Lists.newArrayList(); 9947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 100bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 10123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * Current document zoom scale per {@link WebView#getScale()}. This is the ratio of actual 10223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * screen pixels to logical WebView HTML pixels. We use it to convert from one to the other. 103bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 104bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private float mScale; 105120ea66184c31bdeb729274054ec913afe3872c1Andy Huang /** 106120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * Set to true upon receiving the first touch event. Used to help reject invalid WebView scale 107120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * values. 108120ea66184c31bdeb729274054ec913afe3872c1Andy Huang */ 109120ea66184c31bdeb729274054ec913afe3872c1Andy Huang private boolean mTouchInitialized; 110f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 111bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 112bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * System touch-slop distance per {@link ViewConfiguration#getScaledTouchSlop()}. 113bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 114bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private final int mTouchSlop; 115bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 116bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Current scroll position, as dictated by the background {@link WebView}. 117bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 118f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private int mOffsetY; 119bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 120bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Original pointer Y for slop calculation. 121bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 122bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private float mLastMotionY; 123bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 124bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Original pointer ID for slop calculation. 125bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 126bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private int mActivePointerId; 127bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 128bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Track pointer up/down state to know whether to send a make-up DOWN event to WebView. 129bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * WebView internal logic requires that a stream of {@link MotionEvent#ACTION_MOVE} events be 130bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * preceded by a {@link MotionEvent#ACTION_DOWN} event. 131bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 132bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private boolean mTouchIsDown = false; 133bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 134bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Remember if touch interception was triggered on a {@link MotionEvent#ACTION_POINTER_DOWN}, 135bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * so we can send a make-up event in {@link #onTouchEvent(MotionEvent)}. 136bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 137bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private boolean mMissedPointerDown; 138f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 140c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * A recycler that holds removed scrap views, organized by integer item view type. All views 141c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * in this data structure should be removed from their view parent prior to insertion. 1427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 1437bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private final DequeMap<Integer, View> mScrapViews = new DequeMap<Integer, View>(); 144b5078b287b1cec38817e342ff054ea901d199329Andy Huang 145b5078b287b1cec38817e342ff054ea901d199329Andy Huang /** 14665fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * The current set of overlay views in the view hierarchy. Looking through this map is faster 14765fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * than traversing the view hierarchy. 148b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <p> 149b5078b287b1cec38817e342ff054ea901d199329Andy Huang * WebView sometimes notifies of scroll changes during a draw (or display list generation), when 150b5078b287b1cec38817e342ff054ea901d199329Andy Huang * it's not safe to detach view children because ViewGroup is in the middle of iterating over 15165fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * its child array. So we remove any child from this list immediately and queue up a task to 15265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * detach it later. Since nobody other than the detach task references that view in the 15365fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * meantime, we don't need any further checks or synchronization. 15446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * <p> 15546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * We keep {@link OverlayView} wrappers instead of bare views so that when it's time to dispose 15646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * of all views (on data set or adapter change), we can at least recycle them into the typed 15746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * scrap piles for later reuse. 158b5078b287b1cec38817e342ff054ea901d199329Andy Huang */ 15946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private final SparseArray<OverlayView> mOverlayViews; 160b5078b287b1cec38817e342ff054ea901d199329Andy Huang 161b5078b287b1cec38817e342ff054ea901d199329Andy Huang private int mWidthMeasureSpec; 162b5078b287b1cec38817e342ff054ea901d199329Andy Huang 163c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private boolean mDisableLayoutTracing; 164c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 16531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private final InputSmoother mVelocityTracker; 16631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 16746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private final DataSetObserver mAdapterObserver = new AdapterObserver(); 16846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 169cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang /** 17059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * The adapter index of the lowest overlay item that is above the top of the screen and reports 17159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through 172bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * {@link #positionOverlays}. 17359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 17459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang */ 17559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private int mSnapIndex; 17659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 1778f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang private boolean mSnapEnabled; 1788f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 179ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 180ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * A View that fills the remaining vertical space when the overlays do not take 181ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * up the entire container. Otherwise, a card-like bottom white space appears. 182ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 183888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private View mAdditionalBottomBorder; 184ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 185ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 186ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * A flag denoting whether the fake bottom border has been added to the container. 187ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 188888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private boolean mAdditionalBottomBorderAdded; 189888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 19059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang /** 191ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * An int containing the potential top value for the additional bottom border. 192ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * If this value is less than the height of the scroll container, the additional 193ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * bottom border will be drawn. 194ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 195ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein private int mAdditionalBottomBorderOverlayTop; 196ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 197ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 198cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * Child views of this container should implement this interface to be notified when they are 199cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * being detached. 200cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * 201cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang */ 202cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang public interface DetachListener { 203cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang /** 204cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * Called on a child view when it is removed from its parent as part of 205cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * {@link ConversationContainer} view recycling. 206cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang */ 207cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang void onDetachedFromParent(); 208cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 209cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang 210adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public static class OverlayPosition { 211adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public final int top; 212adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public final int bottom; 213adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 214adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public OverlayPosition(int top, int bottom) { 215adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang this.top = top; 216adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang this.bottom = bottom; 217adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 218adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 219adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 22046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private static class OverlayView { 22146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public View view; 22246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int itemType; 22346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 22446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public OverlayView(View view, int itemType) { 22546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang this.view = view; 22646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang this.itemType = itemType; 22746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 22846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 22946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 230f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public ConversationContainer(Context c) { 231f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang this(c, null); 232f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 233f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 234f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public ConversationContainer(Context c, AttributeSet attrs) { 235f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang super(c, attrs); 236bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 23746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews = new SparseArray<OverlayView>(); 238b5078b287b1cec38817e342ff054ea901d199329Andy Huang 23931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mVelocityTracker = new InputSmoother(c); 24031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 241bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchSlop = ViewConfiguration.get(c).getScaledTouchSlop(); 242bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 243bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // Disabling event splitting fixes pinch-zoom when the first pointer goes down on the 244bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // WebView and the second pointer goes down on an overlay view. 245bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // Intercepting ACTION_POINTER_DOWN events allows pinch-zoom to work when the first pointer 246bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // goes down on an overlay view. 247bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang setMotionEventSplittingEnabled(false); 248bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 249bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 250bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 251bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang protected void onFinishInflate() { 252bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang super.onFinishInflate(); 253bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 2545ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang mWebView = (ConversationWebView) findViewById(R.id.webview); 255bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mWebView.addScrollListener(this); 25647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 25747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (int id : BOTTOM_LAYER_VIEW_IDS) { 25847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNonScrollingChildren.add(findViewById(id)); 25947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 26047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (int id : TOP_LAYER_VIEW_IDS) { 26147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNonScrollingChildren.add(findViewById(id)); 26247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 263f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 264f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 26585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein public void setupSnapHeader() { 266a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein mSnapHeader = (SnapHeader) findViewById(R.id.snap_header); 267a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein mSnapHeader.setSnappy(); 26885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 26985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 270a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein public SnapHeader getSnapHeader() { 27159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang return mSnapHeader; 27259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 27359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 2747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void setOverlayAdapter(ConversationViewAdapter a) { 27546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (mOverlayAdapter != null) { 27646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver); 27746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang clearOverlays(); 27846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 279f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mOverlayAdapter = a; 28046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (mOverlayAdapter != null) { 28146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayAdapter.registerDataSetObserver(mAdapterObserver); 28246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 28351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 28451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 28551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang public Adapter getOverlayAdapter() { 28651067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang return mOverlayAdapter; 287f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 288f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 2898f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang public void setAccountController(ConversationAccountController controller) { 2908f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mAccountController = controller; 2918f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 2928f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mSnapEnabled = isSnapEnabled(); 2938f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang } 2948f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 2956b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang /** 2966b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang * Re-bind any existing views that correspond to the given adapter positions. 2976b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang * 2986b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang */ 2996b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang public void onOverlayModelUpdate(List<Integer> affectedAdapterPositions) { 3006b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang for (Integer i : affectedAdapterPositions) { 3016b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(i); 3026b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang final OverlayView overlay = mOverlayViews.get(i); 3036b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang if (overlay != null && overlay.view != null && item != null) { 3046b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang item.onModelUpdated(overlay.view); 3056b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3064baafcbb39e20a81a1585270fc270a560ec8824dAndy Huang // update the snap header too, but only it's showing if the current item 3074baafcbb39e20a81a1585270fc270a560ec8824dAndy Huang if (i == mSnapIndex && mSnapHeader.isBoundTo(item)) { 3086b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang mSnapHeader.refresh(); 3096b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3106b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3116b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3126b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang 313c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang /** 314c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * Return an overlay view for the given adapter item, or null if no matching view is currently 315c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * visible. This can happen as you scroll away from an overlay view. 316c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * 317c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang */ 318c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang public View getViewForItem(ConversationOverlayItem item) { 319c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang View result = null; 320c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang int adapterPos = -1; 321c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) { 322c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang if (mOverlayAdapter.getItem(i) == item) { 323c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang adapterPos = i; 324c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang break; 325c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 326c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 327c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang if (adapterPos != -1) { 328c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang final OverlayView overlay = mOverlayViews.get(adapterPos); 329c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang if (overlay != null) { 330c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang result = overlay.view; 331c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 332c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 333c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang return result; 334c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 335c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang 33646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void clearOverlays() { 33746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang for (int i = 0, len = mOverlayViews.size(); i < len; i++) { 338bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein detachOverlay(mOverlayViews.valueAt(i), true /* removeFromContainer */); 33946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 34046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews.clear(); 34146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 34246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 34346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void onDataSetChanged() { 3442a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // Recycle all views and re-bind them according to the current set of spacer coordinates. 3452a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // This essentially resets the overlay views and re-renders them. 3462a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // It's fast enough that it's okay to re-do all views on any small change, as long as 3472a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // the change isn't too frequent (< ~1Hz). 3482a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang 34946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang clearOverlays(); 3502a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // also unbind the snap header view, so this "reset" causes the snap header to re-create 3512a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // its view, just like all other headers 3522a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang mSnapHeader.unbind(); 353ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 354ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein // also clear out the additional bottom border 355ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein removeViewInLayout(mAdditionalBottomBorder); 356ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderAdded = false; 357ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 3588f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mSnapEnabled = isSnapEnabled(); 359bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionOverlays(mOffsetY, false /* postAddView */); 36046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 36146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 362bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private void forwardFakeMotionEvent(MotionEvent original, int newAction) { 363bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang MotionEvent newEvent = MotionEvent.obtain(original); 364bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.setAction(newAction); 365bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mWebView.onTouchEvent(newEvent); 366bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang LogUtils.v(TAG, "in Container.OnTouch. fake: action=%d x/y=%f/%f pointers=%d", 367bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.getActionMasked(), newEvent.getX(), newEvent.getY(), 368bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.getPointerCount()); 369bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 370bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 371bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 372bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Touch slop code was copied from {@link ScrollView#onInterceptTouchEvent(MotionEvent)}. 373bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 374bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 375bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang public boolean onInterceptTouchEvent(MotionEvent ev) { 376120ea66184c31bdeb729274054ec913afe3872c1Andy Huang 377120ea66184c31bdeb729274054ec913afe3872c1Andy Huang if (!mTouchInitialized) { 378120ea66184c31bdeb729274054ec913afe3872c1Andy Huang mTouchInitialized = true; 379120ea66184c31bdeb729274054ec913afe3872c1Andy Huang } 380120ea66184c31bdeb729274054ec913afe3872c1Andy Huang 381632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang // no interception when WebView handles the first DOWN 382632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (mWebView.isHandlingTouch()) { 383632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang return false; 384632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 385632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 386bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang boolean intercept = false; 387bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang switch (ev.getActionMasked()) { 388bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_POINTER_DOWN: 389632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "Container is intercepting non-primary touch!"); 390bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang intercept = true; 391bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mMissedPointerDown = true; 392632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang requestDisallowInterceptTouchEvent(true); 393bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 394bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 395bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_DOWN: 396bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mLastMotionY = ev.getY(); 397bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mActivePointerId = ev.getPointerId(0); 398bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 399bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 400bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_MOVE: 401bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int pointerIndex = ev.findPointerIndex(mActivePointerId); 402bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final float y = ev.getY(pointerIndex); 403bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int yDiff = (int) Math.abs(y - mLastMotionY); 404bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang if (yDiff > mTouchSlop) { 405bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mLastMotionY = y; 406bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang intercept = true; 407bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 408bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 409bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 410bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 411632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// LogUtils.v(TAG, "in Container.InterceptTouch. action=%d x/y=%f/%f pointers=%d result=%s", 412632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount(), intercept); 413bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang return intercept; 414bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 415bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 416bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 417bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang public boolean onTouchEvent(MotionEvent ev) { 418bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int action = ev.getActionMasked(); 419bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 420632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 421bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchIsDown = false; 422bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } else if (!mTouchIsDown && 423bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_DOWN)) { 424bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 425bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang forwardFakeMotionEvent(ev, MotionEvent.ACTION_DOWN); 426bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang if (mMissedPointerDown) { 427bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang forwardFakeMotionEvent(ev, MotionEvent.ACTION_POINTER_DOWN); 428bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mMissedPointerDown = false; 429bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 430bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 431bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchIsDown = true; 432bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 433bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 434bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final boolean webViewResult = mWebView.onTouchEvent(ev); 435bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 436632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// LogUtils.v(TAG, "in Container.OnTouch. action=%d x/y=%f/%f pointers=%d", 437632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount()); 438bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang return webViewResult; 439f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 440f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 441f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 442bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein public void onNotifierScroll(final int y) { 44331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mVelocityTracker.onInput(y); 444c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mDisableLayoutTracing = true; 445bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionOverlays(y, true /* postAddView */); // post the addView since we're in draw code 446c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mDisableLayoutTracing = false; 447b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 448b5078b287b1cec38817e342ff054ea901d199329Andy Huang 449bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein /** 450bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * Positions the overlays given an updated y position for the container. 451bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * @param y the current top position on screen 452bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * @param postAddView If {@code true}, posts all calls to 453bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * {@link #addViewInLayoutWrapper(android.view.View, boolean)} 454bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * to the UI thread rather than adding it immediately. If {@code false}, 455bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * calls {@link #addViewInLayoutWrapper(android.view.View, boolean)} 456bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * immediately. 457bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein */ 458bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private void positionOverlays(int y, boolean postAddView) { 459f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mOffsetY = y; 46007f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang 46107f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang /* 462120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * The scale value that WebView reports is inaccurate when measured during WebView 463120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * initialization. This bug is present in ICS, so to work around it, we ignore all 46423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * reported values and use a calculated expected value from ConversationWebView instead. 46523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * Only when the user actually begins to touch the view (to, say, begin a zoom) do we begin 46623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * to pay attention to WebView-reported scale values. 46707f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang */ 468120ea66184c31bdeb729274054ec913afe3872c1Andy Huang if (mTouchInitialized) { 46907f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang mScale = mWebView.getScale(); 47023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang } else if (mScale == 0) { 47123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mScale = mWebView.getInitialScale(); 47207f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang } 473c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(), 474c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mScale); 475f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 476adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (mOverlayPositions == null || mOverlayAdapter == null) { 47751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang return; 47851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 47951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 480b5078b287b1cec38817e342ff054ea901d199329Andy Huang // recycle scrolled-off views and add newly visible views 481b5078b287b1cec38817e342ff054ea901d199329Andy Huang 4827bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // we want consecutive spacers/overlays to stack towards the bottom 4837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // so iterate from the bottom of the conversation up 4847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // starting with the last spacer bottom and the last adapter item, position adapter views 4857bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // in a single stack until you encounter a non-contiguous expanded message header, 4867bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // then decrement to the next spacer. 487b5078b287b1cec38817e342ff054ea901d199329Andy Huang 488adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayPositions.length, 489c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mOverlayAdapter.getCount()); 490c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 49159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = -1; 492ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderOverlayTop = 0; 49359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 494adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang int adapterLoopIndex = mOverlayAdapter.getCount() - 1; 495adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang int spacerIndex = mOverlayPositions.length - 1; 496adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang while (spacerIndex >= 0 && adapterLoopIndex >= 0) { 497adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 498adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int spacerTop = getOverlayTop(spacerIndex); 499adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int spacerBottom = getOverlayBottom(spacerIndex); 500adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 501adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final boolean flip; 502adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int flipOffset; 503adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int forceGravity; 504adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // flip direction from bottom->top to top->bottom traversal on the very first spacer 505adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // to facilitate top-aligned headers at spacer index = 0 506adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (spacerIndex == 0) { 507adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flip = true; 508adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flipOffset = adapterLoopIndex; 509adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity = Gravity.TOP; 510adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } else { 511adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flip = false; 512adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flipOffset = 0; 513adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity = Gravity.NO_GRAVITY; 514adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 515c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 516adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang int adapterIndex = flip ? flipOffset - adapterLoopIndex : adapterLoopIndex; 5177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 5187bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // always place at least one overlay per spacer 51946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang ConversationOverlayItem adapterItem = mOverlayAdapter.getItem(adapterIndex); 5207bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 521adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang OverlayPosition itemPos = calculatePosition(adapterItem, spacerTop, spacerBottom, 522adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity); 5237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 524c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex, 525adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang itemPos.top, itemPos.bottom, adapterItem); 526bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionOverlay(adapterIndex, itemPos.top, itemPos.bottom, postAddView); 5277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 528adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // and keep stacking overlays unconditionally if we are on the first spacer, or as long 529adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // as overlays are contiguous 530adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang while (--adapterLoopIndex >= 0) { 531adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang adapterIndex = flip ? flipOffset - adapterLoopIndex : adapterLoopIndex; 5327bdc3750454efe59617b7df945eadd7e59bee954Andy Huang adapterItem = mOverlayAdapter.getItem(adapterIndex); 533adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (spacerIndex > 0 && !adapterItem.isContiguous()) { 5347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // advance to the next spacer, but stay on this adapter item 5357bdc3750454efe59617b7df945eadd7e59bee954Andy Huang break; 536b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 5377bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 538adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // place this overlay in the region of the spacer above or below the last item, 539adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // depending on direction of iteration 540adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int regionTop = flip ? itemPos.bottom : spacerTop; 541adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int regionBottom = flip ? spacerBottom : itemPos.top; 542adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang itemPos = calculatePosition(adapterItem, regionTop, regionBottom, forceGravity); 5437bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 544c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, 545adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang adapterIndex, itemPos.top, itemPos.bottom, adapterItem); 546bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionOverlay(adapterIndex, itemPos.top, itemPos.bottom, postAddView); 547b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 548c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 549c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang spacerIndex--; 550b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 55159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 55231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang positionSnapHeader(mSnapIndex); 553bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionAdditionalBottomBorder(postAddView); 554b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 555b5078b287b1cec38817e342ff054ea901d199329Andy Huang 556ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 557ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * Adds an additional bottom border to the overlay views in case 558ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * the overlays do not fill the entire screen. 559ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 560bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private void positionAdditionalBottomBorder(boolean postAddView) { 561ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein final int lastBottom = mAdditionalBottomBorderOverlayTop; 56283b460bf42d48bd8ab18ee135fab181242d13b9aAndrew Sapperstein final int containerHeight = webPxToScreenPx(mWebView.getContentHeight()); 563888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein final int speculativeHeight = containerHeight - lastBottom; 564888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (speculativeHeight > 0) { 565888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (mAdditionalBottomBorder == null) { 566888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAdditionalBottomBorder = mOverlayAdapter.getLayoutInflater().inflate( 567888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein R.layout.fake_bottom_border, this, false); 568888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 569888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 570888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein setAdditionalBottomBorderHeight(speculativeHeight); 571888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 572888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (!mAdditionalBottomBorderAdded) { 573bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein addViewInLayoutWrapper(mAdditionalBottomBorder, postAddView); 574888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAdditionalBottomBorderAdded = true; 575888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 576888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 577888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein measureOverlayView(mAdditionalBottomBorder); 578888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein layoutOverlay(mAdditionalBottomBorder, lastBottom, containerHeight); 579888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } else { 580888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (mAdditionalBottomBorder != null && mAdditionalBottomBorderAdded) { 581ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein removeViewInLayout(mAdditionalBottomBorder); 582ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderAdded = false; 583888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 584888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 585888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 586888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 587888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private void setAdditionalBottomBorderHeight(int speculativeHeight) { 588888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein LayoutParams params = mAdditionalBottomBorder.getLayoutParams(); 589888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein params.height = speculativeHeight; 590888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAdditionalBottomBorder.setLayoutParams(params); 591888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 592888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 593ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy private static OverlayPosition calculatePosition(final ConversationOverlayItem adapterItem, 594ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy final int withinTop, final int withinBottom, final int forceGravity) { 595adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (adapterItem.getHeight() == 0) { 596adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // "place" invisible items at the bottom of their region to stay consistent with the 597adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // stacking algorithm in positionOverlays(), unless gravity is forced to the top 598adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int y = (forceGravity == Gravity.TOP) ? withinTop : withinBottom; 599adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return new OverlayPosition(y, y); 600adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 601adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 602adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int v = ((forceGravity != Gravity.NO_GRAVITY) ? 603adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity : adapterItem.getGravity()) & Gravity.VERTICAL_GRAVITY_MASK; 604adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang switch (v) { 605adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang case Gravity.BOTTOM: 606adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return new OverlayPosition(withinBottom - adapterItem.getHeight(), withinBottom); 607adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang case Gravity.TOP: 608adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return new OverlayPosition(withinTop, withinTop + adapterItem.getHeight()); 609adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang default: 610adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang throw new UnsupportedOperationException("unsupported gravity: " + v); 611adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 612adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 613adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 614b5078b287b1cec38817e342ff054ea901d199329Andy Huang /** 6159875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * Executes a measure pass over the specified child overlay view and returns the measured 6169875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * height. The measurement uses whatever the current container's width measure spec is. 6179875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * This method ignores view visibility and returns the height that the view would be if visible. 6189875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * 6199875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * @param overlayView an overlay view to measure. does not actually have to be attached yet. 6209875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * @return height that the view would be if it was visible 621b5078b287b1cec38817e342ff054ea901d199329Andy Huang */ 6229875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang public int measureOverlay(View overlayView) { 6239875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 6249875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang return overlayView.getMeasuredHeight(); 6259875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 626c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 6279875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang /** 6289875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * Copied/stolen from {@link ListView}. 6299875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang */ 6309875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang private void measureOverlayView(View child) { 63147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams(); 632b5078b287b1cec38817e342ff054ea901d199329Andy Huang if (p == null) { 63347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang p = (MarginLayoutParams) generateDefaultLayoutParams(); 634b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 635b5078b287b1cec38817e342ff054ea901d199329Andy Huang 636b5078b287b1cec38817e342ff054ea901d199329Andy Huang int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 63747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang getPaddingLeft() + getPaddingRight() + p.leftMargin + p.rightMargin, p.width); 638b5078b287b1cec38817e342ff054ea901d199329Andy Huang int lpHeight = p.height; 639b5078b287b1cec38817e342ff054ea901d199329Andy Huang int childHeightSpec; 640b5078b287b1cec38817e342ff054ea901d199329Andy Huang if (lpHeight > 0) { 641b5078b287b1cec38817e342ff054ea901d199329Andy Huang childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); 642b5078b287b1cec38817e342ff054ea901d199329Andy Huang } else { 643b5078b287b1cec38817e342ff054ea901d199329Andy Huang childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 644b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 645b5078b287b1cec38817e342ff054ea901d199329Andy Huang child.measure(childWidthSpec, childHeightSpec); 646b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 647b5078b287b1cec38817e342ff054ea901d199329Andy Huang 64846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void onOverlayScrolledOff(final int adapterIndex, final OverlayView overlay, 64946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int overlayTop, int overlayBottom) { 650bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein // immediately remove this view from the view set so future lookups don't find it 65165fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang mOverlayViews.remove(adapterIndex); 652b5078b287b1cec38817e342ff054ea901d199329Andy Huang 653bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein // detach but don't actually remove from the view 654bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein detachOverlay(overlay, false /* removeFromContainer */); 655b5078b287b1cec38817e342ff054ea901d199329Andy Huang 656b5078b287b1cec38817e342ff054ea901d199329Andy Huang // push it out of view immediately 657b5078b287b1cec38817e342ff054ea901d199329Andy Huang // otherwise this scrolled-off header will continue to draw until the runnable runs 65846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang layoutOverlay(overlay.view, overlayTop, overlayBottom); 6597bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 6607bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 6618fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang /** 662c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * Returns an existing scrap view, if available. The view will already be removed from the view 6638fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang * hierarchy. This method will not remove the view from the scrap heap. 6648fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang * 6658fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang */ 6667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public View getScrapView(int type) { 6678fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang return mScrapViews.peek(type); 6687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 6697bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 6707bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void addScrapView(int type, View v) { 6717bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mScrapViews.add(type, v); 672bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein addViewInLayoutWrapper(v, false /* postAddView */); 673b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 674b5078b287b1cec38817e342ff054ea901d199329Andy Huang 675bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private void detachOverlay(OverlayView overlay, boolean removeFromContainer) { 676c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang // Prefer removeViewInLayout over removeView. The typical followup layout pass is unneeded 677c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang // because removing overlay views doesn't affect overall layout. 678bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein if (removeFromContainer) { 679bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein removeViewInLayout(overlay.view); 680bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } 68146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mScrapViews.add(overlay.itemType, overlay.view); 68246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (overlay.view instanceof DetachListener) { 68346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang ((DetachListener) overlay.view).onDetachedFromParent(); 684cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 685cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 686cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang 687cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang @Override 688f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 689f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang super.onMeasure(widthMeasureSpec, heightMeasureSpec); 690632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 691632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%s/%s", 692632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang MeasureSpec.toString(widthMeasureSpec), 693632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang MeasureSpec.toString(heightMeasureSpec)); 694632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 695f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 69647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (View nonScrollingChild : mNonScrollingChildren) { 69747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (nonScrollingChild.getVisibility() != GONE) { 69847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang measureChildWithMargins(nonScrollingChild, widthMeasureSpec, 0 /* widthUsed */, 69947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang heightMeasureSpec, 0 /* heightUsed */); 70047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 7013233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 702b5078b287b1cec38817e342ff054ea901d199329Andy Huang mWidthMeasureSpec = widthMeasureSpec; 7037bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 7049875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang // onLayout will re-measure and re-position overlays for the new container size, but the 7059875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang // spacer offsets would still need to be updated to have them draw at their new locations. 706f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 707f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 708f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 709f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang protected void onLayout(boolean changed, int l, int t, int r, int b) { 710632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "*** IN header container onLayout"); 711f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 71247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (View nonScrollingChild : mNonScrollingChildren) { 71347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (nonScrollingChild.getVisibility() != GONE) { 71447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int w = nonScrollingChild.getMeasuredWidth(); 71547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int h = nonScrollingChild.getMeasuredHeight(); 71647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 71747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final MarginLayoutParams lp = 71847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang (MarginLayoutParams) nonScrollingChild.getLayoutParams(); 71947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 72047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childLeft = lp.leftMargin; 72147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childTop = lp.topMargin; 72247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang nonScrollingChild.layout(childLeft, childTop, childLeft + w, childTop + h); 72347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 72447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 7255ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 726e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang if (mOverlayAdapter != null) { 727e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang // being in a layout pass means overlay children may require measurement, 728e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang // so invalidate them 729e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) { 730e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang mOverlayAdapter.getItem(i).invalidateMeasurement(); 731e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang } 7328778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang } 7338778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang 734bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionOverlays(mOffsetY, false /* postAddView */); 73559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 73659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 73759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang @Override 73847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected LayoutParams generateDefaultLayoutParams() { 73947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 74047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 74147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 74247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 74347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public LayoutParams generateLayoutParams(AttributeSet attrs) { 74447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(getContext(), attrs); 74547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 74647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 74747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 74847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 74947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(p); 75047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 75147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 75247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 75347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 75447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return p instanceof MarginLayoutParams; 75547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 75647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 757adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private int getOverlayTop(int spacerIndex) { 758adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return webPxToScreenPx(mOverlayPositions[spacerIndex].top); 759adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 760adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 7617bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private int getOverlayBottom(int spacerIndex) { 762adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return webPxToScreenPx(mOverlayPositions[spacerIndex].bottom); 763adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 764adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 765adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private int webPxToScreenPx(int webPx) { 7667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // TODO: round or truncate? 767adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // TODO: refactor and unify with ConversationWebView.webPxToScreenPx() 768adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return (int) (webPx * mScale); 769b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 770b5078b287b1cec38817e342ff054ea901d199329Andy Huang 771bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private void positionOverlay( 772bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein int adapterIndex, int overlayTopY, int overlayBottomY, boolean postAddView) { 77346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final OverlayView overlay = mOverlayViews.get(adapterIndex); 77446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(adapterIndex); 7759875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang 77631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // save off the item's current top for later snap calculations 77731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang item.setTop(overlayTopY); 77831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 779c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // is the overlay visible and does it have non-zero height? 780c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY 781c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang && overlayTopY < mOffsetY + getHeight()) { 78246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang View overlayView = overlay != null ? overlay.view : null; 7837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // show and/or move overlay 7847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (overlayView == null) { 785bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein overlayView = addOverlayView(adapterIndex, postAddView); 7862fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein ViewCompat.setLayoutDirection(overlayView, ViewCompat.getLayoutDirection(this)); 7879875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 7889875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang item.markMeasurementValid(); 7899875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("show/measure overlay %d", adapterIndex); 790c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } else { 791c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("move overlay %d", adapterIndex); 7929875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang if (!item.isMeasurementValid()) { 793cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein item.rebindView(overlayView); 7949875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 7959875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang item.markMeasurementValid(); 7969875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex, 7979875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang overlayView.getHeight(), overlayView.getMeasuredHeight()); 7989875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 7997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 8009875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("laying out overlay %d with h=%d", adapterIndex, 8019875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang overlayView.getMeasuredHeight()); 802ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein final int childBottom = overlayTopY + overlayView.getMeasuredHeight(); 803ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein layoutOverlay(overlayView, overlayTopY, childBottom); 804ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderOverlayTop = (childBottom > mAdditionalBottomBorderOverlayTop) ? 805ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein childBottom : mAdditionalBottomBorderOverlayTop; 8067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } else { 8077bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // hide overlay 80846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (overlay != null) { 809c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("hide overlay %d", adapterIndex); 81046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang onOverlayScrolledOff(adapterIndex, overlay, overlayTopY, overlayBottomY); 811c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } else { 812c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("ignore non-visible overlay %d", adapterIndex); 8137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 814ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderOverlayTop = (overlayBottomY > mAdditionalBottomBorderOverlayTop) 815ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein ? overlayBottomY : mAdditionalBottomBorderOverlayTop; 8167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 81759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 81859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) { 81959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (mSnapIndex == -1) { 82059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = adapterIndex; 82159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } else if (adapterIndex > mSnapIndex) { 82259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = adapterIndex; 82359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 82459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 82559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 826b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 827b5078b287b1cec38817e342ff054ea901d199329Andy Huang 8287bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // layout an existing view 8297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // need its top offset into the conversation, its height, and the scroll offset 830c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private void layoutOverlay(View child, int childTop, int childBottom) { 8317bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int top = childTop - mOffsetY; 832c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang final int bottom = childBottom - mOffsetY; 83347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 83447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 83547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childLeft = getPaddingLeft() + lp.leftMargin; 83647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 83747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang child.layout(childLeft, top, childLeft + child.getMeasuredWidth(), bottom); 838b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 839b5078b287b1cec38817e342ff054ea901d199329Andy Huang 840bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private View addOverlayView(int adapterIndex, boolean postAddView) { 8417bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int itemType = mOverlayAdapter.getItemViewType(adapterIndex); 8427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final View convertView = mScrapViews.poll(itemType); 8437bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 844256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang final View view = mOverlayAdapter.getView(adapterIndex, convertView, this); 84546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews.put(adapterIndex, new OverlayView(view, itemType)); 846f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 847c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang if (convertView == view) { 848c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view); 8497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } else { 850c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view); 851f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 8527bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 853bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein if (view.getParent() == null) { 854bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein addViewInLayoutWrapper(view, postAddView); 855bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } else { 856bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein // Need to call postInvalidate since the view is being moved back on 857bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein // screen and we want to force it to draw the view. Without doing this, 858bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein // the view may not draw itself when it comes back on screen. 859bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein view.postInvalidate(); 860bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } 86159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 8627bdc3750454efe59617b7df945eadd7e59bee954Andy Huang return view; 863f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 864f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 865bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private void addViewInLayoutWrapper(View view, boolean postAddView) { 866bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein final AddViewRunnable addviewRunnable = new AddViewRunnable(view); 867bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein if (postAddView) { 868bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein post(addviewRunnable); 869bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } else { 870bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein addviewRunnable.run(); 871bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } 872888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 873888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 874bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein private class AddViewRunnable implements Runnable { 875bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein public final View mView; 876bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein 877bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein public AddViewRunnable(View view) { 878bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein mView = view; 879bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } 880bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein 881bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein @Override 882bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein public void run() { 883bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein final int index = BOTTOM_LAYER_VIEW_IDS.length; 884bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein addViewInLayout(mView, index, mView.getLayoutParams(), true /* preventRequestLayout */); 885bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein } 886bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein }; 887bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein 8888f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang private boolean isSnapEnabled() { 8898f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang if (mAccountController == null || mAccountController.getAccount() == null 8908f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang || mAccountController.getAccount().settings == null) { 8918f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang return true; 8928f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang } 8938f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang final int snap = mAccountController.getAccount().settings.snapHeaders; 8948f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang return snap == UIProvider.SnapHeaderValue.ALWAYS || 8958f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang (snap == UIProvider.SnapHeaderValue.PORTRAIT_ONLY && getResources() 8968f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT); 8978f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang } 8988f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 89931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // render and/or re-position snap header 90031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private void positionSnapHeader(int snapIndex) { 90131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang ConversationOverlayItem snapItem = null; 9028f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang if (mSnapEnabled && snapIndex != -1) { 90331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(snapIndex); 90431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (item.canBecomeSnapHeader()) { 90531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang snapItem = item; 90631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 90731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 90831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (snapItem == null) { 90931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.setVisibility(GONE); 91031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.unbind(); 91131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return; 91231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 91331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 91431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang snapItem.bindView(mSnapHeader, false /* measureOnly */); 91531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.setVisibility(VISIBLE); 91631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 9170a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang // overlap is negative or zero; bump the snap header upwards by that much 91831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang int overlap = 0; 91931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 92031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem next = findNextPushingOverlay(snapIndex + 1); 92131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (next != null) { 92231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang overlap = Math.min(0, next.getTop() - mSnapHeader.getHeight() - mOffsetY); 92331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 92431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // disable overlap drawing past a certain speed 92531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (overlap < 0) { 92631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final Float v = mVelocityTracker.getSmoothedVelocity(); 92731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (v != null && v > SNAP_HEADER_MAX_SCROLL_SPEED) { 92831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang overlap = 0; 92931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 93031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 93131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 9320a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang 9330a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang mSnapHeader.setTranslationY(overlap); 93431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 93531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 93631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // find the next header that can push the snap header up 93731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private ConversationOverlayItem findNextPushingOverlay(int start) { 93831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang for (int i = start, len = mOverlayAdapter.getCount(); i < len; i++) { 93931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem next = mOverlayAdapter.getItem(i); 94031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (next.canPushSnapHeader()) { 94131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return next; 94231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 94331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 94431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return null; 94531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 94631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 947c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang /** 948adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang * Prevents any layouts from happening until the next time 949adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang * {@link #onGeometryChange(OverlayPosition[])} is 950c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * called. Useful when you know the HTML spacer coordinates are inconsistent with adapter items. 951c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * <p> 952adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang * If you call this, you must ensure that a followup call to 953adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang * {@link #onGeometryChange(OverlayPosition[])} 954c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * is made later, when the HTML spacer coordinates are updated. 955c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang * 956c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang */ 957c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang public void invalidateSpacerGeometry() { 958adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang mOverlayPositions = null; 959c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } 960c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 961adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public void onGeometryChange(OverlayPosition[] overlayPositions) { 962adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang traceLayout("*** got overlay spacer positions:"); 963adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang for (OverlayPosition pos : overlayPositions) { 964adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang traceLayout("top=%d bottom=%d", pos.top, pos.bottom); 965f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 966f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 967adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang mOverlayPositions = overlayPositions; 968bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein positionOverlays(mOffsetY, false /* postAddView */); 969f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 970f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 971c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private void traceLayout(String msg, Object... params) { 972c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang if (mDisableLayoutTracing) { 973c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang return; 974c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } 9757f9ef60c5e42470814f6b4bb8ed771c17fdfea22Andy Huang LogUtils.d(TAG, msg, params); 976c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } 977c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 97846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private class AdapterObserver extends DataSetObserver { 97946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang @Override 98046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public void onChanged() { 98146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang onDataSetChanged(); 98246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 98346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 984f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang} 985