/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.litho.utils;
import android.view.View.MeasureSpec;
import android.util.Log;
import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.litho.Size;
import com.facebook.litho.SizeSpec;
import static com.facebook.litho.SizeSpec.AT_MOST;
import static com.facebook.litho.SizeSpec.EXACTLY;
import static com.facebook.litho.SizeSpec.UNSPECIFIED;
import static com.facebook.litho.SizeSpec.getMode;
import static com.facebook.litho.SizeSpec.getSize;
public final class MeasureUtils {
public static int getViewMeasureSpec(int sizeSpec) {
switch (getMode(sizeSpec)) {
case SizeSpec.EXACTLY:
return MeasureSpec.makeMeasureSpec(getSize(sizeSpec), MeasureSpec.EXACTLY);
case SizeSpec.AT_MOST:
return MeasureSpec.makeMeasureSpec(getSize(sizeSpec), MeasureSpec.AT_MOST);
case SizeSpec.UNSPECIFIED:
return MeasureSpec.makeMeasureSpec(getSize(sizeSpec), MeasureSpec.UNSPECIFIED);
default:
throw new IllegalStateException("Unexpected size spec mode");
}
}
/**
* Set the {@param outputSize} to respect both Specs and the desired width and height.
* The desired size is usually the necessary pixels to render the inner content.
*/
public static void measureWithDesiredPx(
int widthSpec,
int heightSpec,
int desiredWidthPx,
int desiredHeightPx,
Size outputSize) {
outputSize.width = getResultSizePxWithSpecAndDesiredPx(widthSpec, desiredWidthPx);
outputSize.height = getResultSizePxWithSpecAndDesiredPx(heightSpec, desiredHeightPx);
}
private static int getResultSizePxWithSpecAndDesiredPx(int spec, int desiredSize) {
final int mode = SizeSpec.getMode(spec);
switch (mode) {
case SizeSpec.UNSPECIFIED:
return desiredSize;
case SizeSpec.AT_MOST:
return Math.min(SizeSpec.getSize(spec), desiredSize);
case SizeSpec.EXACTLY:
return SizeSpec.getSize(spec);
default:
throw new IllegalStateException("Unexpected size spec mode");
}
}
/**
* Set the {@param outputSize} to respect both Specs and try to keep both width and height equal.
* This will only not guarantee equal width and height if thes Specs use modes and sizes which
* prevent it.
*/
public static void measureWithEqualDimens(int widthSpec, int heightSpec, Size outputSize) {
final int widthMode = SizeSpec.getMode(widthSpec);
final int widthSize = SizeSpec.getSize(widthSpec);
final int heightMode = SizeSpec.getMode(heightSpec);
final int heightSize = SizeSpec.getSize(heightSpec);
if (widthMode == UNSPECIFIED && heightMode == UNSPECIFIED) {
outputSize.width = 0;
outputSize.height = 0;
if (ComponentsConfiguration.IS_INTERNAL_BUILD) {
Log.d(
"com.facebook.litho.utils.MeasureUtils",
"Default to size {0, 0} because both width and height are UNSPECIFIED");
}
return;
}
if (widthMode == EXACTLY) {
outputSize.width = widthSize;
switch (heightMode) {
case EXACTLY:
outputSize.height = heightSize;
return;
case AT_MOST:
outputSize.height = Math.min(widthSize, heightSize);
return;
case UNSPECIFIED:
outputSize.height = widthSize;
return;
}
} else if (widthMode == AT_MOST) {
switch (heightMode) {
case EXACTLY:
outputSize.height = heightSize;
outputSize.width = Math.min(widthSize, heightSize);
return;
case AT_MOST:
// if both are AT_MOST, choose the smaller one to keep width and height equal
final int chosenSize = Math.min(widthSize, heightSize);
outputSize.width = chosenSize;
outputSize.height = chosenSize;
return;
case UNSPECIFIED:
outputSize.width = widthSize;
outputSize.height = widthSize;
return;
}
}
// heightMode is either EXACTLY or AT_MOST, and widthMode is UNSPECIFIED
outputSize.height = heightSize;
outputSize.width = heightSize;
}
/**
* Measure according to an aspect ratio an width and height constraints. This version
* of measureWithAspectRatio will respect the intrinsic size of the component being measured.
*
* @param widthSpec A SizeSpec for the width
* @param heightSpec A SizeSpec for the height
* @param intrinsicWidth A pixel value for the intrinsic width of the measured component
* @param intrinsicHeight A pixel value for the intrinsic height of the measured component
* @param aspectRatio The aspect ration size against
* @param outputSize The output size of this measurement
*/
public static void measureWithAspectRatio(
int widthSpec,
int heightSpec,
int intrinsicWidth,
int intrinsicHeight,
float aspectRatio,
Size outputSize) {
if (SizeSpec.getMode(widthSpec) == AT_MOST &&
SizeSpec.getSize(widthSpec) > intrinsicWidth) {
widthSpec = SizeSpec.makeSizeSpec(intrinsicWidth, AT_MOST);
}
if (SizeSpec.getMode(heightSpec) == AT_MOST &&
SizeSpec.getSize(heightSpec) > intrinsicHeight) {
heightSpec = SizeSpec.makeSizeSpec(intrinsicHeight, AT_MOST);
}
measureWithAspectRatio(widthSpec, heightSpec, aspectRatio, outputSize);
}
/**
* Measure according to an aspect ratio an width and height constraints.
*
* @param widthSpec A SizeSpec for the width
* @param heightSpec A SizeSpec for the height
* @param aspectRatio The aspect ration size against
* @param outputSize The output size of this measurement
*/
public static void measureWithAspectRatio(
int widthSpec,
int heightSpec,
float aspectRatio,
Size outputSize) {
final int widthMode = SizeSpec.getMode(widthSpec);
final int widthSize = SizeSpec.getSize(widthSpec);
final int heightMode = SizeSpec.getMode(heightSpec);
final int heightSize = SizeSpec.getSize(heightSpec);
final int widthBasedHeight = (int) Math.ceil(widthSize / aspectRatio);
final int heightBasedWidth = (int) Math.ceil(heightSize * aspectRatio);
if (widthMode == UNSPECIFIED && heightMode == UNSPECIFIED) {
outputSize.width = 0;
outputSize.height = 0;
if (ComponentsConfiguration.IS_INTERNAL_BUILD) {
Log.d(
"com.facebook.litho.utils.MeasureUtils",
"Default to size {0, 0} because both width and height are UNSPECIFIED");
}
return;
}
// Both modes are AT_MOST, find the largest possible size which respects both constraints.
if (widthMode == AT_MOST && heightMode == AT_MOST) {
if (widthBasedHeight > heightSize) {
outputSize.width = heightBasedWidth;
outputSize.height = heightSize;
} else {
outputSize.width = widthSize;
outputSize.height = widthBasedHeight;
}
}
// Width is set to exact measurement and the height is either unspecified or is allowed to be
// large enough to accommodate the given aspect ratio.
else if (widthMode == EXACTLY) {
outputSize.width = widthSize;
if (heightMode == UNSPECIFIED || widthBasedHeight <= heightSize) {
outputSize.height = widthBasedHeight;
} else {
outputSize.height = heightSize;
if (ComponentsConfiguration.IS_INTERNAL_BUILD) {
Log.d(
"com.facebook.litho.utils.MeasureUtils",
String.format(
"Ratio makes height larger than allowed. w:%s h:%s aspectRatio:%f",
SizeSpec.toString(widthSpec),
SizeSpec.toString(heightSpec),
aspectRatio));
}
}
}
// Height is set to exact measurement and the width is either unspecified or is allowed to be
// large enough to accommodate the given aspect ratio.
else if (heightMode == EXACTLY) {
outputSize.height = heightSize;
if (widthMode == UNSPECIFIED || heightBasedWidth <= widthSize) {
outputSize.width = heightBasedWidth;
} else {
outputSize.width = widthSize;
if (ComponentsConfiguration.IS_INTERNAL_BUILD) {
Log.d(
"com.facebook.litho.utils.MeasureUtils",
String.format(
"Ratio makes width larger than allowed. w:%s h:%s aspectRatio:%f",
SizeSpec.toString(widthSpec),
SizeSpec.toString(heightSpec),
aspectRatio));
}
}
}
// Width is set to at most measurement. If that is the case heightMode must be unspecified.
else if (widthMode == AT_MOST) {
outputSize.width = widthSize;
outputSize.height = widthBasedHeight;
}
// Height is set to at most measurement. If that is the case widthMode must be unspecified.
else if (heightMode == AT_MOST) {
outputSize.width = heightBasedWidth;
outputSize.height = heightSize;
}
}
}