mirror of
https://github.com/material-components/material-components-android.git
synced 2026-02-20 08:39:55 +08:00
331 lines
13 KiB
Java
331 lines
13 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
|
|
*
|
|
* http://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 static com.google.android.material.carousel.CarouselHelper.getKeylineMaskPercentage;
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static org.junit.Assert.assertThrows;
|
|
|
|
import com.google.android.material.carousel.KeylineState.Keyline;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.robolectric.RobolectricTestRunner;
|
|
import org.robolectric.annotation.internal.DoNotInstrument;
|
|
|
|
/** Tests for {@link KeylineState}. */
|
|
@RunWith(RobolectricTestRunner.class)
|
|
@DoNotInstrument
|
|
public final class KeylineStateTest {
|
|
|
|
private static final float[] BUILT_LOCS =
|
|
new float[] {-1015F, -565F, -115F, 335F, 785F, 1235F, 1685F, 2135F, 2585F, 3035F, 3485F};
|
|
private static final float[] BUILT_LOC_OFFSETS =
|
|
new float[] {5F, 85F, 235F, 435F, 785F, 1235F, 1685F, 2035F, 2235F, 2385F, 2465F};
|
|
|
|
@Test
|
|
public void testNoFocalRange_throwsException() {
|
|
assertThrows(
|
|
IllegalStateException.class,
|
|
() -> new KeylineState.Builder(100F, 0).addKeyline(0, .5F, 50F).build());
|
|
}
|
|
|
|
@Test
|
|
public void testZeroSizedKeyline_shouldNotAddKeyline() {
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(50F, 0F, 100F, true)
|
|
.addKeyline(100F, 1F, 0F)
|
|
.build();
|
|
assertThat(keylineState.getKeylines()).hasSize(1);
|
|
}
|
|
|
|
@Test
|
|
public void testZeroCountKeylineRange_shouldNotAddKeylines() {
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(50F, 0F, 100F, true)
|
|
.addKeylineRange(110F, .8F, 20F, 0)
|
|
.build();
|
|
assertThat(keylineState.getKeylines()).hasSize(1);
|
|
}
|
|
|
|
@Test
|
|
public void testStartFocalItemWithDifferentSize_shouldThrowException() {
|
|
assertThrows(
|
|
IllegalArgumentException.class,
|
|
() ->
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(25F, .5F, 50F, true)
|
|
.addKeyline(100F, 0F, 100F)
|
|
.addKeyline(200F, 0F, 100F, true)
|
|
.build());
|
|
}
|
|
|
|
@Test
|
|
public void testEndFocalItemWithDifferentSize_shouldThrowException() {
|
|
assertThrows(
|
|
IllegalArgumentException.class,
|
|
() ->
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(50F, 0F, 100F, true)
|
|
.addKeyline(150F, 0F, 100F)
|
|
.addKeyline(275F, .5F, 50F, true)
|
|
.build());
|
|
}
|
|
|
|
@Test
|
|
public void testMiddleFocalItemWithDifferentSize_shouldThrowException() {
|
|
assertThrows(
|
|
IllegalArgumentException.class,
|
|
() ->
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(50F, 0F, 100F, true)
|
|
.addKeyline(125F, .5F, 50F)
|
|
.addKeyline(200F, 0F, 100F, true)
|
|
.build());
|
|
}
|
|
|
|
@Test
|
|
public void testKeylines_areSortedByOffsetLocation() {
|
|
KeylineState keylineState = createKeylineStateBuilder().build();
|
|
assertThat(keylineState.getKeylines())
|
|
.isInOrder((Comparator<Keyline>) (k1, k2) -> (int) (k1.locOffset - k2.locOffset));
|
|
}
|
|
|
|
@Test
|
|
public void testReverseKeylines_shouldReverse() {
|
|
int recyclerWidth = 100;
|
|
// Extra small items are 10F, Small items are 50F, large items are 100F
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, recyclerWidth)
|
|
// left edge of xSmall item is -10 from left edge of carousel container
|
|
.addKeyline(-5F, getKeylineMaskPercentage(10F, 100F), 10F, false, true)
|
|
.addKeyline(50F, 0F, 100F, true)
|
|
.addKeyline(125F, getKeylineMaskPercentage(50F, 100F), 50F)
|
|
// right edge of xSmall item is 60 from right edge of carousel container
|
|
.addKeyline(155F, getKeylineMaskPercentage(10F, 100F), 10F, false, true)
|
|
.build();
|
|
|
|
KeylineState expectedState =
|
|
new KeylineState.Builder(100F, recyclerWidth)
|
|
// left edge of xSmall item is -60 from left of carousel container
|
|
.addKeyline(-55F, getKeylineMaskPercentage(10F, 100F), 10F, false, true)
|
|
.addKeyline(-25F, getKeylineMaskPercentage(50, 100F), 50F)
|
|
.addKeyline(50F, 0F, 100F, true)
|
|
// right edge of xSmall item is 10 from right of carousel container
|
|
.addKeyline(105F, getKeylineMaskPercentage(10F, 100F), 10F, false, true)
|
|
.build();
|
|
KeylineState reversedState = KeylineState.reverse(keylineState, recyclerWidth);
|
|
|
|
assertThat(reversedState.getKeylines()).hasSize(expectedState.getKeylines().size());
|
|
for (int i = 0; i < reversedState.getKeylines().size(); i++) {
|
|
assertThat(reversedState.getKeylines().get(i).locOffset)
|
|
.isEqualTo(expectedState.getKeylines().get(i).locOffset);
|
|
assertThat(reversedState.getKeylines().get(i).loc)
|
|
.isEqualTo(expectedState.getKeylines().get(i).loc);
|
|
assertThat(reversedState.getKeylines().get(i).mask)
|
|
.isEqualTo(expectedState.getKeylines().get(i).mask);
|
|
assertThat(reversedState.getKeylines().get(i).maskedItemSize)
|
|
.isEqualTo(expectedState.getKeylines().get(i).maskedItemSize);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testCenteredArrangement_calculatesOffsetLocations() {
|
|
KeylineState keylineState = createKeylineStateBuilder().build();
|
|
List<Keyline> keylines = keylineState.getKeylines();
|
|
|
|
for (int i = 0; i < keylines.size(); i++) {
|
|
assertThat(keylines.get(i).loc).isEqualTo(BUILT_LOCS[i]);
|
|
assertThat(keylines.get(i).locOffset).isEqualTo(BUILT_LOC_OFFSETS[i]);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testStartArrangement_hasFocalRangeAtFrontOfList() {
|
|
// Create a keyline state that has a [e-e-p-p] arrangement.
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeylineRange(50F, 0F, 100F, 2, true)
|
|
.addKeylineRange(325F, .5F, 50F, 2)
|
|
.build();
|
|
|
|
assertThat(keylineState.getKeylines().get(0).mask).isEqualTo(0F);
|
|
assertThat(keylineState.getKeylines().get(1).mask).isEqualTo(0F);
|
|
}
|
|
|
|
@Test
|
|
public void testAddKeyline_onlyAddsSingleKeyline() {
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, 0).addKeyline(50F, 0F, 100F, true).build();
|
|
assertThat(keylineState.getKeylines()).hasSize(1);
|
|
}
|
|
|
|
@Test
|
|
public void testAddKeylineRange_addsTwoKeylines() {
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, 0).addKeylineRange(50F, 0F, 100F, 2, true).build();
|
|
assertThat(keylineState.getKeylines()).hasSize(2);
|
|
}
|
|
|
|
@Test
|
|
public void testLerpKeylineStates_statesHaveDifferentNumberOfKeylinesShouldThrowException() {
|
|
KeylineState keylineDefaultState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(25F, .5F, 50F)
|
|
.addKeylineRange(100F, 0F, 100F, 2, true)
|
|
.addKeyline(275F, .5F, 50F)
|
|
.build();
|
|
KeylineState keylineStartState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeylineRange(50F, 0F, 100F, 2, true)
|
|
.addKeylineRange(225F, .5F, 50F, 3)
|
|
.build();
|
|
|
|
assertThrows(
|
|
IllegalArgumentException.class,
|
|
() -> KeylineState.lerp(keylineStartState, keylineDefaultState, .5F));
|
|
}
|
|
|
|
@Test
|
|
public void testLerpKeylineStates_focalIndicesShiftAtHalfWayPoint() {
|
|
KeylineState keylineDefaultState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(25F, .5F, 50F)
|
|
.addKeylineRange(100F, 0F, 100F, 2, true)
|
|
.addKeyline(275F, .5F, 50F)
|
|
.build();
|
|
KeylineState keylineStartState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeylineRange(50F, 0F, 100F, 2, true)
|
|
.addKeylineRange(225F, .5F, 50F, 2)
|
|
.build();
|
|
|
|
assertThat(keylineDefaultState.getFirstFocalKeylineIndex()).isEqualTo(1);
|
|
assertThat(keylineDefaultState.getLastFocalKeylineIndex()).isEqualTo(2);
|
|
assertThat(keylineStartState.getFirstFocalKeylineIndex()).isEqualTo(0);
|
|
assertThat(keylineStartState.getLastFocalKeylineIndex()).isEqualTo(1);
|
|
|
|
// Interpolate less than half way between the start state and default state. The focal indices
|
|
// should remain the same (as the start state) until interpolating half way (< .5F).
|
|
KeylineState lerpedState = KeylineState.lerp(keylineStartState, keylineDefaultState, .4F);
|
|
assertThat(lerpedState.getFirstFocalKeylineIndex())
|
|
.isEqualTo(keylineStartState.getFirstFocalKeylineIndex());
|
|
assertThat(lerpedState.getLastFocalKeylineIndex())
|
|
.isEqualTo(keylineStartState.getLastFocalKeylineIndex());
|
|
|
|
// Interpolate half way or more between the start state and the default state. The focal
|
|
// indices shift to equal that of the default state after reaching half way (>= .5F).
|
|
lerpedState = KeylineState.lerp(keylineStartState, keylineDefaultState, .6F);
|
|
assertThat(lerpedState.getFirstFocalKeylineIndex())
|
|
.isEqualTo(keylineDefaultState.getFirstFocalKeylineIndex());
|
|
assertThat(lerpedState.getLastFocalKeylineIndex())
|
|
.isEqualTo(keylineDefaultState.getLastFocalKeylineIndex());
|
|
|
|
lerpedState = KeylineState.lerp(keylineStartState, keylineDefaultState, .5F);
|
|
assertThat(lerpedState.getFirstFocalKeylineIndex())
|
|
.isEqualTo(keylineDefaultState.getFirstFocalKeylineIndex());
|
|
assertThat(lerpedState.getLastFocalKeylineIndex())
|
|
.isEqualTo(keylineDefaultState.getLastFocalKeylineIndex());
|
|
}
|
|
|
|
@Test
|
|
public void testAddingAnchorKeyline_mustBeAtStartOrEnd() {
|
|
assertThrows(
|
|
IllegalArgumentException.class,
|
|
() ->
|
|
new KeylineState.Builder(100F, 0)
|
|
.addAnchorKeyline(25F, .5F, 50F)
|
|
.addAnchorKeyline(100F, 0F, 100F)
|
|
.addAnchorKeyline(200F, 0F, 100F)
|
|
.build());
|
|
}
|
|
|
|
@Test
|
|
public void testGetFirstNonAnchorKeyline() {
|
|
KeylineState keylineDefaultState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addAnchorKeyline(25F, .5F, 50F)
|
|
.addKeylineRange(100F, 0F, 100F, 2, true)
|
|
.build();
|
|
assertThat(keylineDefaultState.getFirstNonAnchorKeyline().maskedItemSize).isEqualTo(100F);
|
|
}
|
|
|
|
@Test
|
|
public void testGetLastNonAnchorKeyline() {
|
|
KeylineState keylineDefaultState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addAnchorKeyline(25F, .5F, 50F)
|
|
.addKeyline(100F, 0F, 100F, true)
|
|
.addAnchorKeyline(175F, .5F, 50F)
|
|
.build();
|
|
assertThat(keylineDefaultState.getFirstNonAnchorKeyline().maskedItemSize).isEqualTo(100F);
|
|
}
|
|
|
|
@Test
|
|
public void testAnchorKeyline_cannotBeFocal() {
|
|
assertThrows(
|
|
IllegalArgumentException.class,
|
|
() ->
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(25F, .5F, 50F, /* isFocal= */ true, /* isAnchor= */ true)
|
|
.build());
|
|
}
|
|
|
|
@Test
|
|
public void testGetFocalKeylines() {
|
|
KeylineState keylineState =
|
|
new KeylineState.Builder(100F, 0)
|
|
.addKeyline(25F, .5F, 50F)
|
|
.addKeylineRange(100F, 0F, 100F, 2, true)
|
|
.addKeyline(275F, .5F, 50F)
|
|
.build();
|
|
|
|
List<Keyline> focalKeylines = keylineState.getFocalKeylines();
|
|
|
|
assertThat(focalKeylines).isEqualTo(keylineState.getKeylines().subList(1, 3));
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link KeylineState.Builder} that has a centered focal range with three large items,
|
|
* and one medium item, two small items, and one extra small item on each side of the focal range.
|
|
*
|
|
* <p>The resulting arrangement looks like (xs = extra small, s = small, m = medium, l = large):
|
|
* [xs-s-s-m-l-l-l-m-s-s-xs]
|
|
*/
|
|
private KeylineState.Builder createKeylineStateBuilder() {
|
|
float extraSmallSize = 10F;
|
|
float smallSize = 150F;
|
|
float mediumSize = 250F;
|
|
float largeSize = 450F;
|
|
|
|
float extraSmallMask = 1F - (extraSmallSize / largeSize);
|
|
float smallMask = 1F - (smallSize / largeSize);
|
|
float mediumMask = 1F - (mediumSize / largeSize);
|
|
|
|
return new KeylineState.Builder(largeSize, 2470)
|
|
.addKeyline(5F, extraSmallMask, extraSmallSize)
|
|
.addKeylineRange(85F, smallMask, smallSize, 2)
|
|
.addKeyline(435F, mediumMask, mediumSize)
|
|
.addKeylineRange(785F, 0F, largeSize, 3, true)
|
|
.addKeyline(2035F, mediumMask, mediumSize)
|
|
.addKeylineRange(2235F, smallMask, smallSize, 2)
|
|
.addKeyline(2465F, extraSmallMask, extraSmallSize);
|
|
}
|
|
}
|