Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 Google Inc. |
| 3 | * Licensed to The Android Open Source Project. |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | */ |
| 17 | |
Andy Huang | 5ff6374 | 2012-03-16 20:30:23 -0700 | [diff] [blame] | 18 | package com.android.mail.browse; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 19 | |
| 20 | import android.content.Context; |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 21 | import android.database.DataSetObserver; |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 22 | import android.graphics.Canvas; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 23 | import android.util.AttributeSet; |
Andy Huang | 65fe28f | 2012-04-06 18:08:53 -0700 | [diff] [blame] | 24 | import android.util.SparseArray; |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 25 | import android.view.MotionEvent; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 26 | import android.view.View; |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 27 | import android.view.ViewConfiguration; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 28 | import android.view.ViewGroup; |
| 29 | import android.webkit.WebView; |
| 30 | import android.widget.Adapter; |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 31 | import android.widget.ListView; |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 32 | import android.widget.ScrollView; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 33 | |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 34 | import com.android.mail.R; |
Andy Huang | 5ff6374 | 2012-03-16 20:30:23 -0700 | [diff] [blame] | 35 | import com.android.mail.browse.ScrollNotifier.ScrollListener; |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 36 | import com.android.mail.ui.ConversationViewFragment; |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 37 | import com.android.mail.utils.DequeMap; |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 38 | import com.android.mail.utils.InputSmoother; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 39 | import com.android.mail.utils.LogUtils; |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 40 | import com.google.common.collect.Lists; |
| 41 | |
| 42 | import java.util.List; |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 43 | |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 44 | /** |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 45 | * A specialized ViewGroup container for conversation view. It is designed to contain a single |
| 46 | * {@link WebView} and a number of overlay views that draw on top of the WebView. In the Mail app, |
| 47 | * the WebView contains all HTML message bodies in a conversation, and the overlay views are the |
| 48 | * subject view, message headers, and attachment views. The WebView does all scroll handling, and |
| 49 | * this container manages scrolling of the overlay views so that they move in tandem. |
| 50 | * |
| 51 | * <h5>INPUT HANDLING</h5> |
| 52 | * Placing the WebView in the same container as the overlay views means we don't have to do a lot of |
| 53 | * manual manipulation of touch events. We do have a |
| 54 | * {@link #forwardFakeMotionEvent(MotionEvent, int)} method that deals with one WebView |
| 55 | * idiosyncrasy: it doesn't react well when touch MOVE events stream in without a preceding DOWN. |
| 56 | * |
| 57 | * <h5>VIEW RECYCLING</h5> |
| 58 | * Normally, it would make sense to put all overlay views into a {@link ListView}. But this view |
| 59 | * sandwich has unique characteristics: the list items are scrolled based on an external controller, |
| 60 | * and we happen to know all of the overlay positions up front. So it didn't make sense to shoehorn |
| 61 | * a ListView in and instead, we rolled our own view recycler by borrowing key details from |
| 62 | * ListView and AbsListView. |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 63 | * |
| 64 | */ |
| 65 | public class ConversationContainer extends ViewGroup implements ScrollListener { |
| 66 | |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 67 | private static final String TAG = ConversationViewFragment.LAYOUT_TAG; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 68 | |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 69 | private static final int[] BOTTOM_LAYER_VIEW_IDS = { |
| 70 | R.id.webview |
| 71 | }; |
| 72 | |
| 73 | private static final int[] TOP_LAYER_VIEW_IDS = { |
| 74 | R.id.conversation_topmost_overlay |
| 75 | }; |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 76 | |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 77 | /** |
| 78 | * Maximum scroll speed (in dp/sec) at which the snap header animation will draw. |
| 79 | * Anything faster than that, and drawing it creates visual artifacting (wagon-wheel effect). |
| 80 | */ |
| 81 | private static final float SNAP_HEADER_MAX_SCROLL_SPEED = 600f; |
| 82 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 83 | private ConversationViewAdapter mOverlayAdapter; |
Andy Huang | 2ffaeab | 2012-02-27 19:43:46 -0800 | [diff] [blame] | 84 | private int[] mOverlayBottoms; |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 85 | private ConversationWebView mWebView; |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 86 | private MessageHeaderView mSnapHeader; |
| 87 | private View mTopMostOverlay; |
| 88 | |
| 89 | /** |
| 90 | * This is a hack. |
| 91 | * |
| 92 | * <p>Without this hack enabled, very fast scrolling can sometimes cause the top-most layers |
| 93 | * to skip being drawn for a frame or two. It happens specifically when overlay views are |
| 94 | * attached or added, and WebView happens to draw (on its own) immediately afterwards. |
| 95 | * |
| 96 | * <p>The workaround is to force an additional draw of the top-most overlay. Since the problem |
| 97 | * only occurs when scrolling overlays are added, restrict the additional draw to only occur |
| 98 | * if scrolling overlays were added since the last draw. |
| 99 | */ |
| 100 | private boolean mAttachedOverlaySinceLastDraw; |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 101 | |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 102 | private final List<View> mNonScrollingChildren = Lists.newArrayList(); |
| 103 | |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 104 | /** |
Andy Huang | 2301470 | 2012-07-09 12:50:36 -0700 | [diff] [blame] | 105 | * Current document zoom scale per {@link WebView#getScale()}. This is the ratio of actual |
| 106 | * screen pixels to logical WebView HTML pixels. We use it to convert from one to the other. |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 107 | */ |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 108 | private float mScale; |
Andy Huang | 120ea66 | 2012-03-27 23:15:12 -0700 | [diff] [blame] | 109 | /** |
| 110 | * Set to true upon receiving the first touch event. Used to help reject invalid WebView scale |
| 111 | * values. |
| 112 | */ |
| 113 | private boolean mTouchInitialized; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 114 | |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 115 | /** |
| 116 | * System touch-slop distance per {@link ViewConfiguration#getScaledTouchSlop()}. |
| 117 | */ |
| 118 | private final int mTouchSlop; |
| 119 | /** |
| 120 | * Current scroll position, as dictated by the background {@link WebView}. |
| 121 | */ |
| 122 | private int mOffsetY; |
| 123 | /** |
| 124 | * Original pointer Y for slop calculation. |
| 125 | */ |
| 126 | private float mLastMotionY; |
| 127 | /** |
| 128 | * Original pointer ID for slop calculation. |
| 129 | */ |
| 130 | private int mActivePointerId; |
| 131 | /** |
| 132 | * Track pointer up/down state to know whether to send a make-up DOWN event to WebView. |
| 133 | * WebView internal logic requires that a stream of {@link MotionEvent#ACTION_MOVE} events be |
| 134 | * preceded by a {@link MotionEvent#ACTION_DOWN} event. |
| 135 | */ |
| 136 | private boolean mTouchIsDown = false; |
| 137 | /** |
| 138 | * Remember if touch interception was triggered on a {@link MotionEvent#ACTION_POINTER_DOWN}, |
| 139 | * so we can send a make-up event in {@link #onTouchEvent(MotionEvent)}. |
| 140 | */ |
| 141 | private boolean mMissedPointerDown; |
| 142 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 143 | /** |
| 144 | * A recycler for scrap views, organized by integer item view type. |
| 145 | */ |
| 146 | private final DequeMap<Integer, View> mScrapViews = new DequeMap<Integer, View>(); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 147 | |
| 148 | /** |
Andy Huang | 65fe28f | 2012-04-06 18:08:53 -0700 | [diff] [blame] | 149 | * The current set of overlay views in the view hierarchy. Looking through this map is faster |
| 150 | * than traversing the view hierarchy. |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 151 | * <p> |
| 152 | * WebView sometimes notifies of scroll changes during a draw (or display list generation), when |
| 153 | * it's not safe to detach view children because ViewGroup is in the middle of iterating over |
Andy Huang | 65fe28f | 2012-04-06 18:08:53 -0700 | [diff] [blame] | 154 | * its child array. So we remove any child from this list immediately and queue up a task to |
| 155 | * detach it later. Since nobody other than the detach task references that view in the |
| 156 | * meantime, we don't need any further checks or synchronization. |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 157 | * <p> |
| 158 | * We keep {@link OverlayView} wrappers instead of bare views so that when it's time to dispose |
| 159 | * of all views (on data set or adapter change), we can at least recycle them into the typed |
| 160 | * scrap piles for later reuse. |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 161 | */ |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 162 | private final SparseArray<OverlayView> mOverlayViews; |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 163 | |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 164 | private int mWidthMeasureSpec; |
| 165 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 166 | private boolean mDisableLayoutTracing; |
| 167 | |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 168 | private final InputSmoother mVelocityTracker; |
| 169 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 170 | private final DataSetObserver mAdapterObserver = new AdapterObserver(); |
| 171 | |
Andy Huang | cf5aeae | 2012-03-09 17:25:08 -0800 | [diff] [blame] | 172 | /** |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 173 | * The adapter index of the lowest overlay item that is above the top of the screen and reports |
| 174 | * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through |
| 175 | * {@link #positionOverlays(int, int)}. |
| 176 | * |
| 177 | */ |
| 178 | private int mSnapIndex; |
| 179 | |
| 180 | /** |
Andy Huang | cf5aeae | 2012-03-09 17:25:08 -0800 | [diff] [blame] | 181 | * Child views of this container should implement this interface to be notified when they are |
| 182 | * being detached. |
| 183 | * |
| 184 | */ |
| 185 | public interface DetachListener { |
| 186 | /** |
| 187 | * Called on a child view when it is removed from its parent as part of |
| 188 | * {@link ConversationContainer} view recycling. |
| 189 | */ |
| 190 | void onDetachedFromParent(); |
| 191 | } |
| 192 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 193 | private static class OverlayView { |
| 194 | public View view; |
| 195 | int itemType; |
| 196 | |
| 197 | public OverlayView(View view, int itemType) { |
| 198 | this.view = view; |
| 199 | this.itemType = itemType; |
| 200 | } |
| 201 | } |
| 202 | |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 203 | public ConversationContainer(Context c) { |
| 204 | this(c, null); |
| 205 | } |
| 206 | |
| 207 | public ConversationContainer(Context c, AttributeSet attrs) { |
| 208 | super(c, attrs); |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 209 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 210 | mOverlayViews = new SparseArray<OverlayView>(); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 211 | |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 212 | mVelocityTracker = new InputSmoother(c); |
| 213 | |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 214 | mTouchSlop = ViewConfiguration.get(c).getScaledTouchSlop(); |
| 215 | |
| 216 | // Disabling event splitting fixes pinch-zoom when the first pointer goes down on the |
| 217 | // WebView and the second pointer goes down on an overlay view. |
| 218 | // Intercepting ACTION_POINTER_DOWN events allows pinch-zoom to work when the first pointer |
| 219 | // goes down on an overlay view. |
| 220 | setMotionEventSplittingEnabled(false); |
| 221 | } |
| 222 | |
| 223 | @Override |
| 224 | protected void onFinishInflate() { |
| 225 | super.onFinishInflate(); |
| 226 | |
Andy Huang | 5ff6374 | 2012-03-16 20:30:23 -0700 | [diff] [blame] | 227 | mWebView = (ConversationWebView) findViewById(R.id.webview); |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 228 | mWebView.addScrollListener(this); |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 229 | |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 230 | mTopMostOverlay = findViewById(R.id.conversation_topmost_overlay); |
| 231 | |
| 232 | mSnapHeader = (MessageHeaderView) findViewById(R.id.snap_header); |
| 233 | mSnapHeader.setSnappy(true); |
| 234 | |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 235 | for (int id : BOTTOM_LAYER_VIEW_IDS) { |
| 236 | mNonScrollingChildren.add(findViewById(id)); |
| 237 | } |
| 238 | for (int id : TOP_LAYER_VIEW_IDS) { |
| 239 | mNonScrollingChildren.add(findViewById(id)); |
| 240 | } |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 241 | } |
| 242 | |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 243 | public MessageHeaderView getSnapHeader() { |
| 244 | return mSnapHeader; |
| 245 | } |
| 246 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 247 | public void setOverlayAdapter(ConversationViewAdapter a) { |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 248 | if (mOverlayAdapter != null) { |
| 249 | mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver); |
| 250 | clearOverlays(); |
| 251 | } |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 252 | mOverlayAdapter = a; |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 253 | if (mOverlayAdapter != null) { |
| 254 | mOverlayAdapter.registerDataSetObserver(mAdapterObserver); |
| 255 | } |
Andy Huang | 5106713 | 2012-03-12 20:08:19 -0700 | [diff] [blame] | 256 | } |
| 257 | |
| 258 | public Adapter getOverlayAdapter() { |
| 259 | return mOverlayAdapter; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 260 | } |
| 261 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 262 | private void clearOverlays() { |
| 263 | for (int i = 0, len = mOverlayViews.size(); i < len; i++) { |
| 264 | detachOverlay(mOverlayViews.valueAt(i)); |
| 265 | } |
| 266 | mOverlayViews.clear(); |
| 267 | } |
| 268 | |
| 269 | private void onDataSetChanged() { |
| 270 | clearOverlays(); |
| 271 | } |
| 272 | |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 273 | private void forwardFakeMotionEvent(MotionEvent original, int newAction) { |
| 274 | MotionEvent newEvent = MotionEvent.obtain(original); |
| 275 | newEvent.setAction(newAction); |
| 276 | mWebView.onTouchEvent(newEvent); |
| 277 | LogUtils.v(TAG, "in Container.OnTouch. fake: action=%d x/y=%f/%f pointers=%d", |
| 278 | newEvent.getActionMasked(), newEvent.getX(), newEvent.getY(), |
| 279 | newEvent.getPointerCount()); |
| 280 | } |
| 281 | |
| 282 | /** |
| 283 | * Touch slop code was copied from {@link ScrollView#onInterceptTouchEvent(MotionEvent)}. |
| 284 | */ |
| 285 | @Override |
| 286 | public boolean onInterceptTouchEvent(MotionEvent ev) { |
Andy Huang | 120ea66 | 2012-03-27 23:15:12 -0700 | [diff] [blame] | 287 | |
| 288 | if (!mTouchInitialized) { |
| 289 | mTouchInitialized = true; |
| 290 | } |
| 291 | |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 292 | // no interception when WebView handles the first DOWN |
| 293 | if (mWebView.isHandlingTouch()) { |
| 294 | return false; |
| 295 | } |
| 296 | |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 297 | boolean intercept = false; |
| 298 | switch (ev.getActionMasked()) { |
| 299 | case MotionEvent.ACTION_POINTER_DOWN: |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 300 | LogUtils.d(TAG, "Container is intercepting non-primary touch!"); |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 301 | intercept = true; |
| 302 | mMissedPointerDown = true; |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 303 | requestDisallowInterceptTouchEvent(true); |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 304 | break; |
| 305 | |
| 306 | case MotionEvent.ACTION_DOWN: |
| 307 | mLastMotionY = ev.getY(); |
| 308 | mActivePointerId = ev.getPointerId(0); |
| 309 | break; |
| 310 | |
| 311 | case MotionEvent.ACTION_MOVE: |
| 312 | final int pointerIndex = ev.findPointerIndex(mActivePointerId); |
| 313 | final float y = ev.getY(pointerIndex); |
| 314 | final int yDiff = (int) Math.abs(y - mLastMotionY); |
| 315 | if (yDiff > mTouchSlop) { |
| 316 | mLastMotionY = y; |
| 317 | intercept = true; |
| 318 | } |
| 319 | break; |
| 320 | } |
| 321 | |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 322 | // LogUtils.v(TAG, "in Container.InterceptTouch. action=%d x/y=%f/%f pointers=%d result=%s", |
| 323 | // ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount(), intercept); |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 324 | return intercept; |
| 325 | } |
| 326 | |
| 327 | @Override |
| 328 | public boolean onTouchEvent(MotionEvent ev) { |
| 329 | final int action = ev.getActionMasked(); |
| 330 | |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 331 | if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 332 | mTouchIsDown = false; |
| 333 | } else if (!mTouchIsDown && |
| 334 | (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_DOWN)) { |
| 335 | |
| 336 | forwardFakeMotionEvent(ev, MotionEvent.ACTION_DOWN); |
| 337 | if (mMissedPointerDown) { |
| 338 | forwardFakeMotionEvent(ev, MotionEvent.ACTION_POINTER_DOWN); |
| 339 | mMissedPointerDown = false; |
| 340 | } |
| 341 | |
| 342 | mTouchIsDown = true; |
| 343 | } |
| 344 | |
| 345 | final boolean webViewResult = mWebView.onTouchEvent(ev); |
| 346 | |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 347 | // LogUtils.v(TAG, "in Container.OnTouch. action=%d x/y=%f/%f pointers=%d", |
| 348 | // ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount()); |
Andy Huang | bb56a15 | 2012-02-24 18:26:47 -0800 | [diff] [blame] | 349 | return webViewResult; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 350 | } |
| 351 | |
| 352 | @Override |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 353 | public void onNotifierScroll(final int x, final int y) { |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 354 | mVelocityTracker.onInput(y); |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 355 | mDisableLayoutTracing = true; |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 356 | positionOverlays(x, y); |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 357 | mDisableLayoutTracing = false; |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 358 | } |
| 359 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 360 | private void positionOverlays(int x, int y) { |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 361 | mOffsetY = y; |
Andy Huang | 07f8732 | 2012-03-27 18:03:53 -0700 | [diff] [blame] | 362 | |
| 363 | /* |
Andy Huang | 120ea66 | 2012-03-27 23:15:12 -0700 | [diff] [blame] | 364 | * The scale value that WebView reports is inaccurate when measured during WebView |
| 365 | * initialization. This bug is present in ICS, so to work around it, we ignore all |
Andy Huang | 2301470 | 2012-07-09 12:50:36 -0700 | [diff] [blame] | 366 | * reported values and use a calculated expected value from ConversationWebView instead. |
| 367 | * Only when the user actually begins to touch the view (to, say, begin a zoom) do we begin |
| 368 | * to pay attention to WebView-reported scale values. |
Andy Huang | 07f8732 | 2012-03-27 18:03:53 -0700 | [diff] [blame] | 369 | */ |
Andy Huang | 120ea66 | 2012-03-27 23:15:12 -0700 | [diff] [blame] | 370 | if (mTouchInitialized) { |
Andy Huang | 07f8732 | 2012-03-27 18:03:53 -0700 | [diff] [blame] | 371 | mScale = mWebView.getScale(); |
Andy Huang | 2301470 | 2012-07-09 12:50:36 -0700 | [diff] [blame] | 372 | } else if (mScale == 0) { |
| 373 | mScale = mWebView.getInitialScale(); |
Andy Huang | 07f8732 | 2012-03-27 18:03:53 -0700 | [diff] [blame] | 374 | } |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 375 | traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(), |
| 376 | mScale); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 377 | |
Andy Huang | e20e163 | 2012-06-15 15:59:07 -0700 | [diff] [blame] | 378 | if (mOverlayBottoms == null || mOverlayAdapter == null) { |
Andy Huang | 5106713 | 2012-03-12 20:08:19 -0700 | [diff] [blame] | 379 | return; |
| 380 | } |
| 381 | |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 382 | // recycle scrolled-off views and add newly visible views |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 383 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 384 | // we want consecutive spacers/overlays to stack towards the bottom |
| 385 | // so iterate from the bottom of the conversation up |
| 386 | // starting with the last spacer bottom and the last adapter item, position adapter views |
| 387 | // in a single stack until you encounter a non-contiguous expanded message header, |
| 388 | // then decrement to the next spacer. |
| 389 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 390 | traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayBottoms.length, |
| 391 | mOverlayAdapter.getCount()); |
| 392 | |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 393 | mSnapIndex = -1; |
| 394 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 395 | int adapterIndex = mOverlayAdapter.getCount() - 1; |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 396 | int spacerIndex = mOverlayBottoms.length - 1; |
| 397 | while (spacerIndex >= 0 && adapterIndex >= 0) { |
| 398 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 399 | final int spacerBottomY = getOverlayBottom(spacerIndex); |
| 400 | |
| 401 | // always place at least one overlay per spacer |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 402 | ConversationOverlayItem adapterItem = mOverlayAdapter.getItem(adapterIndex); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 403 | |
| 404 | int overlayBottomY = spacerBottomY; |
| 405 | int overlayTopY = overlayBottomY - adapterItem.getHeight(); |
| 406 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 407 | traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex, |
| 408 | overlayTopY, overlayBottomY, adapterItem); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 409 | positionOverlay(adapterIndex, overlayTopY, overlayBottomY); |
| 410 | |
| 411 | // and keep stacking overlays as long as they are contiguous |
| 412 | while (--adapterIndex >= 0) { |
| 413 | adapterItem = mOverlayAdapter.getItem(adapterIndex); |
| 414 | if (!adapterItem.isContiguous()) { |
| 415 | // advance to the next spacer, but stay on this adapter item |
| 416 | break; |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 417 | } |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 418 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 419 | overlayBottomY = overlayTopY; // stack on top of previous overlay |
| 420 | overlayTopY = overlayBottomY - adapterItem.getHeight(); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 421 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 422 | traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, |
| 423 | adapterIndex, overlayTopY, overlayBottomY, adapterItem); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 424 | positionOverlay(adapterIndex, overlayTopY, overlayBottomY); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 425 | } |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 426 | |
| 427 | spacerIndex--; |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 428 | } |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 429 | |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 430 | positionSnapHeader(mSnapIndex); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 431 | } |
| 432 | |
| 433 | /** |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 434 | * Executes a measure pass over the specified child overlay view and returns the measured |
| 435 | * height. The measurement uses whatever the current container's width measure spec is. |
| 436 | * This method ignores view visibility and returns the height that the view would be if visible. |
| 437 | * |
| 438 | * @param overlayView an overlay view to measure. does not actually have to be attached yet. |
| 439 | * @return height that the view would be if it was visible |
| 440 | */ |
| 441 | public int measureOverlay(View overlayView) { |
| 442 | measureOverlayView(overlayView); |
| 443 | return overlayView.getMeasuredHeight(); |
| 444 | } |
| 445 | |
| 446 | /** |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 447 | * Copied/stolen from {@link ListView}. |
| 448 | */ |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 449 | private void measureOverlayView(View child) { |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 450 | MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams(); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 451 | if (p == null) { |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 452 | p = (MarginLayoutParams) generateDefaultLayoutParams(); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 453 | } |
| 454 | |
| 455 | int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 456 | getPaddingLeft() + getPaddingRight() + p.leftMargin + p.rightMargin, p.width); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 457 | int lpHeight = p.height; |
| 458 | int childHeightSpec; |
| 459 | if (lpHeight > 0) { |
| 460 | childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); |
| 461 | } else { |
| 462 | childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
| 463 | } |
| 464 | child.measure(childWidthSpec, childHeightSpec); |
| 465 | } |
| 466 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 467 | private void onOverlayScrolledOff(final int adapterIndex, final OverlayView overlay, |
| 468 | int overlayTop, int overlayBottom) { |
Andy Huang | 65fe28f | 2012-04-06 18:08:53 -0700 | [diff] [blame] | 469 | // detach the view asynchronously, as scroll notification can happen during a draw, when |
| 470 | // it's not safe to remove children |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 471 | |
Andy Huang | 65fe28f | 2012-04-06 18:08:53 -0700 | [diff] [blame] | 472 | // but immediately remove this view from the view set so future lookups don't find it |
| 473 | mOverlayViews.remove(adapterIndex); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 474 | |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 475 | post(new Runnable() { |
| 476 | @Override |
| 477 | public void run() { |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 478 | detachOverlay(overlay); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 479 | } |
| 480 | }); |
| 481 | |
| 482 | // push it out of view immediately |
| 483 | // otherwise this scrolled-off header will continue to draw until the runnable runs |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 484 | layoutOverlay(overlay.view, overlayTop, overlayBottom); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 485 | } |
| 486 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 487 | public View getScrapView(int type) { |
| 488 | return mScrapViews.poll(type); |
| 489 | } |
| 490 | |
| 491 | public void addScrapView(int type, View v) { |
| 492 | mScrapViews.add(type, v); |
| 493 | } |
| 494 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 495 | private void detachOverlay(OverlayView overlay) { |
| 496 | detachViewFromParent(overlay.view); |
| 497 | mScrapViews.add(overlay.itemType, overlay.view); |
| 498 | if (overlay.view instanceof DetachListener) { |
| 499 | ((DetachListener) overlay.view).onDetachedFromParent(); |
Andy Huang | cf5aeae | 2012-03-09 17:25:08 -0800 | [diff] [blame] | 500 | } |
| 501 | } |
| 502 | |
| 503 | @Override |
| 504 | protected void onDetachedFromWindow() { |
| 505 | super.onDetachedFromWindow(); |
| 506 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 507 | mScrapViews.visitAll(new DequeMap.Visitor<View>() { |
| 508 | @Override |
| 509 | public void visit(View item) { |
| 510 | removeDetachedView(item, false /* animate */); |
| 511 | } |
| 512 | }); |
Andy Huang | cf5aeae | 2012-03-09 17:25:08 -0800 | [diff] [blame] | 513 | mScrapViews.clear(); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 514 | } |
| 515 | |
| 516 | @Override |
| 517 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 518 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 519 | if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { |
| 520 | LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%s/%s", |
| 521 | MeasureSpec.toString(widthMeasureSpec), |
| 522 | MeasureSpec.toString(heightMeasureSpec)); |
| 523 | } |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 524 | |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 525 | for (View nonScrollingChild : mNonScrollingChildren) { |
| 526 | if (nonScrollingChild.getVisibility() != GONE) { |
| 527 | measureChildWithMargins(nonScrollingChild, widthMeasureSpec, 0 /* widthUsed */, |
| 528 | heightMeasureSpec, 0 /* heightUsed */); |
| 529 | } |
Andy Huang | 3233bff | 2012-03-20 19:38:45 -0700 | [diff] [blame] | 530 | } |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 531 | mWidthMeasureSpec = widthMeasureSpec; |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 532 | |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 533 | // onLayout will re-measure and re-position overlays for the new container size, but the |
| 534 | // spacer offsets would still need to be updated to have them draw at their new locations. |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 535 | } |
| 536 | |
| 537 | @Override |
| 538 | protected void onLayout(boolean changed, int l, int t, int r, int b) { |
Andy Huang | 632721e | 2012-04-11 16:57:26 -0700 | [diff] [blame] | 539 | LogUtils.d(TAG, "*** IN header container onLayout"); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 540 | |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 541 | for (View nonScrollingChild : mNonScrollingChildren) { |
| 542 | if (nonScrollingChild.getVisibility() != GONE) { |
| 543 | final int w = nonScrollingChild.getMeasuredWidth(); |
| 544 | final int h = nonScrollingChild.getMeasuredHeight(); |
| 545 | |
| 546 | final MarginLayoutParams lp = |
| 547 | (MarginLayoutParams) nonScrollingChild.getLayoutParams(); |
| 548 | |
| 549 | final int childLeft = lp.leftMargin; |
| 550 | final int childTop = lp.topMargin; |
| 551 | nonScrollingChild.layout(childLeft, childTop, childLeft + w, childTop + h); |
| 552 | } |
| 553 | } |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 554 | |
Andy Huang | e20e163 | 2012-06-15 15:59:07 -0700 | [diff] [blame] | 555 | if (mOverlayAdapter != null) { |
| 556 | // being in a layout pass means overlay children may require measurement, |
| 557 | // so invalidate them |
| 558 | for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) { |
| 559 | mOverlayAdapter.getItem(i).invalidateMeasurement(); |
| 560 | } |
Andy Huang | 8778a87 | 2012-04-20 17:50:41 -0700 | [diff] [blame] | 561 | } |
| 562 | |
| 563 | positionOverlays(0, mOffsetY); |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 564 | } |
| 565 | |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 566 | @Override |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 567 | protected void dispatchDraw(Canvas canvas) { |
| 568 | super.dispatchDraw(canvas); |
| 569 | |
| 570 | if (mAttachedOverlaySinceLastDraw) { |
| 571 | drawChild(canvas, mTopMostOverlay, getDrawingTime()); |
| 572 | mAttachedOverlaySinceLastDraw = false; |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | @Override |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 577 | protected LayoutParams generateDefaultLayoutParams() { |
| 578 | return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); |
| 579 | } |
| 580 | |
| 581 | @Override |
| 582 | public LayoutParams generateLayoutParams(AttributeSet attrs) { |
| 583 | return new MarginLayoutParams(getContext(), attrs); |
| 584 | } |
| 585 | |
| 586 | @Override |
| 587 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { |
| 588 | return new MarginLayoutParams(p); |
| 589 | } |
| 590 | |
| 591 | @Override |
| 592 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { |
| 593 | return p instanceof MarginLayoutParams; |
| 594 | } |
| 595 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 596 | private int getOverlayBottom(int spacerIndex) { |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 597 | // TODO: round or truncate? |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 598 | return (int) (mOverlayBottoms[spacerIndex] * mScale); |
| 599 | } |
| 600 | |
| 601 | private void positionOverlay(int adapterIndex, int overlayTopY, int overlayBottomY) { |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 602 | final OverlayView overlay = mOverlayViews.get(adapterIndex); |
| 603 | final ConversationOverlayItem item = mOverlayAdapter.getItem(adapterIndex); |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 604 | |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 605 | // save off the item's current top for later snap calculations |
| 606 | item.setTop(overlayTopY); |
| 607 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 608 | // is the overlay visible and does it have non-zero height? |
| 609 | if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY |
| 610 | && overlayTopY < mOffsetY + getHeight()) { |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 611 | View overlayView = overlay != null ? overlay.view : null; |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 612 | // show and/or move overlay |
| 613 | if (overlayView == null) { |
| 614 | overlayView = addOverlayView(adapterIndex); |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 615 | measureOverlayView(overlayView); |
| 616 | item.markMeasurementValid(); |
| 617 | traceLayout("show/measure overlay %d", adapterIndex); |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 618 | } else { |
| 619 | traceLayout("move overlay %d", adapterIndex); |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 620 | if (!item.isMeasurementValid()) { |
| 621 | measureOverlayView(overlayView); |
| 622 | item.markMeasurementValid(); |
| 623 | traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex, |
| 624 | overlayView.getHeight(), overlayView.getMeasuredHeight()); |
| 625 | } |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 626 | } |
Andy Huang | 9875bb4 | 2012-04-04 20:36:21 -0700 | [diff] [blame] | 627 | traceLayout("laying out overlay %d with h=%d", adapterIndex, |
| 628 | overlayView.getMeasuredHeight()); |
Andy Huang | 65fe28f | 2012-04-06 18:08:53 -0700 | [diff] [blame] | 629 | layoutOverlay(overlayView, overlayTopY, overlayTopY + overlayView.getMeasuredHeight()); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 630 | } else { |
| 631 | // hide overlay |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 632 | if (overlay != null) { |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 633 | traceLayout("hide overlay %d", adapterIndex); |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 634 | onOverlayScrolledOff(adapterIndex, overlay, overlayTopY, overlayBottomY); |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 635 | } else { |
| 636 | traceLayout("ignore non-visible overlay %d", adapterIndex); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 637 | } |
| 638 | } |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 639 | |
| 640 | if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) { |
| 641 | if (mSnapIndex == -1) { |
| 642 | mSnapIndex = adapterIndex; |
| 643 | } else if (adapterIndex > mSnapIndex) { |
| 644 | mSnapIndex = adapterIndex; |
| 645 | } |
| 646 | } |
| 647 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 648 | } |
| 649 | |
| 650 | // layout an existing view |
| 651 | // need its top offset into the conversation, its height, and the scroll offset |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 652 | private void layoutOverlay(View child, int childTop, int childBottom) { |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 653 | final int top = childTop - mOffsetY; |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 654 | final int bottom = childBottom - mOffsetY; |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 655 | |
| 656 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); |
| 657 | final int childLeft = getPaddingLeft() + lp.leftMargin; |
| 658 | |
| 659 | child.layout(childLeft, top, childLeft + child.getMeasuredWidth(), bottom); |
Andy Huang | b5078b2 | 2012-03-05 19:52:29 -0800 | [diff] [blame] | 660 | } |
| 661 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 662 | private View addOverlayView(int adapterIndex) { |
| 663 | final int itemType = mOverlayAdapter.getItemViewType(adapterIndex); |
| 664 | final View convertView = mScrapViews.poll(itemType); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 665 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 666 | View view = mOverlayAdapter.getView(adapterIndex, convertView, this); |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 667 | mOverlayViews.put(adapterIndex, new OverlayView(view, itemType)); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 668 | |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 669 | final int index = BOTTOM_LAYER_VIEW_IDS.length; |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 670 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 671 | // Only re-attach if the view had previously been added to a view hierarchy. |
| 672 | // Since external components can contribute to the scrap heap (addScrapView), we can't |
| 673 | // assume scrap views had already been attached. |
| 674 | if (view.getRootView() != view) { |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 675 | LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view); |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 676 | attachViewToParent(view, index, view.getLayoutParams()); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 677 | } else { |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 678 | LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view); |
Andy Huang | 47aa9c9 | 2012-07-31 15:37:21 -0700 | [diff] [blame] | 679 | addViewInLayout(view, index, view.getLayoutParams(), |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 680 | true /* preventRequestLayout */); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 681 | } |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 682 | |
Andy Huang | 59e0b18 | 2012-08-14 14:32:23 -0700 | [diff] [blame] | 683 | mAttachedOverlaySinceLastDraw = true; |
| 684 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 685 | return view; |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 686 | } |
| 687 | |
Andy Huang | 31c38a8 | 2012-08-15 21:39:43 -0700 | [diff] [blame] | 688 | // render and/or re-position snap header |
| 689 | private void positionSnapHeader(int snapIndex) { |
| 690 | ConversationOverlayItem snapItem = null; |
| 691 | if (snapIndex != -1) { |
| 692 | final ConversationOverlayItem item = mOverlayAdapter.getItem(snapIndex); |
| 693 | if (item.canBecomeSnapHeader()) { |
| 694 | snapItem = item; |
| 695 | } |
| 696 | } |
| 697 | if (snapItem == null) { |
| 698 | mSnapHeader.setVisibility(GONE); |
| 699 | mSnapHeader.unbind(); |
| 700 | return; |
| 701 | } |
| 702 | |
| 703 | snapItem.bindView(mSnapHeader, false /* measureOnly */); |
| 704 | mSnapHeader.setVisibility(VISIBLE); |
| 705 | |
| 706 | int overlap = 0; |
| 707 | |
| 708 | final ConversationOverlayItem next = findNextPushingOverlay(snapIndex + 1); |
| 709 | if (next != null) { |
| 710 | overlap = Math.min(0, next.getTop() - mSnapHeader.getHeight() - mOffsetY); |
| 711 | |
| 712 | // disable overlap drawing past a certain speed |
| 713 | if (overlap < 0) { |
| 714 | final Float v = mVelocityTracker.getSmoothedVelocity(); |
| 715 | if (v != null && v > SNAP_HEADER_MAX_SCROLL_SPEED) { |
| 716 | overlap = 0; |
| 717 | } |
| 718 | } |
| 719 | } |
| 720 | mSnapHeader.setTranslateY(overlap); |
| 721 | } |
| 722 | |
| 723 | // find the next header that can push the snap header up |
| 724 | private ConversationOverlayItem findNextPushingOverlay(int start) { |
| 725 | int value = -1; |
| 726 | for (int i = start, len = mOverlayAdapter.getCount(); i < len; i++) { |
| 727 | final ConversationOverlayItem next = mOverlayAdapter.getItem(i); |
| 728 | if (next.canPushSnapHeader()) { |
| 729 | return next; |
| 730 | } |
| 731 | } |
| 732 | return null; |
| 733 | } |
| 734 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 735 | /** |
| 736 | * Prevents any layouts from happening until the next time {@link #onGeometryChange(int[])} is |
| 737 | * called. Useful when you know the HTML spacer coordinates are inconsistent with adapter items. |
| 738 | * <p> |
| 739 | * If you call this, you must ensure that a followup call to {@link #onGeometryChange(int[])} |
| 740 | * is made later, when the HTML spacer coordinates are updated. |
| 741 | * |
| 742 | */ |
| 743 | public void invalidateSpacerGeometry() { |
| 744 | mOverlayBottoms = null; |
| 745 | } |
| 746 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 747 | public void onGeometryChange(int[] overlayBottoms) { |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 748 | traceLayout("*** got overlay spacer bottoms:"); |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 749 | for (int offsetY : overlayBottoms) { |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 750 | traceLayout("%d", offsetY); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 751 | } |
| 752 | |
Andy Huang | 7bdc375 | 2012-03-25 17:18:19 -0700 | [diff] [blame] | 753 | mOverlayBottoms = overlayBottoms; |
| 754 | positionOverlays(0, mOffsetY); |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 755 | } |
| 756 | |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 757 | private void traceLayout(String msg, Object... params) { |
| 758 | if (mDisableLayoutTracing) { |
| 759 | return; |
| 760 | } |
Andy Huang | 7f9ef60 | 2012-07-25 16:44:30 -0700 | [diff] [blame] | 761 | LogUtils.d(TAG, msg, params); |
Andy Huang | c754357 | 2012-04-03 15:34:29 -0700 | [diff] [blame] | 762 | } |
| 763 | |
Andy Huang | 46dfba6 | 2012-04-19 01:47:32 -0700 | [diff] [blame] | 764 | private class AdapterObserver extends DataSetObserver { |
| 765 | @Override |
| 766 | public void onChanged() { |
| 767 | onDataSetChanged(); |
| 768 | } |
| 769 | } |
| 770 | |
Andy Huang | f70fc40 | 2012-02-17 15:37:42 -0800 | [diff] [blame] | 771 | } |