Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(144)

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/widget/findinpage/FindResultBar.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.widget.findinpage;
6
7 import android.animation.Animator;
8 import android.animation.AnimatorListenerAdapter;
9 import android.animation.ObjectAnimator;
10 import android.annotation.SuppressLint;
11 import android.content.Context;
12 import android.content.res.Resources;
13 import android.graphics.Canvas;
14 import android.graphics.Paint;
15 import android.graphics.RectF;
16 import android.view.Gravity;
17 import android.view.MotionEvent;
18 import android.view.View;
19 import android.view.ViewGroup;
20 import android.widget.FrameLayout;
21
22 import com.google.android.apps.chrome.R;
23
24 import org.chromium.base.annotations.SuppressFBWarnings;
25 import org.chromium.chrome.browser.Tab;
26 import org.chromium.chrome.browser.findinpage.FindInPageBridge;
27 import org.chromium.chrome.browser.util.MathUtils;
28 import org.chromium.ui.UiUtils;
29 import org.chromium.ui.base.LocalizationUtils;
30 import org.chromium.ui.interpolators.BakedBezierInterpolator;
31
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.Comparator;
36 import java.util.List;
37
38 /**
39 * The view that shows the positions of the find in page matches and allows scru bbing
40 * between the entries.
41 */
42 class FindResultBar extends View {
43 private static final int VISIBILTY_ANIMATION_DURATION_MS = 200;
44
45 private final int mBackgroundColor;
46 private final int mBackgroundBorderColor;
47 private final int mResultColor;
48 private final int mResultBorderColor;
49 private final int mActiveColor;
50 private final int mActiveBorderColor;
51
52 private final int mBarTouchWidth;
53 private final int mBarDrawWidth;
54 private final int mResultMinHeight;
55 private final int mActiveMinHeight;
56 private final int mBarVerticalPadding;
57 private final int mMinGapBetweenStacks;
58 private final int mStackedResultHeight;
59
60 private final Tab mTab;
61 private FindInPageBridge mFindInPageBridge;
62
63 int mRectsVersion = -1;
64 private RectF[] mMatches = new RectF[0];
65 private RectF mActiveMatch;
66
67 private ArrayList<Tickmark> mTickmarks = new ArrayList<Tickmark>(0);
68 private int mBarHeightForWhichTickmarksWereCached = -1;
69
70 private Animator mVisibilityAnimation;
71 private boolean mDismissing;
72
73 private final Paint mFillPaint;
74 private final Paint mStrokePaint;
75
76 boolean mWaitingForActivateAck = false;
77
78 private static Comparator<RectF> sComparator = new Comparator<RectF>() {
79 @Override
80 public int compare(RectF a, RectF b) {
81 if (a.top != b.top) return a.top > b.top ? 1 : -1;
82 if (a.top != b.top) return a.left > b.left ? 1 : -1;
83 return 0;
84 }
85 };
86
87 /**
88 * Creates an instance of a {@link FindResultBar}.
89 * @param context The Context to create this {@link FindResultBar} under.
90 * @param tab The Tab containing the ContentView this {@link FindResultBar} will be drawn in.
91 */
92 public FindResultBar(Context context, Tab tab, FindInPageBridge findInPageBr idge) {
93 super(context);
94
95 Resources res = context.getResources();
96 mBackgroundColor = res.getColor(
97 R.color.find_result_bar_background_color);
98 mBackgroundBorderColor = res.getColor(
99 R.color.find_result_bar_background_border_color);
100 mResultColor = res.getColor(
101 R.color.find_result_bar_result_color);
102 mResultBorderColor = res.getColor(
103 R.color.find_result_bar_result_border_color);
104 mActiveColor = res.getColor(
105 R.color.find_result_bar_active_color);
106 mActiveBorderColor = res.getColor(
107 R.color.find_result_bar_active_border_color);
108 mBarTouchWidth = res.getDimensionPixelSize(
109 R.dimen.find_result_bar_touch_width);
110 mBarDrawWidth = res.getDimensionPixelSize(R.dimen.find_result_bar_draw_w idth)
111 + res.getDimensionPixelSize(R.dimen.find_in_page_separator_width );
112 mResultMinHeight = res.getDimensionPixelSize(R.dimen.find_result_bar_res ult_min_height);
113 mActiveMinHeight = res.getDimensionPixelSize(
114 R.dimen.find_result_bar_active_min_height);
115 mBarVerticalPadding = res.getDimensionPixelSize(
116 R.dimen.find_result_bar_vertical_padding);
117 mMinGapBetweenStacks = res.getDimensionPixelSize(
118 R.dimen.find_result_bar_min_gap_between_stacks);
119 mStackedResultHeight = res.getDimensionPixelSize(
120 R.dimen.find_result_bar_stacked_result_height);
121
122 mFillPaint = new Paint();
123 mStrokePaint = new Paint();
124 mFillPaint.setAntiAlias(true);
125 mStrokePaint.setAntiAlias(true);
126 mFillPaint.setStyle(Paint.Style.FILL);
127 mStrokePaint.setStyle(Paint.Style.STROKE);
128 mStrokePaint.setStrokeWidth(1.0f);
129
130 mFindInPageBridge = findInPageBridge;
131 mTab = tab;
132 mTab.getContentViewCore().getContainerView().addView(
133 this, new FrameLayout.LayoutParams(mBarTouchWidth,
134 ViewGroup.LayoutParams.MATCH_PARENT, Gravity.END));
135 setTranslationX(
136 MathUtils.flipSignIf(mBarTouchWidth, LocalizationUtils.isLayoutR tl()));
137
138 mVisibilityAnimation = ObjectAnimator.ofFloat(this, TRANSLATION_X, 0);
139 mVisibilityAnimation.setDuration(VISIBILTY_ANIMATION_DURATION_MS);
140 mVisibilityAnimation.setInterpolator(BakedBezierInterpolator.FADE_IN_CUR VE);
141 mTab.getWindowAndroid().startAnimationOverContent(mVisibilityAnimation);
142 }
143
144 /** Dismisses this results bar by removing it from the view hierarchy. */
145 public void dismiss() {
146 mDismissing = true;
147 if (mVisibilityAnimation != null && mVisibilityAnimation.isRunning()) {
148 mVisibilityAnimation.cancel();
149 }
150
151 mVisibilityAnimation = ObjectAnimator.ofFloat(this, TRANSLATION_X,
152 MathUtils.flipSignIf(mBarTouchWidth, LocalizationUtils.isLayoutR tl()));
153 mVisibilityAnimation.setDuration(VISIBILTY_ANIMATION_DURATION_MS);
154 mVisibilityAnimation.setInterpolator(BakedBezierInterpolator.FADE_OUT_CU RVE);
155 mTab.getWindowAndroid().startAnimationOverContent(mVisibilityAnimation);
156 mVisibilityAnimation.addListener(new AnimatorListenerAdapter() {
157 @Override
158 public void onAnimationEnd(Animator animation) {
159 super.onAnimationEnd(animation);
160
161 if (getParent() != null) ((ViewGroup) getParent()).removeView(Fi ndResultBar.this);
162 }
163 });
164 }
165
166 /** Setup the tickmarks to draw using the rects of the find results. */
167 public void setMatchRects(int version, RectF[] rects, RectF activeRect) {
168 if (mRectsVersion != version) {
169 mRectsVersion = version;
170 assert rects != null;
171 mMatches = rects;
172 mTickmarks.clear();
173 Arrays.sort(mMatches, sComparator);
174 mBarHeightForWhichTickmarksWereCached = -1;
175 }
176 mActiveMatch = activeRect; // Can be null.
177 invalidate();
178 }
179
180 /** Clears the tickmarks. */
181 public void clearMatchRects() {
182 setMatchRects(-1, new RectF[0], null);
183 }
184
185 @Override
186 @SuppressLint("ClickableViewAccessibility")
187 public boolean onTouchEvent(MotionEvent event) {
188 if (!mDismissing && mTickmarks.size() > 0 && mTickmarks.size() == mMatch es.length
189 && !mWaitingForActivateAck && event.getAction() != MotionEvent.A CTION_CANCEL) {
190 // We decided it's more important to get the keyboard out of the
191 // way asap; the user can compensate if their next MotionEvent
192 // scrolls somewhere unintended.
193 UiUtils.hideKeyboard(this);
194
195 // Identify which drawn tickmark is closest to the user's finger.
196 int closest = Collections.binarySearch(mTickmarks,
197 new Tickmark(event.getY(), event.getY()));
198 if (closest < 0) {
199 // No exact match, so must determine nearest.
200 int insertionPoint = -1 - closest;
201 if (insertionPoint == 0) {
202 closest = 0;
203 } else if (insertionPoint == mTickmarks.size()) {
204 closest = mTickmarks.size() - 1;
205 } else {
206 float distanceA = Math.abs(event.getY()
207 - mTickmarks.get(insertionPoint - 1).centerY());
208 float distanceB = Math.abs(event.getY()
209 - mTickmarks.get(insertionPoint).centerY());
210 closest = insertionPoint - (distanceA <= distanceB ? 1 : 0);
211 }
212 }
213
214 // Now activate the find match corresponding to that tickmark.
215 // Since mTickmarks may be outdated, we can't just pass the index.
216 // Instead we send the renderer the coordinates of the center of the
217 // find match's rect (as originally received in setMatchRects), and
218 // it will activate whatever find result is currently closest to
219 // that point (which will usually be the same one).
220 mWaitingForActivateAck = true;
221 mFindInPageBridge.activateNearestFindResult(
222 mMatches[closest].centerX(),
223 mMatches[closest].centerY());
224 }
225 return true; // Consume the event, whether or not we acted upon it.
226 }
227
228 @Override
229 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
230 super.onSizeChanged(w, h, oldw, oldh);
231 // Check for new rects, as they may move if the document size changes.
232 if (!mDismissing && mMatches.length > 0) {
233 mFindInPageBridge.requestFindMatchRects(mRectsVersion);
234 }
235 }
236
237 @Override
238 protected void onDraw(Canvas canvas) {
239 super.onDraw(canvas);
240
241 int leftMargin = getLeftMargin();
242 mFillPaint.setColor(mBackgroundColor);
243 mStrokePaint.setColor(mBackgroundBorderColor);
244 canvas.drawRect(leftMargin, 0,
245 leftMargin + mBarDrawWidth, getHeight(), mFillPaint);
246 float lineX = LocalizationUtils.isLayoutRtl()
247 ? leftMargin + mBarDrawWidth - 0.5f
248 : leftMargin + 0.5f;
249 canvas.drawLine(lineX, 0, lineX, getHeight(), mStrokePaint);
250
251 if (mMatches.length == 0) {
252 return;
253 }
254
255 if (mBarHeightForWhichTickmarksWereCached != getHeight()) {
256 calculateTickmarks();
257 }
258
259 // Draw all matches (since they're sorted by increasing y-position
260 // overlapping tickmarks will form nice stacks).
261 mFillPaint.setColor(mResultColor);
262 mStrokePaint.setColor(mResultBorderColor);
263 for (Tickmark tickmark : mTickmarks) {
264 RectF rect = tickmark.toRectF();
265 canvas.drawRoundRect(rect, 2, 2, mFillPaint);
266 canvas.drawRoundRect(rect, 2, 2, mStrokePaint);
267 }
268
269 // Draw the active tickmark on top (covering up the inactive tickmark
270 // we probably already drew for it).
271 if (mActiveMatch != null) {
272 Tickmark tickmark;
273 int i = Arrays.binarySearch(mMatches, mActiveMatch, sComparator);
274 if (i >= 0) {
275 // We've already generated a tickmark for all rects in mMatches,
276 // so use the corresponding one. However it was generated
277 // assuming the match would be inactive. Keep the position, but
278 // re-expand it using mActiveMinHeight.
279 tickmark = expandTickmarkToMinHeight(mTickmarks.get(i), true);
280 } else {
281 // How strange - mActiveMatch isn't in mMatches. Do our best to
282 // draw it anyway (though it might not line up exactly).
283 tickmark = tickmarkForRect(mActiveMatch, true);
284 }
285 RectF rect = tickmark.toRectF();
286 mFillPaint.setColor(mActiveColor);
287 mStrokePaint.setColor(mActiveBorderColor);
288 canvas.drawRoundRect(rect, 2, 2, mFillPaint);
289 canvas.drawRoundRect(rect, 2, 2, mStrokePaint);
290 }
291 }
292
293 private int getLeftMargin() {
294 return LocalizationUtils.isLayoutRtl() ? 0 : getWidth() - mBarDrawWidth;
295 }
296
297 private void calculateTickmarks() {
298 // TODO(johnme): Simplify calculation, and switch to integer arithmetic
299 // where possible (tickmarks within groups will still need fractional
300 // y-positions for anti-aliasing, but the start and end positions of
301 // groups can and should be integer-aligned [will give crisp borders],
302 // and the intermediary logic uses more floats than necessary).
303 // TODO(johnme): Consider adding unit tests for this.
304
305 mBarHeightForWhichTickmarksWereCached = getHeight();
306
307 // Generate tickmarks, neatly clustering any overlapping matches.
308 mTickmarks = new ArrayList<Tickmark>(mMatches.length);
309 int i = 0;
310 Tickmark nextTickmark = tickmarkForRect(mMatches[i], false);
311 float lastGroupEnd = -mMinGapBetweenStacks;
312 while (i < mMatches.length) {
313 // Find next cluster of overlapping tickmarks.
314 List<Tickmark> cluster = new ArrayList<Tickmark>();
315 cluster.add(nextTickmark);
316 i++;
317 while (i < mMatches.length) {
318 nextTickmark = tickmarkForRect(mMatches[i], false);
319 if (nextTickmark.mTop <= cluster.get(cluster.size() - 1).mBottom
320 + mMinGapBetweenStacks) {
321 cluster.add(nextTickmark);
322 i++;
323 } else {
324 break;
325 }
326 }
327
328 // Draw cluster.
329 int cn = cluster.size();
330 float minStart = lastGroupEnd + mMinGapBetweenStacks;
331 lastGroupEnd = cluster.get(cn - 1).mBottom;
332 float preferredStart = lastGroupEnd
333 - (cn - 1) * mStackedResultHeight
334 - mResultMinHeight;
335 float maxStart = cluster.get(0).mTop;
336 float start = Math.round(MathUtils.clamp(preferredStart, minStart, m axStart));
337 float scale = start >= preferredStart ? 1.0f :
338 (lastGroupEnd - start) / (lastGroupEnd - preferredStart);
339 float spacing = cn == 1 ? 0 : (lastGroupEnd - start
340 - scale * mResultMinHeight) / (cn - 1);
341 for (int j = 0; j < cn; j++) {
342 Tickmark tickmark = cluster.get(j);
343 tickmark.mTop = start + j * spacing;
344 if (j != cn - 1) {
345 tickmark.mBottom = tickmark.mTop + scale * mResultMinHeight;
346 }
347 mTickmarks.add(tickmark);
348 }
349 }
350 }
351
352 private Tickmark tickmarkForRect(RectF r, boolean active) {
353 // Ratio of results bar height to page height
354 float vScale = mBarHeightForWhichTickmarksWereCached - 2 * mBarVerticalP adding;
355 Tickmark tickmark = new Tickmark(
356 r.top * vScale + mBarVerticalPadding,
357 r.bottom * vScale + mBarVerticalPadding);
358 return expandTickmarkToMinHeight(tickmark, active);
359 }
360
361 private Tickmark expandTickmarkToMinHeight(Tickmark tickmark,
362 boolean active) {
363 int minHeight = active ? mActiveMinHeight : mResultMinHeight;
364 float missingHeight = minHeight - tickmark.height();
365 if (missingHeight > 0) {
366 return new Tickmark(tickmark.mTop - missingHeight / 2.0f,
367 tickmark.mBottom + missingHeight / 2.0f);
368 }
369 return tickmark;
370 }
371
372 /** Like android.graphics.RectF, but without a left or right. */
373 private class Tickmark implements Comparable<Tickmark> {
374 float mTop;
375 float mBottom;
376 Tickmark(float top, float bottom) {
377 this.mTop = top;
378 this.mBottom = bottom;
379 }
380 float height() {
381 return mBottom - mTop;
382 }
383 float centerY() {
384 return (mTop + mBottom) * 0.5f;
385 }
386 RectF toRectF() {
387 int leftMargin = getLeftMargin();
388 RectF rect = new RectF(leftMargin, mTop, leftMargin + mBarDrawWidth, mBottom);
389 rect.inset(2.0f, 0.5f);
390 rect.offset(LocalizationUtils.isLayoutRtl() ? -0.5f : 0.5f, 0);
391 return rect;
392 }
393 @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUAL")
394 @Override
395 public int compareTo(Tickmark other) {
396 float center = centerY();
397 float otherCenter = other.centerY();
398 if (center == otherCenter) return 0;
399 return center > otherCenter ? 1 : -1;
400 }
401 }
402 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698