1 // Copyright 2012 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.
5 package org.chromium.android_webview;
7 import android.view.View;
8 import android.view.View.MeasureSpec;
11 * Helper methods used to manage the layout of the View that contains AwContents.
13 public class AwLayoutSizer {
14 // These are used to prevent a re-layout if the content size changes within a dimension that is
15 // fixed by the view system.
16 private boolean mWidthMeasurementIsFixed;
17 private boolean mHeightMeasurementIsFixed;
19 // Size of the rendered content, as reported by native.
20 private int mContentHeightCss;
21 private int mContentWidthCss;
23 // Page scale factor. This is set to zero initially so that we don't attempt to do a layout if
24 // we get the content size change notification first and a page scale change second.
25 private float mPageScaleFactor = 0.0f;
27 // Whether to postpone layout requests.
28 private boolean mFreezeLayoutRequests;
29 // Did we try to request a layout since the last time mPostponeLayoutRequests was set to true.
30 private boolean mFrozenLayoutRequestPending;
32 private double mDIPScale;
34 // Was our height larger than the AT_MOST constraint the last time onMeasure was called?
35 private boolean mHeightMeasurementLimited;
36 // If mHeightMeasurementLimited is true then this contains the height limit.
37 private int mHeightMeasurementLimit;
39 // Callback object for interacting with the View.
40 private Delegate mDelegate;
43 * Delegate interface through which the AwLayoutSizer communicates with the view it's sizing.
45 public interface Delegate {
47 void setMeasuredDimension(int measuredWidth, int measuredHeight);
48 boolean isLayoutParamsHeightWrapContent();
49 void setForceZeroLayoutHeight(boolean forceZeroHeight);
53 * Default constructor. Note: both setDelegate and setDIPScale must be called before the class
56 public AwLayoutSizer() {
59 public void setDelegate(Delegate delegate) {
63 public void setDIPScale(double dipScale) {
68 * Postpone requesting layouts till unfreezeLayoutRequests is called.
70 public void freezeLayoutRequests() {
71 mFreezeLayoutRequests = true;
72 mFrozenLayoutRequestPending = false;
76 * Stop postponing layout requests and request layout if such a request would have been made
77 * had the freezeLayoutRequests method not been called before.
79 public void unfreezeLayoutRequests() {
80 mFreezeLayoutRequests = false;
81 if (mFrozenLayoutRequestPending) {
82 mFrozenLayoutRequestPending = false;
83 mDelegate.requestLayout();
88 * Update the contents size.
89 * This should be called whenever the content size changes (due to DOM manipulation or page
91 * The width and height should be in CSS pixels.
93 public void onContentSizeChanged(int widthCss, int heightCss) {
94 doUpdate(widthCss, heightCss, mPageScaleFactor);
98 * Update the contents page scale.
99 * This should be called whenever the content page scale factor changes (due to pinch zoom, for
102 public void onPageScaleChanged(float pageScaleFactor) {
103 doUpdate(mContentWidthCss, mContentHeightCss, pageScaleFactor);
106 private void doUpdate(int widthCss, int heightCss, float pageScaleFactor) {
107 // We want to request layout only if the size or scale change, however if any of the
108 // measurements are 'fixed', then changing the underlying size won't have any effect, so we
109 // ignore changes to dimensions that are 'fixed'.
110 final int heightPix = (int) (heightCss * mPageScaleFactor * mDIPScale);
111 boolean pageScaleChanged = mPageScaleFactor != pageScaleFactor;
112 boolean contentHeightChangeMeaningful = !mHeightMeasurementIsFixed &&
113 (!mHeightMeasurementLimited || heightPix < mHeightMeasurementLimit);
114 boolean pageScaleChangeMeaningful =
115 !mWidthMeasurementIsFixed || contentHeightChangeMeaningful;
116 boolean layoutNeeded = (mContentWidthCss != widthCss && !mWidthMeasurementIsFixed) ||
117 (mContentHeightCss != heightCss && contentHeightChangeMeaningful) ||
118 (pageScaleChanged && pageScaleChangeMeaningful);
120 mContentWidthCss = widthCss;
121 mContentHeightCss = heightCss;
122 mPageScaleFactor = pageScaleFactor;
125 if (mFreezeLayoutRequests) {
126 mFrozenLayoutRequestPending = true;
128 mDelegate.requestLayout();
134 * Calculate the size of the view.
135 * This is designed to be used to implement the android.view.View#onMeasure() method.
137 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
138 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
139 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
140 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
141 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
143 int contentHeightPix = (int) (mContentHeightCss * mPageScaleFactor * mDIPScale);
144 int contentWidthPix = (int) (mContentWidthCss * mPageScaleFactor * mDIPScale);
146 int measuredHeight = contentHeightPix;
147 int measuredWidth = contentWidthPix;
149 // Always use the given size unless unspecified. This matches WebViewClassic behavior.
150 mWidthMeasurementIsFixed = (widthMode != MeasureSpec.UNSPECIFIED);
151 mHeightMeasurementIsFixed = (heightMode == MeasureSpec.EXACTLY);
152 mHeightMeasurementLimited =
153 (heightMode == MeasureSpec.AT_MOST) && (contentHeightPix > heightSize);
154 mHeightMeasurementLimit = heightSize;
156 if (mHeightMeasurementIsFixed || mHeightMeasurementLimited) {
157 measuredHeight = heightSize;
160 if (mWidthMeasurementIsFixed) {
161 measuredWidth = widthSize;
164 if (measuredHeight < contentHeightPix) {
165 measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
168 if (measuredWidth < contentWidthPix) {
169 measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
172 mDelegate.setMeasuredDimension(measuredWidth, measuredHeight);
176 * Notify the AwLayoutSizer that the size of the view has changed.
177 * This should be called by the Android view system after onMeasure if the view's size has
180 public void onSizeChanged(int w, int h, int ow, int oh) {
181 updateLayoutSettings();
185 * Notify the AwLayoutSizer that the layout pass requested via Delegate.requestLayout has
187 * This should be called after onSizeChanged regardless of whether the size has changed or not.
189 public void onLayoutChange() {
190 updateLayoutSettings();
193 // This needs to be called every time either the physical size of the view is changed or layout
194 // params are updated.
195 private void updateLayoutSettings() {
196 mDelegate.setForceZeroLayoutHeight(mDelegate.isLayoutParamsHeightWrapContent());