Bug 1193374 - Remove tab panel preview dimension dependency from TopSites r=mcomella

This commit is contained in:
Martyn Haigh 2015-08-24 15:34:24 +01:00
parent 93ea718da0
commit 6181b5a440
11 changed files with 225 additions and 106 deletions

View File

@ -30,9 +30,19 @@ import java.util.concurrent.atomic.AtomicInteger;
public final class ThumbnailHelper {
private static final String LOGTAG = "GeckoThumbnailHelper";
public static final float THUMBNAIL_ASPECT_RATIO = 0.571f; // this is a 4:7 ratio (as per UX decision)
public static final float TABS_PANEL_THUMBNAIL_ASPECT_RATIO = 0.8333333f;
public static final float TOP_SITES_THUMBNAIL_ASPECT_RATIO = 0.571428571f; // this is a 4:7 ratio (as per UX decision)
private static final float THUMBNAIL_ASPECT_RATIO;
public static enum CachePolicy {
static {
// As we only want to generate one thumbnail for each tab, we calculate the
// largest aspect ratio required and create the thumbnail based off that.
// Any views with a smaller aspect ratio will use a cropped version of the
// same image.
THUMBNAIL_ASPECT_RATIO = Math.max(TABS_PANEL_THUMBNAIL_ASPECT_RATIO, TOP_SITES_THUMBNAIL_ASPECT_RATIO);
}
public enum CachePolicy {
STORE,
NO_STORE
}
@ -55,14 +65,10 @@ public final class ThumbnailHelper {
private int mWidth;
private int mHeight;
private ByteBuffer mBuffer;
private final float mThumbnailAspectRatio;
private ThumbnailHelper() {
final Resources res = GeckoAppShell.getContext().getResources();
final TypedValue outValue = new TypedValue();
res.getValue(R.dimen.thumbnail_aspect_ratio, outValue, true);
mThumbnailAspectRatio = outValue.getFloat();
mPendingThumbnails = new LinkedList<Tab>();
try {
@ -111,7 +117,7 @@ public final class ThumbnailHelper {
private void updateThumbnailSize() {
// Apply any pending width updates.
mWidth = mPendingWidth.get();
mHeight = Math.round(mWidth * mThumbnailAspectRatio);
mHeight = Math.round(mWidth * THUMBNAIL_ASPECT_RATIO);
int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
int capacity = mWidth * mHeight * pixelSize;

View File

@ -8,36 +8,25 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ThumbnailHelper;
import org.mozilla.gecko.util.ColorUtils;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.widget.CropImageView;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* A height constrained ImageView to show thumbnails of top and pinned sites.
* A width constrained ImageView to show thumbnails of top and pinned sites.
*/
public class TopSitesThumbnailView extends ImageView {
public class TopSitesThumbnailView extends CropImageView {
private static final String LOGTAG = "GeckoTopSitesThumbnailView";
// 27.34% opacity filter for the dominant color.
private static final int COLOR_FILTER = 0x46FFFFFF;
// Cache variables used in onMeasure.
//
// Note: we have two matrices because we can't change it in place - see ImageView.getImageMatrix docs.
private final RectF mLayoutRect = new RectF();
private Matrix mLayoutCurrentMatrix = new Matrix();
private Matrix mLayoutNextMatrix = new Matrix();
// Default filter color for "Add a bookmark" views.
private final int mDefaultColor = ColorUtils.getColor(getContext(), R.color.top_site_default);
@ -47,16 +36,11 @@ public class TopSitesThumbnailView extends ImageView {
// Paint for drawing the border.
private final Paint mBorderPaint;
private boolean mResize = false;
private int mWidth;
private int mHeight;
public TopSitesThumbnailView(Context context) {
this(context, null);
// A border will be drawn if needed.
setWillNotDraw(false);
}
public TopSitesThumbnailView(Context context, AttributeSet attrs) {
@ -73,69 +57,9 @@ public class TopSitesThumbnailView extends ImageView {
mBorderPaint.setStyle(Paint.Style.STROKE);
}
public void setImageBitmap(Bitmap bm, boolean resize) {
super.setImageBitmap(bm);
mResize = resize;
clearLayoutVars();
updateImageMatrix();
}
private void clearLayoutVars() {
mLayoutRect.setEmpty();
}
private void updateImageMatrix() {
if (!HardwareUtils.isTablet() || !mResize) {
return;
}
// No work to be done here - assumes the rect gets reset when a new bitmap is set.
if (mLayoutRect.right == mWidth && mLayoutRect.bottom == mHeight) {
return;
}
setScaleType(ScaleType.MATRIX);
mLayoutRect.set(0, 0, mWidth, mHeight);
mLayoutNextMatrix.setRectToRect(mLayoutRect, mLayoutRect, Matrix.ScaleToFit.CENTER);
setImageMatrix(mLayoutNextMatrix);
final Matrix swapReferenceMatrix = mLayoutCurrentMatrix;
mLayoutCurrentMatrix = mLayoutNextMatrix;
mLayoutNextMatrix = swapReferenceMatrix;
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
mResize = false;
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mResize = false;
}
/**
* Measure the view to determine the measured width and height.
* The height is constrained by the measured width.
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec vertical space requirements as imposed by the parent, but ignored.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Default measuring.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Force the height based on the aspect ratio.
mWidth = getMeasuredWidth();
mHeight = (int) (mWidth * ThumbnailHelper.THUMBNAIL_ASPECT_RATIO);
setMeasuredDimension(mWidth, mHeight);
updateImageMatrix();
protected float getAspectRatio() {
return ThumbnailHelper.TOP_SITES_THUMBNAIL_ASPECT_RATIO;
}
/**

View File

@ -469,6 +469,7 @@ gbjar.sources += [
'tabs/TabsLayoutItemView.java',
'tabs/TabsListLayout.java',
'tabs/TabsPanel.java',
'tabs/TabsPanelThumbnailView.java',
'Telemetry.java',
'TelemetryContract.java',
'TextSelection.java',
@ -521,6 +522,7 @@ gbjar.sources += [
'widget/CheckableLinearLayout.java',
'widget/ClickableWhenDisabledEditText.java',
'widget/ContentSecurityDoorHanger.java',
'widget/CropImageView.java',
'widget/DateTimePicker.java',
'widget/DefaultDoorHanger.java',
'widget/Divider.java',

View File

@ -66,9 +66,9 @@
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tablet_tab_thumbnail_width"
android:layout_height="@dimen/tablet_tab_thumbnail_height"
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tablet_tab_thumbnail_width"
android:layout_height="@dimen/tablet_tab_thumbnail_height"
/>
</org.mozilla.gecko.widget.TabThumbnailWrapper>

View File

@ -25,9 +25,9 @@
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="@dimen/tab_thumbnail_height"/>
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="@dimen/tab_thumbnail_height"/>
<LinearLayout android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="wrap_content"

View File

@ -23,9 +23,9 @@
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="@dimen/tab_thumbnail_height"/>
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="@dimen/tab_thumbnail_height"/>
</org.mozilla.gecko.widget.TabThumbnailWrapper>

View File

@ -35,9 +35,6 @@
<dimen name="overlay_prompt_container_width">360dp</dimen>
<!-- Should be closer to 0.83 (140/168) but various roundings mean that 0.9 works better -->
<item name="thumbnail_aspect_ratio" format="float" type="dimen">0.9</item>
<item name="tab_strip_content_start" type="dimen">72dp</item>
</resources>

View File

@ -213,9 +213,6 @@
<dimen name="progress_bar_scroll_offset">1.5dp</dimen>
<!-- This is a 4:7 ratio (as per UX decision). -->
<item name="thumbnail_aspect_ratio" format="float" type="dimen">0.571</item>
<!-- http://blog.danlew.net/2015/01/06/handling-android-resources-with-non-standard-formats/ -->
<item name="match_parent" type="dimen">-1</item>
<item name="wrap_content" type="dimen">-2</item>

View File

@ -10,7 +10,6 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.widget.TabThumbnailWrapper;
import org.mozilla.gecko.widget.ThumbnailView;
import org.json.JSONException;
import org.json.JSONObject;
@ -37,7 +36,7 @@ public class TabsLayoutItemView extends LinearLayout
private int mTabId;
private TextView mTitle;
private ThumbnailView mThumbnail;
private TabsPanelThumbnailView mThumbnail;
private ImageView mCloseButton;
private ImageView mAudioPlayingButton;
private TabThumbnailWrapper mThumbnailWrapper;
@ -98,7 +97,7 @@ public class TabsLayoutItemView extends LinearLayout
protected void onFinishInflate() {
super.onFinishInflate();
mTitle = (TextView) findViewById(R.id.title);
mThumbnail = (ThumbnailView) findViewById(R.id.thumbnail);
mThumbnail = (TabsPanelThumbnailView) findViewById(R.id.thumbnail);
mCloseButton = (ImageView) findViewById(R.id.close);
mAudioPlayingButton = (ImageView) findViewById(R.id.audio_playing);
mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);

View File

@ -0,0 +1,52 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tabs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ThumbnailHelper;
import org.mozilla.gecko.widget.CropImageView;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
/**
* A width constrained ImageView to show thumbnails of open tabs in the tabs panel.
*/
public class TabsPanelThumbnailView extends CropImageView {
public static final String LOGTAG = "Gecko" + TabsPanelThumbnailView.class.getSimpleName();
public TabsPanelThumbnailView(final Context context) {
this(context, null);
}
public TabsPanelThumbnailView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public TabsPanelThumbnailView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected float getAspectRatio() {
return ThumbnailHelper.TABS_PANEL_THUMBNAIL_ASPECT_RATIO;
}
@Override
public void setImageDrawable(Drawable drawable) {
boolean resize = true;
if (drawable == null) {
drawable = getResources().getDrawable(R.drawable.tab_panel_tab_background);
resize = false;
setScaleType(ScaleType.FIT_XY);
}
super.setImageDrawable(drawable, resize);
}
}

View File

@ -0,0 +1,142 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.nineoldandroids.view.ViewHelper;
/**
* An ImageView which will always display at the given width and calculated height (based on the width and
* the supplied aspect ratio), drawn starting from the top left hand corner. A supplied drawable will be resized to fit
* the width of the view; if the resized drawable is too tall for the view then the drawable will be cropped at the
* bottom, however if the resized drawable is too short for the view to display whilst honouring it's given width and
* height then the drawable will be displayed at full height with the right hand side cropped.
*/
public abstract class CropImageView extends ThemedImageView {
public static final String LOGTAG = "Gecko" + CropImageView.class.getSimpleName();
private int viewWidth;
private int viewHeight;
private int drawableWidth;
private int drawableHeight;
private boolean resize = true;
private Matrix layoutCurrentMatrix = new Matrix();
private Matrix layoutNextMatrix = new Matrix();
public CropImageView(final Context context) {
this(context, null);
}
public CropImageView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public CropImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
protected abstract float getAspectRatio();
protected void init() {
// Setting the pivots means that the image will be drawn from the top left hand corner. There are
// issues in Android 4.1 (16) which mean setting these values to 0 may not work.
// http://stackoverflow.com/questions/26658124/setpivotx-doesnt-work-on-android-4-1-1-nineoldandroids
ViewHelper.setPivotX(this, 1);
ViewHelper.setPivotY(this, 1);
}
/**
* Measure the view to determine the measured width and height.
* The height is constrained by the measured width.
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec vertical space requirements as imposed by the parent, but ignored.
*/
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
// Default measuring.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Force the height based on the aspect ratio.
viewWidth = getMeasuredWidth();
viewHeight = (int) (viewWidth * getAspectRatio());
setMeasuredDimension(viewWidth, viewHeight);
updateImageMatrix();
}
protected void updateImageMatrix() {
if (!resize || getDrawable() == null) {
return;
}
setScaleType(ImageView.ScaleType.MATRIX);
getDrawable().setBounds(0, 0, viewWidth, viewHeight);
final float horizontalScaleValue = (float) viewWidth / (float) drawableWidth;
final float verticalScaleValue = (float) viewHeight / (float) drawableHeight;
final float scale = Math.max(verticalScaleValue, horizontalScaleValue);
layoutNextMatrix.setScale(scale, scale);
setImageMatrix(layoutNextMatrix);
// You can't modify the matrix in place and we want to avoid allocation, so let's keep two references to two
// different matrix objects that we can swap when the values need to change
final Matrix swapReferenceMatrix = layoutCurrentMatrix;
layoutCurrentMatrix = layoutNextMatrix;
layoutNextMatrix = swapReferenceMatrix;
}
public void setImageBitmap(final Bitmap bm, final boolean resize) {
super.setImageBitmap(bm);
this.resize = resize;
updateImageMatrix();
}
@Override
public void setImageResource(final int resId) {
super.setImageResource(resId);
setImageMatrix(null);
resize = false;
}
@Override
public void setImageDrawable(final Drawable drawable) {
this.setImageDrawable(drawable, false);
}
public void setImageDrawable(final Drawable drawable, final boolean resize) {
super.setImageDrawable(drawable);
if (drawable != null) {
// Reset the matrix to ensure that any previous changes aren't carried through.
setImageMatrix(null);
drawableWidth = drawable.getIntrinsicWidth();
drawableHeight = drawable.getIntrinsicHeight();
} else {
drawableWidth = -1;
drawableHeight = -1;
}
this.resize = resize;
updateImageMatrix();
}
}