Skip to content

Commit

Permalink
[Carousel] Add carousel alignment attribute
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 548719675
  • Loading branch information
imhappi authored and pekingme committed Jul 18, 2023
1 parent d3dda60 commit 547156e
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 31 deletions.
45 changes: 40 additions & 5 deletions docs/components/Carousel.md
Expand Up @@ -17,7 +17,10 @@ Carousels contain a collection of items that can move into or out of view

* [Design & API documentation](#design-api-documentation)
* [Using carousel](#using-carousel)
* [Multi-browse carousels](#multi-browse-carousels)
* [Multi-browse strategy](#multi-browse-strategy)
* [Hero strategy](#hero-strategy)
* [Fullscreen strategy](#fullscreen-strategy)
* [Attributes](#attributes)
* [Customizing carousel](#customizing-carousel)

## Design & API Documentation
Expand Down Expand Up @@ -121,10 +124,13 @@ A hero strategy highlights large content, like movies and other media, for more
considered browsing and selection. It draws attention and focus to a main
carousel item while hinting at the next item in line.

With a hero strategy, typically there is one large item is at the start of the
list followed by a small item. When there is one large item, the large item
takes up the entire size of the `RecyclerView` container, save some space for
the small item.
With a start-aligned hero strategy, typically there is one large item is at the
start of the list followed by a small item. With a center-aligned hero strategy,
there is typically one large item at the middle of the list surrounded by small
items. When there is one large item, the large item takes up the entire size of
the `RecyclerView` container, save some space for the small item(s). See [focal alignment](#focal-alignment) for more information about changing alignment of the large items.

![A contained center-aligned Carousel](assets/carousel/hero-center.png)

There may be more than one large item depending on the dimensions of the
carousel. On a horizontal carousel, the width of a large item will maximally be
Expand Down Expand Up @@ -168,6 +174,15 @@ SnapHelper snapHelper = new CarouselSnapHelper();
snapHelper.attachToRecyclerView(carouselRecyclerView);
```

## Attributes

Note that in order to use these attributes on the RecyclerView, CarouselLayoutManager must be set through the RecyclerView attribute `app:layoutManager`.

Element | Attribute | Related method(s) | Default value
--------------- | ----------------------- | ---------------------- | -------------
**Orientation** | `android:orientation` | `setOrientation` | `vertical` (if layoutManager has been set through xml)
**Alignment** | `app:carouselAlignment` | `setCarouselAlignment` | `start`

## Customizing carousel

### Item size
Expand Down Expand Up @@ -198,4 +213,24 @@ on your `MaskableFrameLayout` inside your `RecyclerView.ViewHolder`.

In the example above, a title is translated so it appears pinned to the left masking edge of the item. As the item masks and becomes too small for the title, it is faded out.

### Focal Alignment

You can control the alignment of the focal (large) items in the carousel by setting the `app:carousel_alignment` attribute on your RecyclerView, if you are also setting the RecyclerView's LayoutManager through `app:layoutManager`:

```
<androidx.recyclerview.widget.RecyclerView
...
app:layoutManager="com.google.android.material.carousel.CarouselLayoutManager"
app:carousel_alignment="center"
android:orientation="horizontal"/>
```

If CarouselLayoutManager is being set programmatically, you may set the alignment as well programmatically:

```
carouselLayoutManager.setCarouselAlignment(CarouselLayoutManager.CENTER);
```

By default, the focal alignment is start-aligned.


Binary file added docs/components/assets/carousel/hero-center.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions lib/java/com/google/android/material/carousel/Carousel.java
Expand Up @@ -16,6 +16,8 @@

package com.google.android.material.carousel;

import com.google.android.material.carousel.CarouselLayoutManager.Alignment;

/** An interface that defines a widget that can be configured as a Carousel. */
interface Carousel {

Expand All @@ -27,4 +29,7 @@ interface Carousel {

/** Whether or not the orientation is horizontal. */
boolean isHorizontal();

/** Gets the alignment of the carousel. */
@Alignment int getCarouselAlignment();
}
Expand Up @@ -24,6 +24,7 @@

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
Expand All @@ -41,6 +42,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
Expand All @@ -51,6 +53,8 @@
import androidx.core.util.Preconditions;
import androidx.core.view.ViewCompat;
import com.google.android.material.carousel.KeylineState.Keyline;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -111,6 +115,27 @@ public class CarouselLayoutManager extends LayoutManager

private CarouselOrientationHelper orientationHelper;

/**
* Aligns large items to the start of the carousel.
*/
public static final int ALIGNMENT_START = 0;

/**
* Aligns large items to the center of the carousel.
*/
public static final int ALIGNMENT_CENTER = 1;

/**
* Determines where to align the large items in the carousel.
*
* @hide
*/
@IntDef({ALIGNMENT_START, ALIGNMENT_CENTER})
@Retention(RetentionPolicy.SOURCE)
@interface Alignment {}

@Alignment private int carouselAlignment = ALIGNMENT_START;

/**
* An internal object used to store and run checks on a child to be potentially added to the
* RecyclerView and laid out.
Expand Down Expand Up @@ -156,9 +181,31 @@ public CarouselLayoutManager(
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public CarouselLayoutManager(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
setOrientation(properties.orientation);
setCarouselStrategy(new MultiBrowseCarouselStrategy());

setCarouselAttributes(context, attrs);
}

private void setCarouselAttributes(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Carousel);
setCarouselAlignment(a.getInt(R.styleable.Carousel_carousel_alignment, ALIGNMENT_START));
setOrientation(a.getInt(R.styleable.RecyclerView_android_orientation, HORIZONTAL));
a.recycle();
}
}

/**
* Sets the alignment of the focal items in the carousel.
*/
public void setCarouselAlignment(@Alignment int alignment) {
this.carouselAlignment = alignment;
refreshKeylineState();
}

@Override
public int getCarouselAlignment() {
return carouselAlignment;
}

@Override
Expand Down
Expand Up @@ -103,6 +103,20 @@ static float getChildMaskPercentage(float maskedSize, float unmaskedSize, float
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 whether this carousel should mask items against the edges of the carousel container.
*
Expand Down

0 comments on commit 547156e

Please sign in to comment.