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; 2359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport android.graphics.Canvas; 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 65b5078b287b1cec38817e342ff054ea901d199329Andy Huang * ListView and AbsListView. 66f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 67f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */ 68f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpublic class ConversationContainer extends ViewGroup implements ScrollListener { 69632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang private static final String TAG = ConversationViewFragment.LAYOUT_TAG; 70bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 7147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final int[] BOTTOM_LAYER_VIEW_IDS = { 7214f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein R.id.webview, 7314f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein R.id.conversation_side_border_overlay 7447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang }; 7547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 7647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final int[] TOP_LAYER_VIEW_IDS = { 7747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang R.id.conversation_topmost_overlay 7847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang }; 7947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 8031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang /** 8131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang * Maximum scroll speed (in dp/sec) at which the snap header animation will draw. 8231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang * Anything faster than that, and drawing it creates visual artifacting (wagon-wheel effect). 8331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang */ 8431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private static final float SNAP_HEADER_MAX_SCROLL_SPEED = 600f; 8531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 868f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang private ConversationAccountController mAccountController; 877bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private ConversationViewAdapter mOverlayAdapter; 88adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private OverlayPosition[] mOverlayPositions; 89bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private ConversationWebView mWebView; 9059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private MessageHeaderView mSnapHeader; 9159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private View mTopMostOverlay; 9259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 9359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang /** 9459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * This is a hack. 9559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 9659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * <p>Without this hack enabled, very fast scrolling can sometimes cause the top-most layers 9759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * to skip being drawn for a frame or two. It happens specifically when overlay views are 9859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * attached or added, and WebView happens to draw (on its own) immediately afterwards. 9959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 10059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * <p>The workaround is to force an additional draw of the top-most overlay. Since the problem 10159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * only occurs when scrolling overlays are added, restrict the additional draw to only occur 10259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * if scrolling overlays were added since the last draw. 10359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang */ 10459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private boolean mAttachedOverlaySinceLastDraw; 105f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 10647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private final List<View> mNonScrollingChildren = Lists.newArrayList(); 10747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 108bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 10923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * Current document zoom scale per {@link WebView#getScale()}. This is the ratio of actual 11023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * screen pixels to logical WebView HTML pixels. We use it to convert from one to the other. 111bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 112bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private float mScale; 113120ea66184c31bdeb729274054ec913afe3872c1Andy Huang /** 114120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * Set to true upon receiving the first touch event. Used to help reject invalid WebView scale 115120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * values. 116120ea66184c31bdeb729274054ec913afe3872c1Andy Huang */ 117120ea66184c31bdeb729274054ec913afe3872c1Andy Huang private boolean mTouchInitialized; 118f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 119bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 120bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * System touch-slop distance per {@link ViewConfiguration#getScaledTouchSlop()}. 121bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 122bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private final int mTouchSlop; 123bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 124bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Current scroll position, as dictated by the background {@link WebView}. 125bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 126f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private int mOffsetY; 127bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 128bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Original pointer Y for slop calculation. 129bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 130bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private float mLastMotionY; 131bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 132bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Original pointer ID for slop calculation. 133bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 134bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private int mActivePointerId; 135bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 136bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Track pointer up/down state to know whether to send a make-up DOWN event to WebView. 137bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * WebView internal logic requires that a stream of {@link MotionEvent#ACTION_MOVE} events be 138bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * preceded by a {@link MotionEvent#ACTION_DOWN} event. 139bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 140bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private boolean mTouchIsDown = false; 141bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 142bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Remember if touch interception was triggered on a {@link MotionEvent#ACTION_POINTER_DOWN}, 143bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * so we can send a make-up event in {@link #onTouchEvent(MotionEvent)}. 144bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 145bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private boolean mMissedPointerDown; 146f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 148c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * A recycler that holds removed scrap views, organized by integer item view type. All views 149c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * in this data structure should be removed from their view parent prior to insertion. 1507bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 1517bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private final DequeMap<Integer, View> mScrapViews = new DequeMap<Integer, View>(); 152b5078b287b1cec38817e342ff054ea901d199329Andy Huang 153b5078b287b1cec38817e342ff054ea901d199329Andy Huang /** 15465fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * The current set of overlay views in the view hierarchy. Looking through this map is faster 15565fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * than traversing the view hierarchy. 156b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <p> 157b5078b287b1cec38817e342ff054ea901d199329Andy Huang * WebView sometimes notifies of scroll changes during a draw (or display list generation), when 158b5078b287b1cec38817e342ff054ea901d199329Andy Huang * it's not safe to detach view children because ViewGroup is in the middle of iterating over 15965fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * its child array. So we remove any child from this list immediately and queue up a task to 16065fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * detach it later. Since nobody other than the detach task references that view in the 16165fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang * meantime, we don't need any further checks or synchronization. 16246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * <p> 16346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * We keep {@link OverlayView} wrappers instead of bare views so that when it's time to dispose 16446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * of all views (on data set or adapter change), we can at least recycle them into the typed 16546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * scrap piles for later reuse. 166b5078b287b1cec38817e342ff054ea901d199329Andy Huang */ 16746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private final SparseArray<OverlayView> mOverlayViews; 168b5078b287b1cec38817e342ff054ea901d199329Andy Huang 169b5078b287b1cec38817e342ff054ea901d199329Andy Huang private int mWidthMeasureSpec; 170b5078b287b1cec38817e342ff054ea901d199329Andy Huang 171c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private boolean mDisableLayoutTracing; 172c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 17331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private final InputSmoother mVelocityTracker; 17431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 17546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private final DataSetObserver mAdapterObserver = new AdapterObserver(); 17646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 177cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang /** 17859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * The adapter index of the lowest overlay item that is above the top of the screen and reports 17959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through 18059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * {@link #positionOverlays(int, int)}. 18159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang * 18259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang */ 18359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang private int mSnapIndex; 18459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 1858f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang private boolean mSnapEnabled; 1868f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 187ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 188ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * A View that fills the remaining vertical space when the overlays do not take 189ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * up the entire container. Otherwise, a card-like bottom white space appears. 190ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 191888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private View mAdditionalBottomBorder; 192ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 193ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 194ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * A flag denoting whether the fake bottom border has been added to the container. 195ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 196888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private boolean mAdditionalBottomBorderAdded; 197888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 19859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang /** 199ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * An int containing the potential top value for the additional bottom border. 200ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * If this value is less than the height of the scroll container, the additional 201ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * bottom border will be drawn. 202ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 203ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein private int mAdditionalBottomBorderOverlayTop; 204ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 205ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 206cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * Child views of this container should implement this interface to be notified when they are 207cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * being detached. 208cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * 209cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang */ 210cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang public interface DetachListener { 211cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang /** 212cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * Called on a child view when it is removed from its parent as part of 213cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang * {@link ConversationContainer} view recycling. 214cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang */ 215cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang void onDetachedFromParent(); 216cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 217cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang 218adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public static class OverlayPosition { 219adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public final int top; 220adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public final int bottom; 221adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 222adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public OverlayPosition(int top, int bottom) { 223adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang this.top = top; 224adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang this.bottom = bottom; 225adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 226adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 227adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 22846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private static class OverlayView { 22946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public View view; 23046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int itemType; 23146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 23246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public OverlayView(View view, int itemType) { 23346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang this.view = view; 23446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang this.itemType = itemType; 23546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 23646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 23746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 238f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public ConversationContainer(Context c) { 239f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang this(c, null); 240f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 241f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 242f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public ConversationContainer(Context c, AttributeSet attrs) { 243f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang super(c, attrs); 244bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 24546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews = new SparseArray<OverlayView>(); 246b5078b287b1cec38817e342ff054ea901d199329Andy Huang 24731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mVelocityTracker = new InputSmoother(c); 24831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 249bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchSlop = ViewConfiguration.get(c).getScaledTouchSlop(); 250bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 251bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // Disabling event splitting fixes pinch-zoom when the first pointer goes down on the 252bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // WebView and the second pointer goes down on an overlay view. 253bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // Intercepting ACTION_POINTER_DOWN events allows pinch-zoom to work when the first pointer 254bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang // goes down on an overlay view. 255bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang setMotionEventSplittingEnabled(false); 256bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 257bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 258bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 259bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang protected void onFinishInflate() { 260bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang super.onFinishInflate(); 261bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 2625ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang mWebView = (ConversationWebView) findViewById(R.id.webview); 263bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mWebView.addScrollListener(this); 26447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 26559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mTopMostOverlay = findViewById(R.id.conversation_topmost_overlay); 26659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 26759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapHeader = (MessageHeaderView) findViewById(R.id.snap_header); 26859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapHeader.setSnappy(true); 26959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 27047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (int id : BOTTOM_LAYER_VIEW_IDS) { 27147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNonScrollingChildren.add(findViewById(id)); 27247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 27347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (int id : TOP_LAYER_VIEW_IDS) { 27447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNonScrollingChildren.add(findViewById(id)); 27547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 276f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 277f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 27859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang public MessageHeaderView getSnapHeader() { 27959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang return mSnapHeader; 28059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 28159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 2827bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void setOverlayAdapter(ConversationViewAdapter a) { 28346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (mOverlayAdapter != null) { 28446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver); 28546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang clearOverlays(); 28646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 287f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mOverlayAdapter = a; 28846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (mOverlayAdapter != null) { 28946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayAdapter.registerDataSetObserver(mAdapterObserver); 29046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 29151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 29251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 29351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang public Adapter getOverlayAdapter() { 29451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang return mOverlayAdapter; 295f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 296f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 2978f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang public void setAccountController(ConversationAccountController controller) { 2988f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mAccountController = controller; 2998f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 3008f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mSnapEnabled = isSnapEnabled(); 3018f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang } 3028f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 3036b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang /** 3046b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang * Re-bind any existing views that correspond to the given adapter positions. 3056b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang * 3066b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang */ 3076b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang public void onOverlayModelUpdate(List<Integer> affectedAdapterPositions) { 3086b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang for (Integer i : affectedAdapterPositions) { 3096b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(i); 3106b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang final OverlayView overlay = mOverlayViews.get(i); 3116b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang if (overlay != null && overlay.view != null && item != null) { 3126b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang item.onModelUpdated(overlay.view); 3136b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3144baafcbb39e20a81a1585270fc270a560ec8824dAndy Huang // update the snap header too, but only it's showing if the current item 3154baafcbb39e20a81a1585270fc270a560ec8824dAndy Huang if (i == mSnapIndex && mSnapHeader.isBoundTo(item)) { 3166b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang mSnapHeader.refresh(); 3176b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3186b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3196b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang } 3206b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang 321c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang /** 322c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * Return an overlay view for the given adapter item, or null if no matching view is currently 323c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * visible. This can happen as you scroll away from an overlay view. 324c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * 325c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang */ 326c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang public View getViewForItem(ConversationOverlayItem item) { 327c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang View result = null; 328c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang int adapterPos = -1; 329c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) { 330c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang if (mOverlayAdapter.getItem(i) == item) { 331c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang adapterPos = i; 332c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang break; 333c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 334c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 335c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang if (adapterPos != -1) { 336c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang final OverlayView overlay = mOverlayViews.get(adapterPos); 337c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang if (overlay != null) { 338c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang result = overlay.view; 339c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 340c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 341c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang return result; 342c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 343c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang 34446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void clearOverlays() { 34546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang for (int i = 0, len = mOverlayViews.size(); i < len; i++) { 34646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang detachOverlay(mOverlayViews.valueAt(i)); 34746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 34846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews.clear(); 34946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 35046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 35146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void onDataSetChanged() { 3522a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // Recycle all views and re-bind them according to the current set of spacer coordinates. 3532a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // This essentially resets the overlay views and re-renders them. 3542a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // It's fast enough that it's okay to re-do all views on any small change, as long as 3552a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // the change isn't too frequent (< ~1Hz). 3562a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang 35746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang clearOverlays(); 3582a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // also unbind the snap header view, so this "reset" causes the snap header to re-create 3592a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang // its view, just like all other headers 3602a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang mSnapHeader.unbind(); 361ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 362ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein // also clear out the additional bottom border 363ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein removeViewInLayout(mAdditionalBottomBorder); 364ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderAdded = false; 365ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein 3668f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mSnapEnabled = isSnapEnabled(); 36728b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang positionOverlays(0, mOffsetY); 36846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 36946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 370bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang private void forwardFakeMotionEvent(MotionEvent original, int newAction) { 371bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang MotionEvent newEvent = MotionEvent.obtain(original); 372bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.setAction(newAction); 373bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mWebView.onTouchEvent(newEvent); 374bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang LogUtils.v(TAG, "in Container.OnTouch. fake: action=%d x/y=%f/%f pointers=%d", 375bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.getActionMasked(), newEvent.getX(), newEvent.getY(), 376bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang newEvent.getPointerCount()); 377bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 378bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 379bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang /** 380bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang * Touch slop code was copied from {@link ScrollView#onInterceptTouchEvent(MotionEvent)}. 381bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang */ 382bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 383bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang public boolean onInterceptTouchEvent(MotionEvent ev) { 384120ea66184c31bdeb729274054ec913afe3872c1Andy Huang 385120ea66184c31bdeb729274054ec913afe3872c1Andy Huang if (!mTouchInitialized) { 386120ea66184c31bdeb729274054ec913afe3872c1Andy Huang mTouchInitialized = true; 387120ea66184c31bdeb729274054ec913afe3872c1Andy Huang } 388120ea66184c31bdeb729274054ec913afe3872c1Andy Huang 389632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang // no interception when WebView handles the first DOWN 390632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (mWebView.isHandlingTouch()) { 391632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang return false; 392632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 393632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 394bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang boolean intercept = false; 395bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang switch (ev.getActionMasked()) { 396bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_POINTER_DOWN: 397632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "Container is intercepting non-primary touch!"); 398bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang intercept = true; 399bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mMissedPointerDown = true; 400632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang requestDisallowInterceptTouchEvent(true); 401bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 402bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 403bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_DOWN: 404bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mLastMotionY = ev.getY(); 405bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mActivePointerId = ev.getPointerId(0); 406bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 407bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 408bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang case MotionEvent.ACTION_MOVE: 409bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int pointerIndex = ev.findPointerIndex(mActivePointerId); 410bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final float y = ev.getY(pointerIndex); 411bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int yDiff = (int) Math.abs(y - mLastMotionY); 412bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang if (yDiff > mTouchSlop) { 413bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mLastMotionY = y; 414bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang intercept = true; 415bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 416bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang break; 417bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 418bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 419632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// LogUtils.v(TAG, "in Container.InterceptTouch. action=%d x/y=%f/%f pointers=%d result=%s", 420632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount(), intercept); 421bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang return intercept; 422bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 423bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 424bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang @Override 425bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang public boolean onTouchEvent(MotionEvent ev) { 426bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final int action = ev.getActionMasked(); 427bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 428632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 429bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchIsDown = false; 430bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } else if (!mTouchIsDown && 431bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_DOWN)) { 432bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 433bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang forwardFakeMotionEvent(ev, MotionEvent.ACTION_DOWN); 434bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang if (mMissedPointerDown) { 435bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang forwardFakeMotionEvent(ev, MotionEvent.ACTION_POINTER_DOWN); 436bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mMissedPointerDown = false; 437bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 438bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 439bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang mTouchIsDown = true; 440bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang } 441bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 442bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang final boolean webViewResult = mWebView.onTouchEvent(ev); 443bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang 444632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// LogUtils.v(TAG, "in Container.OnTouch. action=%d x/y=%f/%f pointers=%d", 445632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang// ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount()); 446bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang return webViewResult; 447f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 448f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 449f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 450b5078b287b1cec38817e342ff054ea901d199329Andy Huang public void onNotifierScroll(final int x, final int y) { 45131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mVelocityTracker.onInput(y); 452c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mDisableLayoutTracing = true; 4537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang positionOverlays(x, y); 454c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mDisableLayoutTracing = false; 455b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 456b5078b287b1cec38817e342ff054ea901d199329Andy Huang 4577bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private void positionOverlays(int x, int y) { 458f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mOffsetY = y; 45907f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang 46007f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang /* 461120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * The scale value that WebView reports is inaccurate when measured during WebView 462120ea66184c31bdeb729274054ec913afe3872c1Andy Huang * initialization. This bug is present in ICS, so to work around it, we ignore all 46323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * reported values and use a calculated expected value from ConversationWebView instead. 46423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * Only when the user actually begins to touch the view (to, say, begin a zoom) do we begin 46523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * to pay attention to WebView-reported scale values. 46607f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang */ 467120ea66184c31bdeb729274054ec913afe3872c1Andy Huang if (mTouchInitialized) { 46807f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang mScale = mWebView.getScale(); 46923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang } else if (mScale == 0) { 47023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mScale = mWebView.getInitialScale(); 47107f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang } 472c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(), 473c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mScale); 474f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 475adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (mOverlayPositions == null || mOverlayAdapter == null) { 47651067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang return; 47751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 47851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 479b5078b287b1cec38817e342ff054ea901d199329Andy Huang // recycle scrolled-off views and add newly visible views 480b5078b287b1cec38817e342ff054ea901d199329Andy Huang 4817bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // we want consecutive spacers/overlays to stack towards the bottom 4827bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // so iterate from the bottom of the conversation up 4837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // starting with the last spacer bottom and the last adapter item, position adapter views 4847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // in a single stack until you encounter a non-contiguous expanded message header, 4857bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // then decrement to the next spacer. 486b5078b287b1cec38817e342ff054ea901d199329Andy Huang 487adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayPositions.length, 488c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mOverlayAdapter.getCount()); 489c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 49059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = -1; 491ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderOverlayTop = 0; 49259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 493adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang int adapterLoopIndex = mOverlayAdapter.getCount() - 1; 494adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang int spacerIndex = mOverlayPositions.length - 1; 495adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang while (spacerIndex >= 0 && adapterLoopIndex >= 0) { 496adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 497adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int spacerTop = getOverlayTop(spacerIndex); 498adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int spacerBottom = getOverlayBottom(spacerIndex); 499adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 500adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final boolean flip; 501adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int flipOffset; 502adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int forceGravity; 503adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // flip direction from bottom->top to top->bottom traversal on the very first spacer 504adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // to facilitate top-aligned headers at spacer index = 0 505adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (spacerIndex == 0) { 506adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flip = true; 507adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flipOffset = adapterLoopIndex; 508adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity = Gravity.TOP; 509adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } else { 510adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flip = false; 511adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang flipOffset = 0; 512adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity = Gravity.NO_GRAVITY; 513adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 514c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 515adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang int adapterIndex = flip ? flipOffset - adapterLoopIndex : adapterLoopIndex; 5167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 5177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // always place at least one overlay per spacer 51846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang ConversationOverlayItem adapterItem = mOverlayAdapter.getItem(adapterIndex); 5197bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 520adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang OverlayPosition itemPos = calculatePosition(adapterItem, spacerTop, spacerBottom, 521adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity); 5227bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 523c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex, 524adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang itemPos.top, itemPos.bottom, adapterItem); 525adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang positionOverlay(adapterIndex, itemPos.top, itemPos.bottom); 5267bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 527adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // and keep stacking overlays unconditionally if we are on the first spacer, or as long 528adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // as overlays are contiguous 529adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang while (--adapterLoopIndex >= 0) { 530adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang adapterIndex = flip ? flipOffset - adapterLoopIndex : adapterLoopIndex; 5317bdc3750454efe59617b7df945eadd7e59bee954Andy Huang adapterItem = mOverlayAdapter.getItem(adapterIndex); 532adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (spacerIndex > 0 && !adapterItem.isContiguous()) { 5337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // advance to the next spacer, but stay on this adapter item 5347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang break; 535b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 5367bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 537adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // place this overlay in the region of the spacer above or below the last item, 538adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // depending on direction of iteration 539adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int regionTop = flip ? itemPos.bottom : spacerTop; 540adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int regionBottom = flip ? spacerBottom : itemPos.top; 541adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang itemPos = calculatePosition(adapterItem, regionTop, regionBottom, forceGravity); 5427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 543c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, 544adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang adapterIndex, itemPos.top, itemPos.bottom, adapterItem); 545adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang positionOverlay(adapterIndex, itemPos.top, itemPos.bottom); 546b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 547c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 548c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang spacerIndex--; 549b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 55059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 55131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang positionSnapHeader(mSnapIndex); 552ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein positionAdditionalBottomBorder(); 553b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 554b5078b287b1cec38817e342ff054ea901d199329Andy Huang 555ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein /** 556ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * Adds an additional bottom border to the overlay views in case 557ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein * the overlays do not fill the entire screen. 558ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein */ 559888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private void positionAdditionalBottomBorder() { 560ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein final int lastBottom = mAdditionalBottomBorderOverlayTop; 56183b460bf42d48bd8ab18ee135fab181242d13b9aAndrew Sapperstein final int containerHeight = webPxToScreenPx(mWebView.getContentHeight()); 562888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein final int speculativeHeight = containerHeight - lastBottom; 563888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (speculativeHeight > 0) { 564888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (mAdditionalBottomBorder == null) { 565888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAdditionalBottomBorder = mOverlayAdapter.getLayoutInflater().inflate( 566888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein R.layout.fake_bottom_border, this, false); 567888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 568888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 569888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein setAdditionalBottomBorderHeight(speculativeHeight); 570888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 571888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (!mAdditionalBottomBorderAdded) { 572888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein addViewInLayoutWrapper(mAdditionalBottomBorder); 573888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAdditionalBottomBorderAdded = true; 574888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 575888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 576888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein measureOverlayView(mAdditionalBottomBorder); 577888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein layoutOverlay(mAdditionalBottomBorder, lastBottom, containerHeight); 578888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } else { 579888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein if (mAdditionalBottomBorder != null && mAdditionalBottomBorderAdded) { 580ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein removeViewInLayout(mAdditionalBottomBorder); 581ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderAdded = false; 582888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 583888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 584888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 585888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 586888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private void setAdditionalBottomBorderHeight(int speculativeHeight) { 587888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein LayoutParams params = mAdditionalBottomBorder.getLayoutParams(); 588888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein params.height = speculativeHeight; 589888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAdditionalBottomBorder.setLayoutParams(params); 590888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 591888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 592ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy private static OverlayPosition calculatePosition(final ConversationOverlayItem adapterItem, 593ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy final int withinTop, final int withinBottom, final int forceGravity) { 594adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (adapterItem.getHeight() == 0) { 595adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // "place" invisible items at the bottom of their region to stay consistent with the 596adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // stacking algorithm in positionOverlays(), unless gravity is forced to the top 597adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int y = (forceGravity == Gravity.TOP) ? withinTop : withinBottom; 598adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return new OverlayPosition(y, y); 599adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 600adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 601adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int v = ((forceGravity != Gravity.NO_GRAVITY) ? 602adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang forceGravity : adapterItem.getGravity()) & Gravity.VERTICAL_GRAVITY_MASK; 603adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang switch (v) { 604adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang case Gravity.BOTTOM: 605adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return new OverlayPosition(withinBottom - adapterItem.getHeight(), withinBottom); 606adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang case Gravity.TOP: 607adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return new OverlayPosition(withinTop, withinTop + adapterItem.getHeight()); 608adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang default: 609adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang throw new UnsupportedOperationException("unsupported gravity: " + v); 610adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 611adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 612adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 613b5078b287b1cec38817e342ff054ea901d199329Andy Huang /** 6149875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * Executes a measure pass over the specified child overlay view and returns the measured 6159875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * height. The measurement uses whatever the current container's width measure spec is. 6169875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * This method ignores view visibility and returns the height that the view would be if visible. 6179875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * 6189875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * @param overlayView an overlay view to measure. does not actually have to be attached yet. 6199875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * @return height that the view would be if it was visible 620b5078b287b1cec38817e342ff054ea901d199329Andy Huang */ 6219875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang public int measureOverlay(View overlayView) { 6229875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 6239875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang return overlayView.getMeasuredHeight(); 6249875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 625c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 6269875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang /** 6279875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang * Copied/stolen from {@link ListView}. 6289875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang */ 6299875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang private void measureOverlayView(View child) { 63047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams(); 631b5078b287b1cec38817e342ff054ea901d199329Andy Huang if (p == null) { 63247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang p = (MarginLayoutParams) generateDefaultLayoutParams(); 633b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 634b5078b287b1cec38817e342ff054ea901d199329Andy Huang 635b5078b287b1cec38817e342ff054ea901d199329Andy Huang int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 63647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang getPaddingLeft() + getPaddingRight() + p.leftMargin + p.rightMargin, p.width); 637b5078b287b1cec38817e342ff054ea901d199329Andy Huang int lpHeight = p.height; 638b5078b287b1cec38817e342ff054ea901d199329Andy Huang int childHeightSpec; 639b5078b287b1cec38817e342ff054ea901d199329Andy Huang if (lpHeight > 0) { 640b5078b287b1cec38817e342ff054ea901d199329Andy Huang childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); 641b5078b287b1cec38817e342ff054ea901d199329Andy Huang } else { 642b5078b287b1cec38817e342ff054ea901d199329Andy Huang childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 643b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 644b5078b287b1cec38817e342ff054ea901d199329Andy Huang child.measure(childWidthSpec, childHeightSpec); 645b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 646b5078b287b1cec38817e342ff054ea901d199329Andy Huang 64746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void onOverlayScrolledOff(final int adapterIndex, final OverlayView overlay, 64846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int overlayTop, int overlayBottom) { 64965fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang // detach the view asynchronously, as scroll notification can happen during a draw, when 65065fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang // it's not safe to remove children 651b5078b287b1cec38817e342ff054ea901d199329Andy Huang 65265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang // but immediately remove this view from the view set so future lookups don't find it 65365fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang mOverlayViews.remove(adapterIndex); 654b5078b287b1cec38817e342ff054ea901d199329Andy Huang 655b5078b287b1cec38817e342ff054ea901d199329Andy Huang post(new Runnable() { 656b5078b287b1cec38817e342ff054ea901d199329Andy Huang @Override 657b5078b287b1cec38817e342ff054ea901d199329Andy Huang public void run() { 65846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang detachOverlay(overlay); 659b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 660b5078b287b1cec38817e342ff054ea901d199329Andy Huang }); 661b5078b287b1cec38817e342ff054ea901d199329Andy Huang 662b5078b287b1cec38817e342ff054ea901d199329Andy Huang // push it out of view immediately 663b5078b287b1cec38817e342ff054ea901d199329Andy Huang // otherwise this scrolled-off header will continue to draw until the runnable runs 66446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang layoutOverlay(overlay.view, overlayTop, overlayBottom); 6657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 6667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 6678fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang /** 668c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang * Returns an existing scrap view, if available. The view will already be removed from the view 6698fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang * hierarchy. This method will not remove the view from the scrap heap. 6708fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang * 6718fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang */ 6727bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public View getScrapView(int type) { 6738fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang return mScrapViews.peek(type); 6747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 6757bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 6767bdc3750454efe59617b7df945eadd7e59bee954Andy Huang public void addScrapView(int type, View v) { 6777bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mScrapViews.add(type, v); 678b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 679b5078b287b1cec38817e342ff054ea901d199329Andy Huang 68046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void detachOverlay(OverlayView overlay) { 681c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang // Prefer removeViewInLayout over removeView. The typical followup layout pass is unneeded 682c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang // because removing overlay views doesn't affect overall layout. 683c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang removeViewInLayout(overlay.view); 68446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mScrapViews.add(overlay.itemType, overlay.view); 68546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (overlay.view instanceof DetachListener) { 68646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang ((DetachListener) overlay.view).onDetachedFromParent(); 687cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 688cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang } 689cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang 690cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang @Override 691f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 692f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang super.onMeasure(widthMeasureSpec, heightMeasureSpec); 693632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 694632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%s/%s", 695632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang MeasureSpec.toString(widthMeasureSpec), 696632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang MeasureSpec.toString(heightMeasureSpec)); 697632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 698f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 69947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (View nonScrollingChild : mNonScrollingChildren) { 70047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (nonScrollingChild.getVisibility() != GONE) { 70147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang measureChildWithMargins(nonScrollingChild, widthMeasureSpec, 0 /* widthUsed */, 70247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang heightMeasureSpec, 0 /* heightUsed */); 70347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 7043233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 705b5078b287b1cec38817e342ff054ea901d199329Andy Huang mWidthMeasureSpec = widthMeasureSpec; 7067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 7079875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang // onLayout will re-measure and re-position overlays for the new container size, but the 7089875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang // spacer offsets would still need to be updated to have them draw at their new locations. 709f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 710f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 711f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 712f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang protected void onLayout(boolean changed, int l, int t, int r, int b) { 713632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang LogUtils.d(TAG, "*** IN header container onLayout"); 714f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 71547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang for (View nonScrollingChild : mNonScrollingChildren) { 71647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (nonScrollingChild.getVisibility() != GONE) { 71747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int w = nonScrollingChild.getMeasuredWidth(); 71847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int h = nonScrollingChild.getMeasuredHeight(); 71947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 72047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final MarginLayoutParams lp = 72147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang (MarginLayoutParams) nonScrollingChild.getLayoutParams(); 72247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 72347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childLeft = lp.leftMargin; 72447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childTop = lp.topMargin; 72547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang nonScrollingChild.layout(childLeft, childTop, childLeft + w, childTop + h); 72647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 72747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 7285ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 729e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang if (mOverlayAdapter != null) { 730e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang // being in a layout pass means overlay children may require measurement, 731e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang // so invalidate them 732e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) { 733e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang mOverlayAdapter.getItem(i).invalidateMeasurement(); 734e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang } 7358778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang } 7368778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang 7378778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang positionOverlays(0, mOffsetY); 7389875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 7399875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang 74047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 74159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang protected void dispatchDraw(Canvas canvas) { 74259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang super.dispatchDraw(canvas); 74359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 74459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (mAttachedOverlaySinceLastDraw) { 74559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang drawChild(canvas, mTopMostOverlay, getDrawingTime()); 74659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mAttachedOverlaySinceLastDraw = false; 74759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 74859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 74959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 75059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang @Override 75147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected LayoutParams generateDefaultLayoutParams() { 75247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 75347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 75447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 75547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 75647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public LayoutParams generateLayoutParams(AttributeSet attrs) { 75747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(getContext(), attrs); 75847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 75947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 76047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 76147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 76247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return new MarginLayoutParams(p); 76347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 76447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 76547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang @Override 76647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 76747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return p instanceof MarginLayoutParams; 76847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 76947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 770adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private int getOverlayTop(int spacerIndex) { 771adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return webPxToScreenPx(mOverlayPositions[spacerIndex].top); 772adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 773adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 7747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private int getOverlayBottom(int spacerIndex) { 775adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return webPxToScreenPx(mOverlayPositions[spacerIndex].bottom); 776adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 777adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 778adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private int webPxToScreenPx(int webPx) { 7797bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // TODO: round or truncate? 780adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // TODO: refactor and unify with ConversationWebView.webPxToScreenPx() 781adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return (int) (webPx * mScale); 782b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 783b5078b287b1cec38817e342ff054ea901d199329Andy Huang 7847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private void positionOverlay(int adapterIndex, int overlayTopY, int overlayBottomY) { 78546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final OverlayView overlay = mOverlayViews.get(adapterIndex); 78646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(adapterIndex); 7879875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang 78831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // save off the item's current top for later snap calculations 78931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang item.setTop(overlayTopY); 79031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 791c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // is the overlay visible and does it have non-zero height? 792c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY 793c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang && overlayTopY < mOffsetY + getHeight()) { 79446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang View overlayView = overlay != null ? overlay.view : null; 7957bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // show and/or move overlay 7967bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (overlayView == null) { 7977bdc3750454efe59617b7df945eadd7e59bee954Andy Huang overlayView = addOverlayView(adapterIndex); 7989875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 7999875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang item.markMeasurementValid(); 8009875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("show/measure overlay %d", adapterIndex); 801c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } else { 802c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("move overlay %d", adapterIndex); 8039875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang if (!item.isMeasurementValid()) { 804cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein item.rebindView(overlayView); 8059875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang measureOverlayView(overlayView); 8069875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang item.markMeasurementValid(); 8079875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex, 8089875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang overlayView.getHeight(), overlayView.getMeasuredHeight()); 8099875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang } 8107bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 8119875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang traceLayout("laying out overlay %d with h=%d", adapterIndex, 8129875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang overlayView.getMeasuredHeight()); 813ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein final int childBottom = overlayTopY + overlayView.getMeasuredHeight(); 814ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein layoutOverlay(overlayView, overlayTopY, childBottom); 815ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderOverlayTop = (childBottom > mAdditionalBottomBorderOverlayTop) ? 816ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein childBottom : mAdditionalBottomBorderOverlayTop; 8177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } else { 8187bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // hide overlay 81946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (overlay != null) { 820c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("hide overlay %d", adapterIndex); 82146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang onOverlayScrolledOff(adapterIndex, overlay, overlayTopY, overlayBottomY); 822c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang } else { 823c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang traceLayout("ignore non-visible overlay %d", adapterIndex); 8247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 825ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein mAdditionalBottomBorderOverlayTop = (overlayBottomY > mAdditionalBottomBorderOverlayTop) 826ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein ? overlayBottomY : mAdditionalBottomBorderOverlayTop; 8277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 82859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 82959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) { 83059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang if (mSnapIndex == -1) { 83159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = adapterIndex; 83259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } else if (adapterIndex > mSnapIndex) { 83359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang mSnapIndex = adapterIndex; 83459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 83559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang } 83659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 837b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 838b5078b287b1cec38817e342ff054ea901d199329Andy Huang 8397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // layout an existing view 8407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // need its top offset into the conversation, its height, and the scroll offset 841c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang private void layoutOverlay(View child, int childTop, int childBottom) { 8427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int top = childTop - mOffsetY; 843c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang final int bottom = childBottom - mOffsetY; 84447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 84547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 84647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final int childLeft = getPaddingLeft() + lp.leftMargin; 84747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 84847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang child.layout(childLeft, top, childLeft + child.getMeasuredWidth(), bottom); 849b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 850b5078b287b1cec38817e342ff054ea901d199329Andy Huang 8517bdc3750454efe59617b7df945eadd7e59bee954Andy Huang private View addOverlayView(int adapterIndex) { 8527bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int itemType = mOverlayAdapter.getItemViewType(adapterIndex); 8537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final View convertView = mScrapViews.poll(itemType); 8547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 855256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang final View view = mOverlayAdapter.getView(adapterIndex, convertView, this); 85646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mOverlayViews.put(adapterIndex, new OverlayView(view, itemType)); 857f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 858c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang if (convertView == view) { 859c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view); 8607bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } else { 861c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view); 862f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 8637bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 864888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein addViewInLayoutWrapper(view); 86559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 8667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang return view; 867f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 868f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 869888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein private void addViewInLayoutWrapper(View view) { 870888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein final int index = BOTTOM_LAYER_VIEW_IDS.length; 871888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein addViewInLayout(view, index, view.getLayoutParams(), true /* preventRequestLayout */); 872888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein mAttachedOverlaySinceLastDraw = true; 873888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein } 874888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein 8758f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang private boolean isSnapEnabled() { 8768f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang if (mAccountController == null || mAccountController.getAccount() == null 8778f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang || mAccountController.getAccount().settings == null) { 8788f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang return true; 8798f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang } 8808f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang final int snap = mAccountController.getAccount().settings.snapHeaders; 8818f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang return snap == UIProvider.SnapHeaderValue.ALWAYS || 8828f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang (snap == UIProvider.SnapHeaderValue.PORTRAIT_ONLY && getResources() 8838f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT); 8848f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang } 8858f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang 88631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // render and/or re-position snap header 88731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private void positionSnapHeader(int snapIndex) { 88831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang ConversationOverlayItem snapItem = null; 8898f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang if (mSnapEnabled && snapIndex != -1) { 89031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem item = mOverlayAdapter.getItem(snapIndex); 89131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (item.canBecomeSnapHeader()) { 89231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang snapItem = item; 89331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 89431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 89531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (snapItem == null) { 89631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.setVisibility(GONE); 89731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.unbind(); 89831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return; 89931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 90031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 90131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang snapItem.bindView(mSnapHeader, false /* measureOnly */); 90231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang mSnapHeader.setVisibility(VISIBLE); 90331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 9040a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang // overlap is negative or zero; bump the snap header upwards by that much 90531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang int overlap = 0; 90631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 90731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem next = findNextPushingOverlay(snapIndex + 1); 90831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (next != null) { 90931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang overlap = Math.min(0, next.getTop() - mSnapHeader.getHeight() - mOffsetY); 91031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 91131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // disable overlap drawing past a certain speed 91231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (overlap < 0) { 91331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final Float v = mVelocityTracker.getSmoothedVelocity(); 91431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (v != null && v > SNAP_HEADER_MAX_SCROLL_SPEED) { 91531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang overlap = 0; 91631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 91731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 91831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 9190a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang 9200a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang mSnapHeader.setTranslationY(overlap); 92131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 92231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 92331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang // find the next header that can push the snap header up 92431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang private ConversationOverlayItem findNextPushingOverlay(int start) { 92531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang for (int i = start, len = mOverlayAdapter.getCount(); i < len; i++) { 92631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang final ConversationOverlayItem next = mOverlayAdapter.getItem(i); 92731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang if (next.canPushSnapHeader()) { 92831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return next; 92931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 93031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 93131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang return null; 93231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang } 93331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang 934c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang /** 935c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * Return a collection of all currently visible overlay views, in no particular order. 936c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * Please don't mess with them too badly (e.g. remove from parent). 937c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang * 938c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang */ 939c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang public List<View> getOverlayViews() { 940c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang final List<View> views = Lists.newArrayList(); 941c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang for (int i = 0, len = mOverlayViews.size(); i < len; i++) { 942c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang views.add(mOverlayViews.valueAt(i).view); 943c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 944c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang return views; 945c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang } 946c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang 947c1fb9a9c2730178105977fca629e80951bfc3cdcAndy 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; 9687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang positionOverlays(0, mOffsetY); 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