mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-16 09:52:53 +08:00
281 lines
10 KiB
Java
281 lines
10 KiB
Java
/*
|
|
* Copyright 2023 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.google.android.material.carousel;
|
|
|
|
import com.google.android.material.R;
|
|
|
|
import static com.google.android.material.carousel.CarouselStrategy.getChildMaskPercentage;
|
|
import static java.lang.Math.max;
|
|
import static java.lang.Math.min;
|
|
|
|
import android.content.Context;
|
|
import androidx.annotation.NonNull;
|
|
import com.google.android.material.carousel.CarouselLayoutManager.Alignment;
|
|
|
|
/**
|
|
* A helper class with utility methods for {@link CarouselStrategy} implementations.
|
|
*/
|
|
final class CarouselStrategyHelper {
|
|
|
|
private CarouselStrategyHelper() {}
|
|
|
|
static float getExtraSmallSize(@NonNull Context context) {
|
|
return context.getResources().getDimension(R.dimen.m3_carousel_gone_size);
|
|
}
|
|
|
|
static float getSmallSizeMin(@NonNull Context context) {
|
|
return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size_min);
|
|
}
|
|
|
|
static float getSmallSizeMax(@NonNull Context context) {
|
|
return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size_max);
|
|
}
|
|
|
|
static KeylineState createKeylineState(
|
|
@NonNull Context context,
|
|
float childMargins,
|
|
int availableSpace,
|
|
@NonNull Arrangement arrangement,
|
|
@Alignment int alignment) {
|
|
if (alignment == CarouselLayoutManager.ALIGNMENT_CENTER) {
|
|
return createCenterAlignedKeylineState(context, childMargins, availableSpace, arrangement);
|
|
}
|
|
return createLeftAlignedKeylineState(context, childMargins, availableSpace, arrangement);
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link KeylineState} associated with the given parameters.
|
|
*
|
|
* @param context The context used to load resources.
|
|
* @param childHorizontalMargins The child margins to use when calculating mask percentage.
|
|
* @param availableSpace the space that the {@link KeylineState} needs to fit.
|
|
* @param arrangement the {@link Arrangement} to translate into a {@link KeylineState}.
|
|
* @return the {@link KeylineState} associated with the arrangement with the lowest cost
|
|
* according to the item count array priorities and how close it is to the target sizes.
|
|
*/
|
|
static KeylineState createLeftAlignedKeylineState(
|
|
@NonNull Context context,
|
|
float childHorizontalMargins,
|
|
int availableSpace,
|
|
@NonNull Arrangement arrangement) {
|
|
|
|
float extraSmallChildWidth =
|
|
min(getExtraSmallSize(context) + childHorizontalMargins, arrangement.largeSize);
|
|
|
|
float start = 0F;
|
|
float extraSmallHeadCenterX = start - (extraSmallChildWidth / 2F);
|
|
|
|
float largeStartCenterX = addStart(start, arrangement.largeSize, arrangement.largeCount);
|
|
float largeEndCenterX =
|
|
addEnd(largeStartCenterX, arrangement.largeSize, arrangement.largeCount);
|
|
start =
|
|
updateCurPosition(start, largeEndCenterX, arrangement.largeSize, arrangement.largeCount);
|
|
|
|
float mediumCenterX = addStart(start, arrangement.mediumSize, arrangement.mediumCount);
|
|
start =
|
|
updateCurPosition(start, mediumCenterX, arrangement.mediumSize, arrangement.mediumCount);
|
|
|
|
float smallStartCenterX = addStart(start, arrangement.smallSize, arrangement.smallCount);
|
|
|
|
float extraSmallTailCenterX = availableSpace + (extraSmallChildWidth / 2F);
|
|
|
|
float extraSmallMask =
|
|
getChildMaskPercentage(extraSmallChildWidth, arrangement.largeSize, childHorizontalMargins);
|
|
float smallMask =
|
|
getChildMaskPercentage(
|
|
arrangement.smallSize, arrangement.largeSize, childHorizontalMargins);
|
|
float mediumMask =
|
|
getChildMaskPercentage(
|
|
arrangement.mediumSize, arrangement.largeSize, childHorizontalMargins);
|
|
float largeMask = 0F;
|
|
|
|
KeylineState.Builder builder =
|
|
new KeylineState.Builder(arrangement.largeSize, availableSpace)
|
|
.addAnchorKeyline(extraSmallHeadCenterX, extraSmallMask, extraSmallChildWidth)
|
|
.addKeylineRange(
|
|
largeStartCenterX, largeMask, arrangement.largeSize, arrangement.largeCount, true);
|
|
if (arrangement.mediumCount > 0) {
|
|
builder.addKeyline(mediumCenterX, mediumMask, arrangement.mediumSize);
|
|
}
|
|
if (arrangement.smallCount > 0) {
|
|
builder.addKeylineRange(
|
|
smallStartCenterX, smallMask, arrangement.smallSize, arrangement.smallCount);
|
|
}
|
|
builder.addAnchorKeyline(extraSmallTailCenterX, extraSmallMask, extraSmallChildWidth);
|
|
return builder.build();
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link KeylineState} associated with the given parameters, with the focal item
|
|
* in the center.
|
|
*
|
|
* @param context The context used to load resources.
|
|
* @param childHorizontalMargins The child margins to use when calculating mask percentage.
|
|
* @param availableSpace the space that the {@link KeylineState} needs to fit.
|
|
* @param arrangement the {@link Arrangement} to translate into a {@link KeylineState}.
|
|
* @return the {@link KeylineState} associated with the arrangement with the lowest cost
|
|
* according to the item count array priorities and how close it is to the target sizes.
|
|
*/
|
|
static KeylineState createCenterAlignedKeylineState(
|
|
@NonNull Context context,
|
|
float childHorizontalMargins,
|
|
int availableSpace,
|
|
@NonNull Arrangement arrangement) {
|
|
|
|
float extraSmallChildWidth =
|
|
min(getExtraSmallSize(context) + childHorizontalMargins, arrangement.largeSize);
|
|
|
|
float start = 0F;
|
|
float extraSmallHeadCenterX = start - (extraSmallChildWidth / 2F);
|
|
|
|
// Half of the small items
|
|
float halfSmallStartCenterX = addStart(start, arrangement.smallSize, arrangement.smallCount);
|
|
float halfSmallEndCenterX =
|
|
addEnd(
|
|
halfSmallStartCenterX,
|
|
arrangement.smallSize,
|
|
(int) Math.floor((float) arrangement.smallCount / 2F));
|
|
start =
|
|
updateCurPosition(
|
|
start, halfSmallEndCenterX, arrangement.smallSize, arrangement.smallCount);
|
|
|
|
// Half of the medium items
|
|
float halfMediumStartCenterX = addStart(start, arrangement.mediumSize, arrangement.mediumCount);
|
|
float halfMediumEndCenterX =
|
|
addEnd(
|
|
halfMediumStartCenterX,
|
|
arrangement.mediumSize,
|
|
(int) Math.floor((float) arrangement.mediumCount / 2F));
|
|
start =
|
|
updateCurPosition(
|
|
start, halfMediumEndCenterX, arrangement.mediumSize, arrangement.mediumCount);
|
|
|
|
float largeStartCenterX = addStart(start, arrangement.largeSize, arrangement.largeCount);
|
|
float largeEndCenterX =
|
|
addEnd(largeStartCenterX, arrangement.largeSize, arrangement.largeCount);
|
|
start =
|
|
updateCurPosition(start, largeEndCenterX, arrangement.largeSize, arrangement.largeCount);
|
|
|
|
float secondHalfMediumStartCenterX =
|
|
addStart(start, arrangement.mediumSize, arrangement.mediumCount);
|
|
float secondHalfMediumEndCenterX =
|
|
addEnd(
|
|
secondHalfMediumStartCenterX,
|
|
arrangement.mediumSize,
|
|
(int) Math.ceil((float) arrangement.mediumCount / 2F));
|
|
start =
|
|
updateCurPosition(
|
|
start, secondHalfMediumEndCenterX, arrangement.mediumSize, arrangement.mediumCount);
|
|
|
|
float secondHalfSmallStartCenterX =
|
|
addStart(start, arrangement.smallSize, arrangement.smallCount);
|
|
|
|
float extraSmallTailCenterX = availableSpace + (extraSmallChildWidth / 2F);
|
|
|
|
float extraSmallMask =
|
|
getChildMaskPercentage(extraSmallChildWidth, arrangement.largeSize, childHorizontalMargins);
|
|
float smallMask =
|
|
getChildMaskPercentage(
|
|
arrangement.smallSize, arrangement.largeSize, childHorizontalMargins);
|
|
float mediumMask =
|
|
getChildMaskPercentage(
|
|
arrangement.mediumSize, arrangement.largeSize, childHorizontalMargins);
|
|
float largeMask = 0F;
|
|
|
|
KeylineState.Builder builder =
|
|
new KeylineState.Builder(arrangement.largeSize, availableSpace)
|
|
.addAnchorKeyline(extraSmallHeadCenterX, extraSmallMask, extraSmallChildWidth);
|
|
if (arrangement.smallCount > 0) {
|
|
builder.addKeylineRange(
|
|
halfSmallStartCenterX,
|
|
smallMask,
|
|
arrangement.smallSize,
|
|
(int) Math.floor(arrangement.smallCount / 2F));
|
|
}
|
|
if (arrangement.mediumCount > 0) {
|
|
builder.addKeylineRange(
|
|
halfMediumStartCenterX,
|
|
mediumMask,
|
|
arrangement.mediumSize,
|
|
(int) Math.floor(arrangement.mediumCount / 2F));
|
|
}
|
|
|
|
builder.addKeylineRange(
|
|
largeStartCenterX, largeMask, arrangement.largeSize, arrangement.largeCount, true);
|
|
|
|
if (arrangement.mediumCount > 0) {
|
|
builder.addKeylineRange(
|
|
secondHalfMediumStartCenterX,
|
|
mediumMask,
|
|
arrangement.mediumSize,
|
|
(int) Math.ceil(arrangement.mediumCount / 2F));
|
|
}
|
|
|
|
if (arrangement.smallCount > 0) {
|
|
builder.addKeylineRange(
|
|
secondHalfSmallStartCenterX,
|
|
smallMask,
|
|
arrangement.smallSize,
|
|
(int) Math.ceil(arrangement.smallCount / 2F));
|
|
}
|
|
|
|
builder.addAnchorKeyline(extraSmallTailCenterX, extraSmallMask, extraSmallChildWidth);
|
|
return builder.build();
|
|
}
|
|
|
|
static int maxValue(int[] array) {
|
|
int largest = Integer.MIN_VALUE;
|
|
for (int j : array) {
|
|
if (j > largest) {
|
|
largest = j;
|
|
}
|
|
}
|
|
|
|
return largest;
|
|
}
|
|
|
|
/**
|
|
* Helper method to calculate the keyline position of the start of the item block according to the
|
|
* start position, item size, and item count.
|
|
*/
|
|
static float addStart(float start, float itemSize, int count) {
|
|
if (count > 0) {
|
|
return start + itemSize / 2F;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
/**
|
|
* Helper method to calculate the keyline position of the end of the item block according to the
|
|
* start position, item size, and item count.
|
|
*/
|
|
static float addEnd(float startKeylinePos, float itemSize, int count) {
|
|
return startKeylinePos + (max(0, count - 1) * itemSize);
|
|
}
|
|
|
|
/**
|
|
* Helper method to update the current position of the keyline calculations after the last keyline
|
|
* in the item range.
|
|
*/
|
|
static float updateCurPosition(
|
|
float curPosition, float lastEndKeyline, float itemSize, int count) {
|
|
if (count > 0) {
|
|
return lastEndKeyline + itemSize / 2F;
|
|
}
|
|
return curPosition;
|
|
}
|
|
}
|