Commit 32a6cc46 authored by Tomasz Wiszkowski's avatar Tomasz Wiszkowski Committed by Commit Bot

Add decorated suggestion view class.

This change introduces DecoratedSuggestionView that is hosting the final suggestion
content view. This class features suggestion icon that is rendered on the left side
of the suggestion itself.
Since the ViewGroup serves as an isolated focus area, it must be a separate entity
from BaseSuggestionView. I chose to implement it in a separate file for clarity and
to ensure future changes do not increase entanglement between the classes.

Change-Id: I413c901b1e119355b27b1f8f970d0acc80cd1d9a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1726900
Commit-Queue: Ender <ender@google.com>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#698641}
parent 70aa386b
...@@ -1157,6 +1157,9 @@ chrome_java_sources = [ ...@@ -1157,6 +1157,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProperties.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProperties.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/base/SuggestionDrawableState.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/base/DecoratedSuggestionView.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/base/SimpleHorizontalLayoutView.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionHost.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionHost.java",
"java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java",
......
...@@ -157,6 +157,7 @@ chrome_junit_test_java_sources = [ ...@@ -157,6 +157,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/omnibox/geo/VisibleNetworksTest.java", "junit/src/org/chromium/chrome/browser/omnibox/geo/VisibleNetworksTest.java",
"junit/src/org/chromium/chrome/browser/omnibox/geo/VisibleNetworksTrackerTest.java", "junit/src/org/chromium/chrome/browser/omnibox/geo/VisibleNetworksTrackerTest.java",
"junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java", "junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java",
"junit/src/org/chromium/chrome/browser/omnibox/suggestions/base/SimpleHorizontalLayoutViewTest.java",
"junit/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessorTest.java", "junit/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessorTest.java",
"junit/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionTest.java", "junit/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionTest.java",
"junit/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessorTest.java", "junit/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessorTest.java",
......
...@@ -322,12 +322,15 @@ ...@@ -322,12 +322,15 @@
<dimen name="omnibox_suggestion_start_offset_without_icon">18dp</dimen> <dimen name="omnibox_suggestion_start_offset_without_icon">18dp</dimen>
<dimen name="omnibox_suggestion_start_offset_with_icon">56dp</dimen> <dimen name="omnibox_suggestion_start_offset_with_icon">56dp</dimen>
<dimen name="omnibox_suggestion_36dp_icon_size">36dp</dimen>
<dimen name="omnibox_suggestion_24dp_icon_size">24dp</dimen>
<dimen name="omnibox_suggestion_36dp_icon_margin_start">10dp</dimen> <dimen name="omnibox_suggestion_36dp_icon_margin_start">10dp</dimen>
<dimen name="omnibox_suggestion_36dp_icon_margin_end">10dp</dimen> <dimen name="omnibox_suggestion_36dp_icon_margin_end">10dp</dimen>
<dimen name="omnibox_suggestion_24dp_icon_margin_start">16dp</dimen> <dimen name="omnibox_suggestion_24dp_icon_margin_start">16dp</dimen>
<dimen name="omnibox_suggestion_24dp_icon_margin_end">16dp</dimen> <dimen name="omnibox_suggestion_24dp_icon_margin_end">16dp</dimen>
<dimen name="omnibox_suggestion_favicon_size">24dp</dimen> <dimen name="omnibox_suggestion_favicon_size">24dp</dimen>
<dimen name="omnibox_suggestion_entity_icon_size">36dp</dimen> <dimen name="omnibox_suggestion_entity_icon_size">36dp</dimen>
<dimen name="omnibox_suggestion_icon_area_size">56dp</dimen>
<dimen name="omnibox_suggestion_refine_width">48dp</dimen> <dimen name="omnibox_suggestion_refine_width">48dp</dimen>
<dimen name="omnibox_suggestion_text_vertical_padding">5dp</dimen> <dimen name="omnibox_suggestion_text_vertical_padding">5dp</dimen>
......
...@@ -5,71 +5,31 @@ ...@@ -5,71 +5,31 @@
package org.chromium.chrome.browser.omnibox.suggestions.base; package org.chromium.chrome.browser.omnibox.suggestions.base;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.View.MeasureSpec;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewDelegate; import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewDelegate;
import org.chromium.chrome.browser.ui.widget.RoundedCornerImageView;
import org.chromium.chrome.browser.util.KeyNavigationUtil; import org.chromium.chrome.browser.util.KeyNavigationUtil;
/** /**
* Base layout for common suggestion types. Includes support for a configurable suggestion content * Base layout for common suggestion types. Includes support for a configurable suggestion content
* and the common suggestion patterns shared across suggestion formats. * and the common suggestion patterns shared across suggestion formats.
*/ */
public class BaseSuggestionView extends ViewGroup { public class BaseSuggestionView extends SimpleHorizontalLayoutView {
/** protected final ImageView mActionView;
* Container view for omnibox suggestions allowing soft focus from keyboard.
*/
private static final class DecoratedSuggestionView extends ViewGroup {
private View mContentView;
public DecoratedSuggestionView(Context context) {
super(context);
}
protected void setContentView(View view) {
if (mContentView != null) removeView(mContentView);
mContentView = view;
addView(mContentView);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mContentView.layout(0, 0, right - left, top - bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
assert MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
mContentView.measure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec), mContentView.getMeasuredHeight());
}
@Override
public boolean isFocused() {
return super.isFocused() || (isSelected() && !isInTouchMode());
}
}
protected final ImageView mRefineView;
protected final DecoratedSuggestionView mContentView; protected final DecoratedSuggestionView mContentView;
private SuggestionViewDelegate mDelegate; private SuggestionViewDelegate mDelegate;
private boolean mUseDarkIconTint;
/** /**
* Constructs a new suggestion view. * Constructs a new suggestion view.
...@@ -84,35 +44,30 @@ public class BaseSuggestionView extends ViewGroup { ...@@ -84,35 +44,30 @@ public class BaseSuggestionView extends ViewGroup {
@DrawableRes @DrawableRes
int selectableBackgroundRes = themeRes.resourceId; int selectableBackgroundRes = themeRes.resourceId;
mContentView = new DecoratedSuggestionView(getContext()); mContentView = new DecoratedSuggestionView(getContext(), selectableBackgroundRes);
mContentView.setBackgroundResource(selectableBackgroundRes);
mContentView.setClickable(true);
mContentView.setFocusable(true);
mContentView.setOnClickListener(v -> mDelegate.onSelection()); mContentView.setOnClickListener(v -> mDelegate.onSelection());
mContentView.setOnLongClickListener(v -> { mContentView.setOnLongClickListener(v -> {
mDelegate.onLongPress(); mDelegate.onLongPress();
return true; return true;
}); });
mContentView.setLayoutParams(LayoutParams.forDynamicView());
addView(mContentView); addView(mContentView);
// Action icons. Currently we only support the Refine button. // Action icons. Currently we only support the Refine button.
mRefineView = new ImageView(getContext()); mActionView = new ImageView(getContext());
mRefineView.setBackgroundResource(selectableBackgroundRes); mActionView.setBackgroundResource(selectableBackgroundRes);
mRefineView.setClickable(true); mActionView.setClickable(true);
mRefineView.setFocusable(true); mActionView.setFocusable(true);
mRefineView.setScaleType(ImageView.ScaleType.CENTER); mActionView.setScaleType(ImageView.ScaleType.CENTER);
mRefineView.setContentDescription( mActionView.setContentDescription(
getResources().getString(R.string.accessibility_omnibox_btn_refine)); getResources().getString(R.string.accessibility_omnibox_btn_refine));
mRefineView.setImageResource(R.drawable.btn_suggestion_refine); mActionView.setImageResource(R.drawable.btn_suggestion_refine);
mRefineView.setOnClickListener(v -> mDelegate.onRefineSuggestion()); mActionView.setOnClickListener(v -> mDelegate.onRefineSuggestion());
// Note: height is technically unused, but the behavior is MATCH_PARENT. mActionView.setLayoutParams(new LayoutParams(
mRefineView.setLayoutParams(new LayoutParams(
getResources().getDimensionPixelSize(R.dimen.omnibox_suggestion_refine_width), getResources().getDimensionPixelSize(R.dimen.omnibox_suggestion_refine_width),
LayoutParams.MATCH_PARENT)); LayoutParams.MATCH_PARENT));
addView(mRefineView); addView(mActionView);
Resources res = getResources();
setContentView(view); setContentView(view);
} }
...@@ -127,59 +82,15 @@ public class BaseSuggestionView extends ViewGroup { ...@@ -127,59 +82,15 @@ public class BaseSuggestionView extends ViewGroup {
this(LayoutInflater.from(context).inflate(layoutId, null)); this(LayoutInflater.from(context).inflate(layoutId, null));
} }
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Note: We layout children in the following order:
// - first-to-last in LTR orientation and
// - last-to-first in RTL orientation.
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int index = isRtl ? getChildCount() - 1 : 0;
int increment = isRtl ? -1 : 1;
for (; index >= 0 && index < getChildCount(); index += increment) {
View v = getChildAt(index);
if (v.getVisibility() == GONE) continue;
v.layout(left, 0, left + v.getMeasuredWidth(), bottom - top);
left += v.getMeasuredWidth();
}
}
@Override @Override
protected void onMeasure(int widthSpec, int heightSpec) { protected void onMeasure(int widthSpec, int heightSpec) {
int contentViewWidth = MeasureSpec.getSize(widthSpec); int contentViewWidth = MeasureSpec.getSize(widthSpec);
// TODO(ender): Drop this end padding, and expand the icon size by 8dp to ensure it remains
// Carve out padding at the end of the object. // centered with the omnibox "Clear" button.
contentViewWidth -= getResources().getDimensionPixelSize( contentViewWidth -= getResources().getDimensionPixelSize(
R.dimen.omnibox_suggestion_refine_view_modern_end_padding); R.dimen.omnibox_suggestion_refine_view_modern_end_padding);
super.onMeasure(
// Compute and apply space we can offer to content view. MeasureSpec.makeMeasureSpec(contentViewWidth, MeasureSpec.EXACTLY), heightSpec);
for (int index = 0; index < getChildCount(); ++index) {
View v = getChildAt(index);
// Do not take mContentView into consideration when computing space for it.
if (v == mContentView) continue;
if (v.getVisibility() == GONE) continue;
LayoutParams p = v.getLayoutParams();
if (p.width > 0) contentViewWidth -= p.width;
}
// Measure height of the content view given the width constraint.
mContentView.measure(MeasureSpec.makeMeasureSpec(contentViewWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
final int heightPx = mContentView.getMeasuredHeight();
heightSpec = MeasureSpec.makeMeasureSpec(heightPx, MeasureSpec.EXACTLY);
// Apply measured dimensions to all children.
for (int index = 0; index < getChildCount(); ++index) {
View v = getChildAt(index);
// Avoid calling (expensive) measure on content view twice.
if (v == mContentView) continue;
v.measure(MeasureSpec.makeMeasureSpec(v.getLayoutParams().width, MeasureSpec.EXACTLY),
heightSpec);
}
setMeasuredDimension(MeasureSpec.getSize(widthSpec), MeasureSpec.getSize(heightSpec));
} }
@Override @Override
...@@ -216,7 +127,7 @@ public class BaseSuggestionView extends ViewGroup { ...@@ -216,7 +127,7 @@ public class BaseSuggestionView extends ViewGroup {
* *
* @param view View to be displayed as suggestion content. * @param view View to be displayed as suggestion content.
*/ */
public void setContentView(View view) { void setContentView(View view) {
mContentView.setContentView(view); mContentView.setContentView(view);
} }
...@@ -225,14 +136,13 @@ public class BaseSuggestionView extends ViewGroup { ...@@ -225,14 +136,13 @@ public class BaseSuggestionView extends ViewGroup {
mDelegate = delegate; mDelegate = delegate;
} }
/** Change refine button visibility. */ /** Return widget holding suggestion decoration icon. */
void setRefineVisible(boolean visible) { RoundedCornerImageView getSuggestionImageView() {
mRefineView.setVisibility(visible ? View.VISIBLE : View.GONE); return mContentView.getImageView();
} }
/** Update the refine icon tint color. */ /** Return widget holding action icon. */
void updateIconTint(@ColorInt int color) { ImageView getActionImageView() {
Drawable drawable = mRefineView.getDrawable(); return mActionView;
DrawableCompat.setTint(drawable, color);
} }
} }
...@@ -4,11 +4,16 @@ ...@@ -4,11 +4,16 @@
package org.chromium.chrome.browser.omnibox.suggestions.base; package org.chromium.chrome.browser.omnibox.suggestions.base;
import android.content.res.Resources;
import android.support.annotation.CallSuper;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.CallSuper; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties; import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
import org.chromium.chrome.browser.ui.widget.RoundedCornerImageView;
import org.chromium.chrome.browser.util.ColorUtils; import org.chromium.chrome.browser.util.ColorUtils;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
...@@ -24,15 +29,72 @@ public class BaseSuggestionViewBinder ...@@ -24,15 +29,72 @@ public class BaseSuggestionViewBinder
public void bind(PropertyModel model, BaseSuggestionView view, PropertyKey propertyKey) { public void bind(PropertyModel model, BaseSuggestionView view, PropertyKey propertyKey) {
if (BaseSuggestionViewProperties.SUGGESTION_DELEGATE == propertyKey) { if (BaseSuggestionViewProperties.SUGGESTION_DELEGATE == propertyKey) {
view.setDelegate(model.get(BaseSuggestionViewProperties.SUGGESTION_DELEGATE)); view.setDelegate(model.get(BaseSuggestionViewProperties.SUGGESTION_DELEGATE));
} else if (BaseSuggestionViewProperties.REFINE_VISIBLE == propertyKey) { } else if (BaseSuggestionViewProperties.ICON == propertyKey) {
view.setRefineVisible(model.get(BaseSuggestionViewProperties.REFINE_VISIBLE)); updateSuggestionIcon(model, view);
} else if (BaseSuggestionViewProperties.ACTION_ICON == propertyKey) {
updateActionIcon(model, view);
} else if (SuggestionCommonProperties.LAYOUT_DIRECTION == propertyKey) { } else if (SuggestionCommonProperties.LAYOUT_DIRECTION == propertyKey) {
ViewCompat.setLayoutDirection( ViewCompat.setLayoutDirection(
view, model.get(SuggestionCommonProperties.LAYOUT_DIRECTION)); view, model.get(SuggestionCommonProperties.LAYOUT_DIRECTION));
} else if (SuggestionCommonProperties.USE_DARK_COLORS == propertyKey) { } else if (SuggestionCommonProperties.USE_DARK_COLORS == propertyKey) {
boolean useDarkColors = model.get(SuggestionCommonProperties.USE_DARK_COLORS); updateSuggestionIcon(model, view);
int tint = ColorUtils.getIconTint(view.getContext(), !useDarkColors).getDefaultColor(); updateActionIcon(model, view);
view.updateIconTint(tint); }
}
/** Returns which color scheme should be used to tint drawables. */
private static boolean isDarkMode(PropertyModel model) {
return model.get(SuggestionCommonProperties.USE_DARK_COLORS);
}
/** Update attributes of decorated suggestion icon. */
private static void updateSuggestionIcon(PropertyModel model, BaseSuggestionView baseView) {
final RoundedCornerImageView view = baseView.getSuggestionImageView();
final SuggestionDrawableState sds = model.get(BaseSuggestionViewProperties.ICON);
final Resources res = view.getContext().getResources();
final int paddingStart = res.getDimensionPixelSize(sds.isLarge()
? R.dimen.omnibox_suggestion_36dp_icon_margin_start
: R.dimen.omnibox_suggestion_24dp_icon_margin_start);
final int paddingEnd = res.getDimensionPixelSize(sds.isLarge()
? R.dimen.omnibox_suggestion_36dp_icon_margin_end
: R.dimen.omnibox_suggestion_24dp_icon_margin_end);
// TODO(ender): move logic applying corner rounding to updateIcon when action images use
// RoundedCornerImageView too.
RoundedCornerImageView rciv = (RoundedCornerImageView) view;
int radius = sds.isRounded()
? res.getDimensionPixelSize(R.dimen.default_rounded_corner_radius)
: 0;
rciv.setRoundedCorners(radius, radius, radius, radius);
view.setPadding(paddingStart, 0, paddingEnd, 0);
updateIcon(view, sds, isDarkMode(model));
}
/** Update attributes of decorated suggestion icon. */
private static void updateActionIcon(PropertyModel model, BaseSuggestionView baseView) {
final ImageView view = baseView.getActionImageView();
final SuggestionDrawableState sds = model.get(BaseSuggestionViewProperties.ACTION_ICON);
updateIcon(view, sds, isDarkMode(model));
}
/** Update image view using supplied drawable state object */
private static void updateIcon(
ImageView view, SuggestionDrawableState sds, boolean useDarkColors) {
final Resources res = view.getContext().getResources();
view.setVisibility(sds == null ? View.GONE : View.VISIBLE);
if (sds == null) {
// Release any drawable that is still attached to this view to reclaim memory.
view.setImageDrawable(null);
return;
}
view.setImageDrawable(sds.getDrawable());
if (sds.isTintable()) {
ApiCompatibilityUtils.setImageTintList(
view, ColorUtils.getIconTint(view.getContext(), !useDarkColors));
} }
} }
} }
...@@ -30,12 +30,32 @@ public abstract class BaseSuggestionViewProcessor implements SuggestionProcessor ...@@ -30,12 +30,32 @@ public abstract class BaseSuggestionViewProcessor implements SuggestionProcessor
return true; return true;
} }
/**
* Specify SuggestionDrawableState for suggestion decoration.
*
* @param decoration SuggestionDrawableState object defining decoration for the suggestion.
*/
protected void setSuggestionDrawableState(
PropertyModel model, SuggestionDrawableState decoration) {
model.set(BaseSuggestionViewProperties.ICON, decoration);
}
/**
* Specify SuggestionDrawableState for action button.
*
* @param decoration SuggestionDrawableState object defining decoration for the action button.
*/
protected void setActionDrawableState(PropertyModel model, SuggestionDrawableState decoration) {
model.set(BaseSuggestionViewProperties.ACTION_ICON, decoration);
}
@Override @Override
public void populateModel(OmniboxSuggestion suggestion, PropertyModel model, int position) { public void populateModel(OmniboxSuggestion suggestion, PropertyModel model, int position) {
SuggestionViewDelegate delegate = SuggestionViewDelegate delegate =
mSuggestionHost.createSuggestionViewDelegate(suggestion, position); mSuggestionHost.createSuggestionViewDelegate(suggestion, position);
model.set(BaseSuggestionViewProperties.SUGGESTION_DELEGATE, delegate); model.set(BaseSuggestionViewProperties.SUGGESTION_DELEGATE, delegate);
model.set(BaseSuggestionViewProperties.REFINE_VISIBLE, canRefine(suggestion)); model.set(BaseSuggestionViewProperties.ICON, null);
model.set(BaseSuggestionViewProperties.ACTION_ICON, null);
} }
} }
...@@ -8,21 +8,24 @@ import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonPropertie ...@@ -8,21 +8,24 @@ import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonPropertie
import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewDelegate; import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewDelegate;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
/** The base set of properties for most omnibox suggestions. */ /** The base set of properties for most omnibox suggestions. */
public class BaseSuggestionViewProperties { public class BaseSuggestionViewProperties {
/** Whether refine icon should be visible. */ /** SuggestionDrawableState to show as a suggestion icon. */
public static final WritableBooleanPropertyKey REFINE_VISIBLE = public static final WritableObjectPropertyKey<SuggestionDrawableState> ICON =
new WritableBooleanPropertyKey(); new WritableObjectPropertyKey();
/** SuggestionDrawableState to show as an action icon. */
public static final WritableObjectPropertyKey<SuggestionDrawableState> ACTION_ICON =
new WritableObjectPropertyKey();
/** Delegate receiving user events. */ /** Delegate receiving user events. */
public static final WritableObjectPropertyKey<SuggestionViewDelegate> SUGGESTION_DELEGATE = public static final WritableObjectPropertyKey<SuggestionViewDelegate> SUGGESTION_DELEGATE =
new WritableObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_UNIQUE_KEYS = public static final PropertyKey[] ALL_UNIQUE_KEYS =
new PropertyKey[] {REFINE_VISIBLE, SUGGESTION_DELEGATE}; new PropertyKey[] {ICON, ACTION_ICON, SUGGESTION_DELEGATE};
public static final PropertyKey[] ALL_KEYS = public static final PropertyKey[] ALL_KEYS =
PropertyModel.concatKeys(ALL_UNIQUE_KEYS, SuggestionCommonProperties.ALL_KEYS); PropertyModel.concatKeys(ALL_UNIQUE_KEYS, SuggestionCommonProperties.ALL_KEYS);
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.suggestions.base;
import android.content.Context;
import android.support.annotation.DrawableRes;
import android.view.View;
import android.widget.ImageView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ui.widget.RoundedCornerImageView;
/**
* Container view for omnibox suggestions supplying icon decoration.
*/
class DecoratedSuggestionView extends SimpleHorizontalLayoutView {
private final RoundedCornerImageView mSuggestionIcon;
private View mContentView;
private boolean mUseDarkIconTint;
/**
* Constructs a new suggestion view.
*
* @param context The context used to construct the suggestion view.
* @param background Selectable background resource ID.
*/
DecoratedSuggestionView(Context context, @DrawableRes int background) {
super(context);
setBackgroundResource(background);
setClickable(true);
setFocusable(true);
mSuggestionIcon = new RoundedCornerImageView(getContext());
mSuggestionIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
mSuggestionIcon.setLayoutParams(new LayoutParams(
getResources().getDimensionPixelSize(R.dimen.omnibox_suggestion_icon_area_size),
LayoutParams.MATCH_PARENT));
addView(mSuggestionIcon);
}
/** Specify content view (suggestion body). */
void setContentView(View view) {
if (mContentView != null) removeView(view);
mContentView = view;
mContentView.setLayoutParams(LayoutParams.forDynamicView());
addView(mContentView);
}
/** Returns widget holding suggestion decoration icon. */
RoundedCornerImageView getImageView() {
return mSuggestionIcon;
}
@Override
public boolean isFocused() {
return super.isFocused() || (isSelected() && !isInTouchMode());
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.suggestions.base;
import android.content.Context;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
/**
* SimpleHorizontalLayoutView is a fast and specialized horizontal layout view.
* It is based on a premise that no more than one child view can expand dynamically, while all other
* views must have fixed, predefined size.
*
* Principles of operation:
* - Each fixed-size view must come with an associated LayoutParams structure.
* - The dynamically-sized view must have LayoutParams structure unset.
* - The height of the view will be the result of measurement of the dynamically sized view.
*/
class SimpleHorizontalLayoutView extends ViewGroup {
/**
* SimpleHorizontalLayoutView's LayoutParams.
*
* These parameters introduce additional value to be used with |width| parameter
* that identifies object that should occupy remaining space.
*/
static class LayoutParams extends ViewGroup.LayoutParams {
/** Indicates a resizable view. */
public boolean dynamic;
LayoutParams(int width, int height) {
super(width, height);
}
/**
* Create LayoutParams for a dynamic, resizeable view.
*/
static LayoutParams forDynamicView() {
LayoutParams res = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
res.dynamic = true;
return res;
}
}
SimpleHorizontalLayoutView(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Note: We layout children in the following order:
// - first-to-last in LTR orientation and
// - last-to-first in RTL orientation.
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int index = isRtl ? getChildCount() - 1 : 0;
int increment = isRtl ? -1 : 1;
left = 0;
for (; index >= 0 && index < getChildCount(); index += increment) {
View v = getChildAt(index);
if (v.getVisibility() == GONE) continue;
v.layout(left, 0, left + v.getMeasuredWidth(), bottom - top);
left += v.getMeasuredWidth();
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int contentViewWidth = MeasureSpec.getSize(widthSpec);
View dynamicView = null;
// Compute and apply space we can offer to content view.
for (int index = 0; index < getChildCount(); ++index) {
View v = getChildAt(index);
LayoutParams p = (LayoutParams) v.getLayoutParams();
// Do not take dynamic view into consideration when computing space for it.
// We identify the dynamic view by its custom width parameter.
if (p.dynamic) {
assert dynamicView == null : "Only one dynamically sized view is permitted.";
dynamicView = v;
continue;
}
if (v.getVisibility() == GONE) continue;
if (p.width > 0) contentViewWidth -= p.width;
}
assert dynamicView != null : "No content view specified";
// Measure height of the content view given the width constraint.
dynamicView.measure(MeasureSpec.makeMeasureSpec(contentViewWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
final int heightPx = dynamicView.getMeasuredHeight();
heightSpec = MeasureSpec.makeMeasureSpec(heightPx, MeasureSpec.EXACTLY);
// Apply measured dimensions to all children.
for (int index = 0; index < getChildCount(); ++index) {
View v = getChildAt(index);
// Avoid calling (expensive) measure on dynamic view twice.
if (v == dynamicView) continue;
v.measure(MeasureSpec.makeMeasureSpec(v.getLayoutParams().width, MeasureSpec.EXACTLY),
heightSpec);
}
setMeasuredDimension(MeasureSpec.getSize(widthSpec), MeasureSpec.getSize(heightSpec));
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.suggestions.base;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.v4.util.ObjectsCompat;
import android.support.v7.content.res.AppCompatResources;
import org.chromium.chrome.R;
/** Represents graphical decoration for the suggestion components. */
public class SuggestionDrawableState {
private Drawable mDrawable;
private boolean mAllowTint;
private boolean mUseRoundedCorners;
private boolean mIsLarge;
/**
* Create new SuggestionDrawableState representing a supplied Drawable object.
*
* @param cxt Current context.
* @param drawable Drawable object to use.
* @param isLarge Whether drawable object should be large (or small).
* @param allowTint Whether supplied drawable can be tinted.
* @param rounded Whether supplied drawable should use rounded corners.
*/
public SuggestionDrawableState(
Context ctx, Drawable drawable, boolean isLarge, boolean allowTint, boolean rounded) {
mDrawable = drawable;
mIsLarge = isLarge;
mAllowTint = allowTint;
mUseRoundedCorners = rounded;
}
/**
* Create new SuggestionDrawableState representing a supplied Bitmap.
* Bitmap drawables are not tintable.
*
* @param cxt Current context.
* @param bitmap Bitmap to use.
* @param isLarge Whether drawable object should be large (or small).
* @param rounded Whether supplied drawable should use rounded corners.
*/
public SuggestionDrawableState(Context ctx, Bitmap bitmap, boolean isLarge, boolean rounded) {
this(ctx, new BitmapDrawable(bitmap), isLarge, false, rounded);
}
/**
* Create new SuggestionDrawableState representing a supplied Color.
* This instance is not tintable.
* This method does not utilize ColorDrawables directly, because these are freely resize-able,
* making it impossible to restrict its aspect ratio to a rectangle.
*
* @param cxt Current context.
* @param color Color to show.
* @param isLarge Whether drawable object should be large (or small).
* @param rounded Whether supplied drawable should use rounded corners.
*/
public SuggestionDrawableState(
Context ctx, @ColorInt int color, boolean isLarge, boolean rounded) {
this(ctx, new PaintDrawable(color), isLarge, false, rounded);
final int edgeSize = ctx.getResources().getDimensionPixelSize(isLarge
? R.dimen.omnibox_suggestion_36dp_icon_size
: R.dimen.omnibox_suggestion_24dp_icon_size);
final PaintDrawable drawable = (PaintDrawable) mDrawable;
drawable.setIntrinsicWidth(edgeSize);
drawable.setIntrinsicHeight(edgeSize);
}
/**
* Create new SuggestionDrawableState representing a supplied drawable resource.
*
* @param cxt Current context.
* @param res Drawable resource to use.
* @param isLarge Whether drawable object should be large (or small).
* @param allowTint Whether supplied drawable can be tinted.
* @param rounded Whether supplied drawable should use rounded corners.
*/
public SuggestionDrawableState(Context ctx, @DrawableRes int res, boolean isLarge,
boolean allowTint, boolean rounded) {
this(ctx, AppCompatResources.getDrawable(ctx, res), isLarge, allowTint, rounded);
}
/** Get Drawable associated with this instance. */
Drawable getDrawable() {
return mDrawable;
}
/** Whether drawable can be tinted. */
boolean isTintable() {
return mAllowTint;
}
/** Whether drawable should be drawn as large. */
boolean isLarge() {
return mIsLarge;
}
/** Whether drawable should be rounded. */
boolean isRounded() {
return mUseRoundedCorners;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (!(object instanceof SuggestionDrawableState)) return false;
SuggestionDrawableState other = (SuggestionDrawableState) object;
return mIsLarge == other.mIsLarge && mUseRoundedCorners == other.mUseRoundedCorners
&& mAllowTint == other.mAllowTint
&& ObjectsCompat.equals(mDrawable, other.mDrawable);
}
};
...@@ -42,7 +42,7 @@ public class BaseSuggestionViewTest { ...@@ -42,7 +42,7 @@ public class BaseSuggestionViewTest {
// TODO(https://github.com/robolectric/robolectric/issues/3910) Remove the class below once // TODO(https://github.com/robolectric/robolectric/issues/3910) Remove the class below once
// the above issue is resolved and our robolectric version is rolled forward to the version // the above issue is resolved and our robolectric version is rolled forward to the version
// that supports layout direction changes. // that supports layout direction changes.
class BaseSuggestionViewForTest extends BaseSuggestionView { static class BaseSuggestionViewForTest extends BaseSuggestionView {
private int mCurrentDirection = View.LAYOUT_DIRECTION_LTR; private int mCurrentDirection = View.LAYOUT_DIRECTION_LTR;
BaseSuggestionViewForTest(View childView) { BaseSuggestionViewForTest(View childView) {
...@@ -76,7 +76,7 @@ public class BaseSuggestionViewTest { ...@@ -76,7 +76,7 @@ public class BaseSuggestionViewTest {
} }
View getRefineView() { View getRefineView() {
return mRefineView; return mActionView;
} }
} }
......
...@@ -21,7 +21,6 @@ import android.graphics.drawable.shapes.Shape; ...@@ -21,7 +21,6 @@ import android.graphics.drawable.shapes.Shape;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.ColorRes; import androidx.annotation.ColorRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -89,7 +88,7 @@ public class RoundedCornerImageView extends ImageView { ...@@ -89,7 +88,7 @@ public class RoundedCornerImageView extends ImageView {
/** /**
* Updates the rounded corners, using the radius set in the layout. * Updates the rounded corners, using the radius set in the layout.
*/ */
private void setRoundedCorners(int cornerRadiusTopStart, int cornerRadiusTopEnd, public void setRoundedCorners(int cornerRadiusTopStart, int cornerRadiusTopEnd,
int cornerRadiusBottomStart, int cornerRadiusBottomEnd) { int cornerRadiusBottomStart, int cornerRadiusBottomEnd) {
float[] radii; float[] radii;
if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) { if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment