Bug 767980 - Reimplement PropertyAnimator in terms of view proxies (r=mfinkle)

This commit is contained in:
Lucas Rocha 2012-10-12 12:57:07 +01:00
parent 14c4c78adc
commit 60fcbd1dd8
9 changed files with 537 additions and 113 deletions

View File

@ -0,0 +1,303 @@
/* -*- 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;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
public class AnimatorProxy {
private static final WeakHashMap<View, AnimatorProxy> PROXIES =
new WeakHashMap<View, AnimatorProxy>();
private static interface AnimatorProxyImpl {
public int getScrollX();
public int getScrollY();
public void scrollTo(int scrollX, int scrollY);
public float getTranslationX();
public void setTranslationX(float translationX);
public float getTranslationY();
public void setTranslationY(float translationY);
}
private AnimatorProxyImpl mImpl;
private AnimatorProxy(AnimatorProxyImpl impl) {
mImpl = impl;
}
public static AnimatorProxy create(View view) {
AnimatorProxy proxy = PROXIES.get(view);
boolean needsAnimationProxy = (Build.VERSION.SDK_INT < 11);
// If the view's animation proxy has been overridden from somewhere else, we need to
// create a new AnimatorProxy for the view.
if (proxy == null || (needsAnimationProxy && proxy.mImpl != view.getAnimation())) {
AnimatorProxyImpl impl = (needsAnimationProxy ? new AnimatorProxyPreHC(view) :
new AnimatorProxyPostHC(view));
proxy = new AnimatorProxy(impl);
PROXIES.put(view, proxy);
}
return proxy;
}
public int getScrollX() {
return mImpl.getScrollX();
}
public int getScrollY() {
return mImpl.getScrollY();
}
public void scrollTo(int scrollX, int scrollY) {
mImpl.scrollTo(scrollX, scrollY);
}
public float getTranslationX() {
return mImpl.getTranslationX();
}
public void setTranslationX(float translationX) {
mImpl.setTranslationX(translationX);
}
public float getTranslationY() {
return mImpl.getTranslationY();
}
public void setTranslationY(float translationY) {
mImpl.setTranslationY(translationY);
}
/*
* AnimatorProxyPreHC uses the technique used by the NineOldAndroids described here:
* http://jakewharton.com/advanced-pre-honeycomb-animation/
*
* Some of this code is based on Jake Wharton's AnimatorProxy released as part of
* the NineOldAndroids library under the Apache License 2.0.
*/
private static class AnimatorProxyPreHC extends Animation implements AnimatorProxyImpl {
private WeakReference<View> mViewRef;
private final RectF mBefore;
private final RectF mAfter;
private final Matrix mTempMatrix;
private float mTranslationX;
private float mTranslationY;
public AnimatorProxyPreHC(View view) {
mBefore = new RectF();
mAfter = new RectF();
mTempMatrix = new Matrix();
mTranslationX = 0;
mTranslationY = 0;
loadCurrentTransformation(view);
setDuration(0);
setFillAfter(true);
view.setAnimation(this);
mViewRef = new WeakReference<View>(view);
}
private void loadCurrentTransformation(View view) {
Animation animation = view.getAnimation();
if (animation == null)
return;
Transformation transformation = new Transformation();
float[] matrix = new float[9];
animation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), transformation);
transformation.getMatrix().getValues(matrix);
mTranslationX = matrix[Matrix.MTRANS_X];
mTranslationY = matrix[Matrix.MTRANS_Y];
}
private void prepareForUpdate() {
View view = mViewRef.get();
if (view != null)
computeRect(mBefore, view);
}
private void computeRect(final RectF r, View view) {
final float w = view.getWidth();
final float h = view.getHeight();
r.set(0, 0, w, h);
final Matrix m = mTempMatrix;
m.reset();
transformMatrix(m, view);
mTempMatrix.mapRect(r);
r.offset(view.getLeft(), view.getTop());
}
private void transformMatrix(Matrix m, View view) {
m.postTranslate(mTranslationX, mTranslationY);
}
private void invalidateAfterUpdate() {
View view = mViewRef.get();
if (view == null || view.getParent() == null)
return;
final RectF after = mAfter;
computeRect(after, view);
after.union(mBefore);
((View)view.getParent()).invalidate(
(int) Math.floor(after.left),
(int) Math.floor(after.top),
(int) Math.ceil(after.right),
(int) Math.ceil(after.bottom));
}
@Override
public int getScrollX() {
View view = mViewRef.get();
if (view != null)
return view.getScrollX();
return 0;
}
@Override
public int getScrollY() {
View view = mViewRef.get();
if (view != null)
return view.getScrollY();
return 0;
}
@Override
public void scrollTo(int scrollX, int scrollY) {
View view = mViewRef.get();
if (view != null)
view.scrollTo(scrollX, scrollY);
}
@Override
public float getTranslationX() {
return mTranslationX;
}
@Override
public void setTranslationX(float translationX) {
if (mTranslationX == translationX)
return;
prepareForUpdate();
mTranslationX = translationX;
invalidateAfterUpdate();
}
@Override
public float getTranslationY() {
return mTranslationY;
}
@Override
public void setTranslationY(float translationY) {
if (mTranslationY == translationY)
return;
prepareForUpdate();
mTranslationY = translationY;
invalidateAfterUpdate();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
View view = mViewRef.get();
if (view != null)
transformMatrix(t.getMatrix(), view);
}
}
private static class AnimatorProxyPostHC implements AnimatorProxyImpl {
private WeakReference<View> mViewRef;
public AnimatorProxyPostHC(View view) {
mViewRef = new WeakReference<View>(view);
}
@Override
public int getScrollX() {
View view = mViewRef.get();
if (view != null)
return view.getScrollX();
return 0;
}
@Override
public int getScrollY() {
View view = mViewRef.get();
if (view != null)
return view.getScrollY();
return 0;
}
@Override
public void scrollTo(int scrollX, int scrollY) {
View view = mViewRef.get();
if (view != null)
view.scrollTo(scrollX, scrollY);
}
@Override
public float getTranslationX() {
View view = mViewRef.get();
if (view != null)
return view.getTranslationX();
return 0;
}
@Override
public void setTranslationX(float translationX) {
View view = mViewRef.get();
if (view != null)
view.setTranslationX(translationX);
}
@Override
public float getTranslationY() {
View view = mViewRef.get();
if (view != null)
return view.getTranslationY();
return 0;
}
@Override
public void setTranslationY(float translationY) {
View view = mViewRef.get();
if (view != null)
view.setTranslationY(translationY);
}
}
}

View File

@ -511,26 +511,29 @@ abstract public class BrowserApp extends GeckoApp
mMainLayoutAnimator = new PropertyAnimator(450, sTabsInterpolator);
mMainLayoutAnimator.setPropertyAnimationListener(this);
boolean usingTextureView = mLayerView.shouldUseTextureView();
mMainLayoutAnimator.setUseHardwareLayer(usingTextureView);
if (hasTabsSideBar()) {
mMainLayoutAnimator.attach(mBrowserToolbar.getLayout(),
PropertyAnimator.Property.SHRINK_LEFT,
width);
mBrowserToolbar.prepareTabsAnimation(mMainLayoutAnimator, width);
// Set the gecko layout for sliding.
if (!mTabsPanel.isShown()) {
((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(0, 0, 0, 0);
mGeckoLayout.scrollTo(mTabsPanel.getWidth() * -1, 0);
if (!usingTextureView)
mGeckoLayout.scrollTo(mTabsPanel.getWidth() * -1, 0);
mGeckoLayout.requestLayout();
}
mMainLayoutAnimator.attach(mGeckoLayout,
PropertyAnimator.Property.SLIDE_LEFT,
width);
usingTextureView ? PropertyAnimator.Property.TRANSLATION_X :
PropertyAnimator.Property.SCROLL_X,
usingTextureView ? width : -width);
} else {
mMainLayoutAnimator.attach(mMainLayout,
PropertyAnimator.Property.SLIDE_TOP,
height);
usingTextureView ? PropertyAnimator.Property.TRANSLATION_Y :
PropertyAnimator.Property.SCROLL_Y,
usingTextureView ? height : -height);
}
mMainLayoutAnimator.start();
@ -538,30 +541,39 @@ abstract public class BrowserApp extends GeckoApp
@Override
public void onPropertyAnimationStart() {
mMainHandler.post(new Runnable() {
public void run() {
mBrowserToolbar.updateTabs(true);
}
});
mBrowserToolbar.updateTabs(true);
// Although the tabs panel is not animating per se, it will be re-drawn several
// times while the main/gecko layout slides to left/top. Adding a hardware layer
// here considerably improves the frame rate of the animation.
if (Build.VERSION.SDK_INT >= 11)
mTabsPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
@Override
public void onPropertyAnimationEnd() {
mMainHandler.post(new Runnable() {
public void run() {
if (hasTabsSideBar() && mTabsPanel.isShown()) {
// Fake the gecko layout to have been shrunk, instead of sliding.
((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(mTabsPanel.getWidth(), 0, 0, 0);
mGeckoLayout.scrollTo(0, 0);
mGeckoLayout.requestLayout();
}
// Destroy the hardware layer used during the animation
if (Build.VERSION.SDK_INT >= 11)
mTabsPanel.setLayerType(View.LAYER_TYPE_NONE, null);
if (!mTabsPanel.isShown()) {
mBrowserToolbar.updateTabs(false);
mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
}
});
if (hasTabsSideBar() && mTabsPanel.isShown()) {
boolean usingTextureView = mLayerView.shouldUseTextureView();
int leftMargin = (usingTextureView ? 0 : mTabsPanel.getWidth());
int rightMargin = (usingTextureView ? mTabsPanel.getWidth() : 0);
((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(leftMargin, 0, rightMargin, 0);
if (!usingTextureView)
mGeckoLayout.scrollTo(0, 0);
mGeckoLayout.requestLayout();
}
if (!mTabsPanel.isShown()) {
mBrowserToolbar.updateTabs(false);
mBrowserToolbar.finishTabsAnimation();
mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
}
/* Favicon methods */

View File

@ -51,12 +51,15 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
Animation.AnimationListener {
private static final String LOGTAG = "GeckoToolbar";
private LinearLayout mLayout;
private Button mAwesomeBar;
private View mAwesomeBar;
private View mAwesomeBarRightEdge;
private View mAddressBarBg;
private TextView mTitle;
private int mTitlePadding;
private boolean mSiteSecurityVisible;
private boolean mAnimateSiteSecurity;
private ImageButton mTabs;
private int mTabsPaneWidth;
private ImageView mBack;
private ImageView mForward;
public ImageButton mFavicon;
@ -112,12 +115,29 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
mShowSiteSecurity = false;
mShowReader = false;
// Only used on tablet layout. We need a separate view for the background
// because we need to slide it left/right for hiding/shoing the tabs sidebar
// See prepareTabsAnimation().
mAddressBarBg = mLayout.findViewById(R.id.address_bar_bg);
// Only used on tablet layout. The tabs sidebar slide animation is implemented
// in terms of translating the inner elements of the tablet toolbar to give the
// impression of resizing. In order to do this, This "fake" right edge is kept
// in the same position during the animation while the elements on the left
// (favicon, back, forware, lock icon, title, ...) slide behind it.
// See prepareTabsAnimation().
mAwesomeBarRightEdge = mLayout.findViewById(R.id.awesome_bar_right_edge);
// This will hold the translation width inside the toolbar when the tabs
// pane is visible. It will affect the padding applied to the title TextView.
mTabsPaneWidth = 0;
mTitle = (TextView) mLayout.findViewById(R.id.awesome_bar_title);
mTitlePadding = mTitle.getPaddingRight();
if (Build.VERSION.SDK_INT >= 16)
mTitle.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mAwesomeBar = (Button) mLayout.findViewById(R.id.awesome_bar);
mAwesomeBar = mLayout.findViewById(R.id.awesome_bar);
mAwesomeBar.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
mActivity.autoHideTabs();
@ -460,6 +480,54 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
updateTabs(mActivity.areTabsShown());
}
public void prepareTabsAnimation(PropertyAnimator animator, int width) {
// This is negative before we want to keep the right edge in the same
// position while animating the left-most elements below.
animator.attach(mAwesomeBarRightEdge,
PropertyAnimator.Property.TRANSLATION_X,
-width);
animator.attach(mAwesomeBar,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mAddressBarBg,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mTabs,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mTabsCount,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mBack,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mForward,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mTitle,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mFavicon,
PropertyAnimator.Property.TRANSLATION_X,
width);
animator.attach(mSiteSecurity,
PropertyAnimator.Property.TRANSLATION_X,
width);
mTabsPaneWidth = width;
// Only update title padding immediatelly when shrinking the browser
// toolbar. Leave the padding update to the end of the animation when
// expanding (see finishTabsAnimation()).
if (mTabsPaneWidth > 0)
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
public void finishTabsAnimation() {
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
public void updateTabs(boolean areTabsShown) {
if (areTabsShown) {
mTabs.getBackground().setLevel(TABS_EXPANDED);
@ -511,7 +579,7 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
// We want title to fill the whole space available for it when there are icons
// being shown on the right side of the toolbar as the icons already have some
// padding in them. This is just to avoid wasting space when icons are shown.
mTitle.setPadding(0, 0, (!mShowReader && !isLoading ? mTitlePadding : 0), 0);
mTitle.setPadding(0, 0, (!mShowReader && !isLoading ? mTitlePadding : 0) + mTabsPaneWidth, 0);
updateFocusOrder();
}

View File

@ -175,7 +175,7 @@ abstract public class GeckoApp
protected FormAssistPopup mFormAssistPopup;
protected TabsPanel mTabsPanel;
private LayerView mLayerView;
protected LayerView mLayerView;
private AbsoluteLayout mPluginContainer;
private FullScreenHolder mFullScreenPluginContainer;

View File

@ -40,6 +40,7 @@ FENNEC_JAVA_FILES = \
ActivityHandlerHelper.java \
AndroidImport.java \
AndroidImportPreference.java \
AnimatorProxy.java \
AlertNotification.java \
AnimatedHeightLayout.java \
AwesomeBar.java \

View File

@ -17,24 +17,23 @@ import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class PropertyAnimator implements Runnable {
private static final String LOGTAG = "GeckoPropertyAnimator";
public static enum Property {
SHRINK_LEFT,
SHRINK_TOP,
SLIDE_TOP,
SLIDE_LEFT
TRANSLATION_X,
TRANSLATION_Y,
SCROLL_X,
SCROLL_Y
}
private class ElementHolder {
View view;
AnimatorProxy proxy;
Property property;
int from;
int to;
float from;
float to;
}
public static interface PropertyAnimationListener {
@ -49,6 +48,7 @@ public class PropertyAnimator implements Runnable {
private List<ElementHolder> mElementsList;
private PropertyAnimationListener mListener;
private FramePoster mFramePoster;
private boolean mUseHardwareLayer;
public PropertyAnimator(int duration) {
this(duration, new DecelerateInterpolator());
@ -60,25 +60,20 @@ public class PropertyAnimator implements Runnable {
mInterpolator = interpolator;
mElementsList = new ArrayList<ElementHolder>();
mFramePoster = FramePoster.create(this);
mUseHardwareLayer = true;
}
public void setUseHardwareLayer(boolean useHardwareLayer) {
mUseHardwareLayer = useHardwareLayer;
}
public void attach(View view, Property property, int to) {
if (!(view instanceof ViewGroup) && (property == Property.SHRINK_LEFT ||
property == Property.SHRINK_TOP)) {
Log.i(LOGTAG, "Margin can only be animated on Viewgroups");
return;
}
ElementHolder element = new ElementHolder();
element.view = view;
element.proxy = AnimatorProxy.create(view);
element.property = property;
// Sliding should happen in the negative.
if (property == Property.SLIDE_TOP || property == Property.SLIDE_LEFT)
element.to = to * -1;
else
element.to = to;
element.to = to;
mElementsList.add(element);
}
@ -98,7 +93,7 @@ public class PropertyAnimator implements Runnable {
float interpolation = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
for (ElementHolder element : mElementsList) {
int delta = element.from + (int) ((element.to - element.from) * interpolation);
float delta = element.from + ((element.to - element.from) * interpolation);
invalidate(element, delta);
}
@ -110,17 +105,17 @@ public class PropertyAnimator implements Runnable {
// Fix the from value based on current position and property
for (ElementHolder element : mElementsList) {
if (element.property == Property.SLIDE_TOP)
element.from = element.view.getScrollY();
else if (element.property == Property.SLIDE_LEFT)
element.from = element.view.getScrollX();
else {
ViewGroup.MarginLayoutParams params = ((ViewGroup.MarginLayoutParams) element.view.getLayoutParams());
if (element.property == Property.SHRINK_TOP)
element.from = params.topMargin;
else if (element.property == Property.SHRINK_LEFT)
element.from = params.leftMargin;
}
if (element.property == Property.TRANSLATION_Y)
element.from = element.proxy.getTranslationY();
else if (element.property == Property.TRANSLATION_X)
element.from = element.proxy.getTranslationX();
else if (element.property == Property.SCROLL_Y)
element.from = element.proxy.getScrollY();
else if (element.property == Property.SCROLL_X)
element.from = element.proxy.getScrollX();
if (shouldEnableHardwareLayer(element))
element.view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
if (mDuration != 0) {
@ -137,6 +132,8 @@ public class PropertyAnimator implements Runnable {
// Make sure to snap to the end position.
for (ElementHolder element : mElementsList) {
invalidate(element, element.to);
if (shouldEnableHardwareLayer(element))
element.view.setLayerType(View.LAYER_TYPE_NONE, null);
}
mElementsList.clear();
@ -147,7 +144,23 @@ public class PropertyAnimator implements Runnable {
}
}
private void invalidate(final ElementHolder element, final int delta) {
private boolean shouldEnableHardwareLayer(ElementHolder element) {
if (!mUseHardwareLayer)
return false;
if (Build.VERSION.SDK_INT < 11)
return false;
if (!(element.view instanceof ViewGroup))
return false;
if (element.property == Property.TRANSLATION_Y || element.property == Property.TRANSLATION_X)
return true;
return false;
}
private void invalidate(final ElementHolder element, final float delta) {
final View view = element.view;
// check to see if the view was detached between the check above and this code
@ -155,22 +168,14 @@ public class PropertyAnimator implements Runnable {
if (view.getHandler() == null)
return;
if (element.property == Property.SLIDE_TOP) {
view.scrollTo(view.getScrollX(), delta);
return;
} else if (element.property == Property.SLIDE_LEFT) {
view.scrollTo(delta, view.getScrollY());
return;
}
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (element.property == Property.SHRINK_TOP)
params.setMargins(params.leftMargin, delta, params.rightMargin, params.bottomMargin);
else if (element.property == Property.SHRINK_LEFT)
params.setMargins(delta, params.topMargin, params.rightMargin, params.bottomMargin);
view.requestLayout();
if (element.property == Property.TRANSLATION_Y)
element.proxy.setTranslationY(delta);
else if (element.property == Property.TRANSLATION_X)
element.proxy.setTranslationX(delta);
else if (element.property == Property.SCROLL_Y)
element.proxy.scrollTo(element.proxy.getScrollX(), (int) delta);
else if (element.property == Property.SCROLL_X)
element.proxy.scrollTo((int) delta, element.proxy.getScrollY());
}
private static abstract class FramePoster {

View File

@ -297,7 +297,7 @@ public class TabsTray extends LinearLayout
private void animateTo(final View view, int x, int duration) {
PropertyAnimator pa = new PropertyAnimator(duration);
pa.attach(view, Property.SLIDE_LEFT, x);
pa.attach(view, Property.SCROLL_X, -x);
if (x != 0 && !mWaitingForClose) {
mWaitingForClose = true;

View File

@ -63,7 +63,7 @@ public class LayerView extends FrameLayout {
public static final int PAINT_BEFORE_FIRST = 0;
public static final int PAINT_AFTER_FIRST = 1;
boolean shouldUseTextureView() {
public boolean shouldUseTextureView() {
// Disable TextureView support for now as it causes panning/zooming
// performance regressions (see bug 792259). Uncomment the code below
// once this bug is fixed.

View File

@ -10,8 +10,12 @@
style="@style/BrowserToolbar">
<RelativeLayout android:id="@+id/address_bar"
style="@style/AddressBar"
android:background="@drawable/address_bar_bg">
style="@style/AddressBar">
<ImageView android:id="@+id/address_bar_bg"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/address_bar_bg"/>
<org.mozilla.gecko.TabsButton android:id="@+id/tabs"
style="@style/AddressBar.ImageButton"
@ -33,18 +37,17 @@
android:layout_alignLeft="@id/tabs"
android:gravity="center_horizontal"/>
<org.mozilla.gecko.MenuButton android:id="@+id/menu"
style="@style/AddressBar.ImageButton"
android:layout_width="56dip"
android:layout_alignParentRight="true"
gecko:curveTowards="none"
android:gravity="center_vertical"
android:src="@drawable/menu"
android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"
android:paddingLeft="14dip"
android:paddingRight="14dip"
android:visibility="gone"/>
<ImageButton android:id="@+id/menu"
style="@style/AddressBar.ImageButton"
android:layout_width="56dip"
android:layout_alignParentRight="true"
android:gravity="center_vertical"
android:src="@drawable/menu"
android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"
android:paddingLeft="14dip"
android:paddingRight="14dip"
android:visibility="gone"/>
<LinearLayout android:id="@+id/menu_items"
android:layout_width="wrap_content"
@ -52,20 +55,52 @@
android:orientation="horizontal"
android:layout_toLeftOf="@id/menu"/>
<FrameLayout style="@style/AddressBar.Button"
android:layout_toRightOf="@id/tabs"
android:layout_toLeftOf="@id/menu_items"
android:layout_marginLeft="-28dp"
android:layout_alignParentBottom="true"
android:layout_centerVertical="true">
<RelativeLayout style="@style/AddressBar.Button"
android:layout_toRightOf="@id/tabs"
android:layout_toLeftOf="@id/menu_items"
android:layout_marginLeft="-28dp"
android:layout_alignParentBottom="true"
android:layout_centerVertical="true">
<Button android:id="@+id/awesome_bar"
style="@style/AddressBar.Button"
android:layout_marginLeft="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:layout_marginRight="0dp"
android:background="@drawable/address_bar_url_level"/>
<RelativeLayout android:id="@+id/awesome_bar"
style="@style/AddressBar.Button"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true">
<ImageView style="@style/AddressBar.Button"
android:layout_marginLeft="20dp"
android:layout_marginRight="0dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:duplicateParentState="true"
android:clickable="false"
android:focusable="false"
android:background="@drawable/address_bar_url"/>
<FrameLayout android:id="@+id/awesome_bar_right_edge"
style="@style/AddressBar.ImageButton"
android:layout_width="25dp"
android:layout_height="fill_parent"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:duplicateParentState="true"
android:background="@drawable/address_bar_bg">
<ImageView android:layout_width="50dp"
android:layout_height="fill_parent"
android:scaleType="fitXY"
android:layout_marginLeft="-26dp"
android:duplicateParentState="true"
android:clickable="false"
android:focusable="false"
android:src="@drawable/address_bar_url"/>
</FrameLayout>
</RelativeLayout>
<ImageButton android:id="@+id/forward"
style="@style/AddressBar.ImageButton"
@ -73,7 +108,7 @@
android:layout_height="40dip"
android:layout_marginLeft="22dp"
android:paddingLeft="22dp"
android:layout_gravity="center_vertical"
android:layout_centerVertical="true"
android:src="@drawable/ic_menu_forward"
android:contentDescription="@string/forward"
android:background="@drawable/address_bar_forward_button"/>
@ -82,7 +117,7 @@
style="@style/AddressBar.ImageButton"
android:layout_width="50dip"
android:layout_height="50dip"
android:layout_gravity="center_vertical"
android:layout_centerVertical="true"
android:src="@drawable/ic_menu_back"
android:contentDescription="@string/back"
android:background="@drawable/address_bar_back_button"/>
@ -139,7 +174,7 @@
</LinearLayout>
</FrameLayout>
</RelativeLayout>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"