mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-16 09:52:53 +08:00
215 lines
8.5 KiB
Java
215 lines
8.5 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 android.content.Context;
|
|
import android.view.View;
|
|
import androidx.annotation.FloatRange;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.RestrictTo;
|
|
import androidx.annotation.RestrictTo.Scope;
|
|
|
|
/**
|
|
* A class responsible for creating a model used by a carousel to mask and offset views as they move
|
|
* along a scrolling axis.
|
|
*/
|
|
public abstract class CarouselStrategy {
|
|
|
|
private float smallSizeMin;
|
|
|
|
private float smallSizeMax;
|
|
|
|
/**
|
|
* Enum that defines whether or not the strategy is contained or uncontained. Contained strategies
|
|
* will always have all of its items within bounds of the carousel width.
|
|
*/
|
|
enum StrategyType {
|
|
CONTAINED,
|
|
UNCONTAINED
|
|
}
|
|
|
|
void initialize(Context context) {
|
|
smallSizeMin =
|
|
smallSizeMin > 0 ? smallSizeMin : CarouselStrategyHelper.getSmallSizeMin(context);
|
|
smallSizeMax =
|
|
smallSizeMax > 0 ? smallSizeMax : CarouselStrategyHelper.getSmallSizeMax(context);
|
|
}
|
|
|
|
/**
|
|
* Calculates a keyline arrangement and returns a constructed {@link KeylineState}.
|
|
*
|
|
* <p>This method is called when {@link Carousel} measures the first item to be added to its
|
|
* scroll container. This method then must create a {@link KeylineState} which tells {@link
|
|
* Carousel} how to fill the scroll container with items - how many are visible at once, what
|
|
* their sizes are, and where they're placed.
|
|
*
|
|
* <p>For example, take a simple arrangement that fills the scroll container with two large items
|
|
* and one small item. As the user scrolls the first large item moves off-screen to the left, the
|
|
* second large item moves to position 1, the small item unmasks into a large item at position 2,
|
|
* and a new small item scrolls into view from the right. To create this arrangement, pick any
|
|
* size for the small item that will be smaller than the large item. Next, take the carousel's
|
|
* total space, subtract the small item size and divide the remainder by two - this is your large
|
|
* item size. After determining the size of our large and small items, we can now construct a
|
|
* {@link KeylineState} and add keylines representing each item:
|
|
*
|
|
* <pre>
|
|
*
|
|
* // Find the centers of the items in our arrangement, aligning the first item's left with the
|
|
* // left of the scroll container (0).
|
|
* float firstLargeItemCenter = largeChildSize / 2F;
|
|
* float smallItemCenter = (largeChildSize * 2F) + (smallChildSize / 2F);
|
|
*
|
|
* // Get our child margins to use when calculating mask percentage
|
|
* LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
|
|
* float childMargins = childLayoutParams.leftMargin + childLayoutParams.rightMargin;
|
|
*
|
|
* return new KeylineState.Builder(largeChildWidth)
|
|
* .addKeylineRange(
|
|
* firstLargeItemCenter, // offsetLoc
|
|
* getChildMaskPercentage(largeChildSize, largeChildSize, childMargins), // mask
|
|
* largeChildSize, // maskedItemSize
|
|
* 2, // count
|
|
* true) // isFocal
|
|
* .addKeyline(
|
|
* smallItemCenter, // offsetLoc
|
|
* getChildMaskPercentage(smallChildSize, largeChildSize, childMargins), // mask
|
|
* smallChildSize); // maskedItemSize
|
|
*
|
|
* </pre>
|
|
*
|
|
* <p>A strategy does not need to take layout direction into account. {@link
|
|
* CarouselLayoutManager} automatically reverses the strategy's {@link KeylineState} when laid out
|
|
* in right-to-left. Additionally, {@link CarouselLayoutManager} shifts the focal keylines to the
|
|
* start or end of the container when at the start or end of a list in order to allow every item
|
|
* in the list to pass through the focal state.
|
|
*
|
|
* <p>For additional guidelines on constructing valid KeylineStates, see {@link
|
|
* KeylineState.Builder}.
|
|
*
|
|
* @param carousel The carousel to create a {@link KeylineState} for
|
|
* @param child The first measured view from the carousel.
|
|
* @return A {@link KeylineState} to be used by the layout manager to offset and mask children
|
|
* along the scrolling axis.
|
|
*/
|
|
@NonNull
|
|
@RestrictTo(Scope.LIBRARY_GROUP)
|
|
public abstract KeylineState onFirstChildMeasuredWithMargins(
|
|
@NonNull Carousel carousel, @NonNull View child);
|
|
|
|
/**
|
|
* Helper method to calculate a child's mask percentage given its masked size, unmasked size, and
|
|
* margins.
|
|
*
|
|
* @param maskedSize The size this method calculates a mask percentage for
|
|
* @param unmaskedSize The size this child is when fully unmasked (mask == 0F). This should likely
|
|
* be the {@code itemSize} passed to the {@link KeylineState.Builder} constructor.
|
|
* @param childMargins The total margins at the start+end or top+bottom of this child. By default,
|
|
* these are removed from the returned mask as margins should not change in size as a child's
|
|
* mask changes.
|
|
* @return A percentage by which the child should be masked in order to be sized at {@code
|
|
* maskedSize}. 0F is fully unmasked and 1F is fully masked.
|
|
*/
|
|
@FloatRange(from = 0F, to = 1F)
|
|
public static float getChildMaskPercentage(
|
|
float maskedSize, float unmaskedSize, float childMargins) {
|
|
return 1F - ((maskedSize - childMargins) / (unmaskedSize - childMargins));
|
|
}
|
|
|
|
/**
|
|
* Helper method to return an array that doubles every number in the given array.
|
|
*
|
|
* @param count the array containing numbers to be doubled
|
|
* @return A new array that doubles each number in the original count array
|
|
*/
|
|
static int[] doubleCounts(int[] count) {
|
|
int[] doubledCount = new int[count.length];
|
|
for (int i = 0; i < doubledCount.length; i++) {
|
|
doubledCount[i] = count[i] * 2;
|
|
}
|
|
return doubledCount;
|
|
}
|
|
|
|
/**
|
|
* Gets the strategy type of this strategy. Contained strategies should mask items against the
|
|
* edges of the carousel container.
|
|
*
|
|
* @return the {@link StrategyType} of this strategy. A value of {@link StrategyType#CONTAINED}
|
|
* means items in the carousel should mask/squash against the edges of the carousel container.
|
|
* {@link StrategyType#UNCONTAINED} means the carousel should allow items to bleed past the edges
|
|
* of the container and be clipped.
|
|
*/
|
|
StrategyType getStrategyType() {
|
|
return StrategyType.CONTAINED;
|
|
}
|
|
|
|
/**
|
|
* Whether or not the strategy keylines should be refreshed based on the old item count and the
|
|
* carousel's current parameters. This method is called when the item count is updated, and is
|
|
* used to update the keyline strategy when the item count is less than the number of keylines in
|
|
* the normal keyline strategy.
|
|
*
|
|
* @return true if the keylines should be refreshed.
|
|
*/
|
|
@RestrictTo(Scope.LIBRARY_GROUP)
|
|
public boolean shouldRefreshKeylineState(@NonNull Carousel carousel, int oldItemCount) {
|
|
// TODO: b/301332183 - Update existing strategies with logic on when to refresh keyline
|
|
// state based on item count.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Sets the minimum size for the small items.
|
|
*
|
|
* <p> This method is a no-op for strategies that do not have small items.
|
|
*
|
|
* <p> Note that setting this size may impact other sizes in the carousel
|
|
* in order to fit the carousel strategy configuration.
|
|
* @param minSmallItemSize size to set the small item to.
|
|
*/
|
|
public void setSmallItemSizeMin(float minSmallItemSize) {
|
|
smallSizeMin = minSmallItemSize;
|
|
}
|
|
|
|
/**
|
|
* Sets the maximum size for the small items.
|
|
*
|
|
* <p> This method is a no-op for strategies that do not have small items.
|
|
*
|
|
* <p> Note that setting this size may impact other sizes in the carousel
|
|
* in order to fit the carousel strategy configuration.
|
|
* @param maxSmallItemSize size to set the small item to.
|
|
*/
|
|
public void setSmallItemSizeMax(float maxSmallItemSize) {
|
|
smallSizeMax = maxSmallItemSize;
|
|
}
|
|
|
|
/**
|
|
* Returns the minimum small item size value.
|
|
*/
|
|
public float getSmallItemSizeMin() {
|
|
return smallSizeMin;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the maximum small item size value.
|
|
*/
|
|
public float getSmallItemSizeMax() {
|
|
return smallSizeMax;
|
|
}
|
|
}
|