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. 4 5package org.chromium.android_webview; 6 7import android.view.View; 8import android.view.View.MeasureSpec; 9 10/** 11 * Helper methods used to manage the layout of the View that contains AwContents. 12 */ 13public 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; 18 19 // Size of the rendered content, as reported by native. 20 private int mContentHeightCss; 21 private int mContentWidthCss; 22 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; 26 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; 31 32 private double mDIPScale; 33 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; 38 39 // Callback object for interacting with the View. 40 private Delegate mDelegate; 41 42 /** 43 * Delegate interface through which the AwLayoutSizer communicates with the view it's sizing. 44 */ 45 public interface Delegate { 46 void requestLayout(); 47 void setMeasuredDimension(int measuredWidth, int measuredHeight); 48 boolean isLayoutParamsHeightWrapContent(); 49 void setForceZeroLayoutHeight(boolean forceZeroHeight); 50 } 51 52 /** 53 * Default constructor. Note: both setDelegate and setDIPScale must be called before the class 54 * is ready for use. 55 */ 56 public AwLayoutSizer() { 57 } 58 59 public void setDelegate(Delegate delegate) { 60 mDelegate = delegate; 61 } 62 63 public void setDIPScale(double dipScale) { 64 mDIPScale = dipScale; 65 } 66 67 /** 68 * Postpone requesting layouts till unfreezeLayoutRequests is called. 69 */ 70 public void freezeLayoutRequests() { 71 mFreezeLayoutRequests = true; 72 mFrozenLayoutRequestPending = false; 73 } 74 75 /** 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. 78 */ 79 public void unfreezeLayoutRequests() { 80 mFreezeLayoutRequests = false; 81 if (mFrozenLayoutRequestPending) { 82 mFrozenLayoutRequestPending = false; 83 mDelegate.requestLayout(); 84 } 85 } 86 87 /** 88 * Update the contents size. 89 * This should be called whenever the content size changes (due to DOM manipulation or page 90 * load, for example). 91 * The width and height should be in CSS pixels. 92 */ 93 public void onContentSizeChanged(int widthCss, int heightCss) { 94 doUpdate(widthCss, heightCss, mPageScaleFactor); 95 } 96 97 /** 98 * Update the contents page scale. 99 * This should be called whenever the content page scale factor changes (due to pinch zoom, for 100 * example). 101 */ 102 public void onPageScaleChanged(float pageScaleFactor) { 103 doUpdate(mContentWidthCss, mContentHeightCss, pageScaleFactor); 104 } 105 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); 119 120 mContentWidthCss = widthCss; 121 mContentHeightCss = heightCss; 122 mPageScaleFactor = pageScaleFactor; 123 124 if (layoutNeeded) { 125 if (mFreezeLayoutRequests) { 126 mFrozenLayoutRequestPending = true; 127 } else { 128 mDelegate.requestLayout(); 129 } 130 } 131 } 132 133 /** 134 * Calculate the size of the view. 135 * This is designed to be used to implement the android.view.View#onMeasure() method. 136 */ 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); 142 143 int contentHeightPix = (int) (mContentHeightCss * mPageScaleFactor * mDIPScale); 144 int contentWidthPix = (int) (mContentWidthCss * mPageScaleFactor * mDIPScale); 145 146 int measuredHeight = contentHeightPix; 147 int measuredWidth = contentWidthPix; 148 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; 155 156 if (mHeightMeasurementIsFixed || mHeightMeasurementLimited) { 157 measuredHeight = heightSize; 158 } 159 160 if (mWidthMeasurementIsFixed) { 161 measuredWidth = widthSize; 162 } 163 164 if (measuredHeight < contentHeightPix) { 165 measuredHeight |= View.MEASURED_STATE_TOO_SMALL; 166 } 167 168 if (measuredWidth < contentWidthPix) { 169 measuredWidth |= View.MEASURED_STATE_TOO_SMALL; 170 } 171 172 mDelegate.setMeasuredDimension(measuredWidth, measuredHeight); 173 } 174 175 /** 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 178 * changed. 179 */ 180 public void onSizeChanged(int w, int h, int ow, int oh) { 181 updateLayoutSettings(); 182 } 183 184 /** 185 * Notify the AwLayoutSizer that the layout pass requested via Delegate.requestLayout has 186 * completed. 187 * This should be called after onSizeChanged regardless of whether the size has changed or not. 188 */ 189 public void onLayoutChange() { 190 updateLayoutSettings(); 191 } 192 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()); 197 } 198} 199