mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-20 20:12:52 +08:00
143 lines
5.8 KiB
Java
143 lines
5.8 KiB
Java
/*
|
|
* Copyright 2022 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 java.lang.Math.floor;
|
|
import static java.lang.Math.max;
|
|
|
|
import android.content.Context;
|
|
import androidx.annotation.FloatRange;
|
|
import androidx.annotation.NonNull;
|
|
|
|
/**
|
|
* A class that knows how to size and fit large, medium, small, and extra small items into a
|
|
* container to create a layout for quick browsing of multiple items at once.
|
|
*
|
|
* <p>Large items are items that are fully unmasked and in focus. Large items use the full size set
|
|
* by the client for items in the Carousel. Medium items are items that can vary in size and are
|
|
* used used to fill extra space in the container when a whole number of large and small items
|
|
* cannot fit. Small items are masked slices that appear and disappear at the edges of the container
|
|
* before unmasking into large items.
|
|
*/
|
|
public abstract class MultiBrowseCarouselConfiguration extends CarouselConfiguration {
|
|
|
|
MultiBrowseCarouselConfiguration(@NonNull Carousel carousel) {
|
|
super(carousel);
|
|
}
|
|
|
|
protected float getSmallSize(@NonNull Context context) {
|
|
return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size);
|
|
}
|
|
|
|
protected float getExtraSmallSize(@NonNull Context context) {
|
|
return context.getResources().getDimension(R.dimen.m3_carousel_extra_small_item_size);
|
|
}
|
|
|
|
/**
|
|
* Gets the total available space to be filled with items inside the carousel.
|
|
*
|
|
* <p>This is the width of the carousel minus any paddings.
|
|
*/
|
|
protected float getTotalAvailableSpace() {
|
|
return getCarousel().getContainerWidth()
|
|
- (getCarousel().getContainerPaddingStart() + getCarousel().getContainerPaddingEnd());
|
|
}
|
|
|
|
/**
|
|
* Fits the maximum number of large items into {@code availableSpace} that creates a pleasing
|
|
* visual layout.
|
|
*
|
|
* <p>Valid combinations that fill {@code availableSpace} include a single large item or any
|
|
* number of large items plus medium and small items. The arrangement is determined by the size of
|
|
* {@code largeChildSize} and the way it divides into {@code availableSpace}.
|
|
*
|
|
* @param availableSpace The amount of space available to be occupied by large items. The total
|
|
* width taken up by large items must not be larger than this available space.
|
|
* @param largeChildSize The size of a single large item.
|
|
* @param smallChildSize The size needed for a small item
|
|
* @param mediumChildSize The size needed for a medium item
|
|
* @return The number of expanded items for this configuration.
|
|
*/
|
|
protected static int getLargeCountForAvailableSpace(
|
|
float availableSpace, float largeChildSize, float smallChildSize, float mediumChildSize) {
|
|
if (largeChildSize == availableSpace) {
|
|
// There is space for exactly 1 large item. Fill the entire available space.
|
|
return 1;
|
|
}
|
|
// Determine the number of large items that can fit within the given space.
|
|
float largeFill = availableSpace / largeChildSize;
|
|
float largeRemainder = availableSpace % largeChildSize;
|
|
if (largeRemainder == smallChildSize
|
|
|| largeRemainder >= (smallChildSize + mediumChildSize)
|
|
|| largeFill < 2) {
|
|
// The remaining space after flooring the largeFill is enough to fit exactly one
|
|
// small item OR one small item plus one medium item.
|
|
return (int) floor(largeFill);
|
|
} else {
|
|
// Remove the overflowed large item plus an additional item to make room for small or
|
|
// medium items.
|
|
// TODO(b/239845088): Create strategy for resizing items for a pleasing layout.
|
|
return (int) floor(largeFill - 1F);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fits the maximum number of small items into {@code availableSpace} that creates a pleasing
|
|
* visual layout.
|
|
*
|
|
* @param availableSpace The amount of space available to be occupied by small items.
|
|
* @param smallChildSize The size of a single small item.
|
|
* @return The number of small items for this configuration.
|
|
*/
|
|
protected static int getSmallCountForAvailableSpace(float availableSpace, float smallChildSize) {
|
|
if (availableSpace < smallChildSize) {
|
|
return 0;
|
|
}
|
|
// Determine the number of small items that can fit within the remaining available space.
|
|
float smallFill = availableSpace / smallChildSize;
|
|
float smallRemainder = availableSpace % smallChildSize;
|
|
if (smallRemainder == 0F) {
|
|
// There is exactly enough space for a whole number of small items
|
|
return (int) smallFill;
|
|
} else {
|
|
// There is a mix of small and other items. Remove the remainder and return the fill
|
|
// count.
|
|
return (int) max(0, floor(smallFill - 1F));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets whether a medium item should be used for this configuration.
|
|
*
|
|
* <p>Only one medium item should ever be used on either size of a focal range.
|
|
*
|
|
* @param availableSpace The amount of space available to be occupied by medium items.
|
|
* @return true if a medium item should be used for this configuration
|
|
*/
|
|
protected static boolean shouldShowMediumItem(float availableSpace) {
|
|
return availableSpace > 0;
|
|
}
|
|
|
|
@FloatRange(from = 0F, to = 1F)
|
|
protected static float getChildMaskPercentage(
|
|
float maskedSize, float unmaskedSize, float childMargins) {
|
|
return 1F - ((maskedSize - childMargins) / (unmaskedSize - childMargins));
|
|
}
|
|
}
|