Comment imiter le comportement en 3 phases de la feuille de fond de Google Maps?

110

Contexte

Je suis chargé de créer une interface utilisateur qui se comporte de la même manière que Google Maps affiche une feuille de fond pour un résultat trouvé.

Il comporte trois phases différentes:

  1. Contenu inférieur. La zone supérieure est toujours palpable et ne fera pas défiler quoi que ce soit en bas
  2. Contenu plein écran, tandis que la zone supérieure a un grand en-tête.
  3. Contenu plein écran, tandis que la zone supérieure n'a que la barre d'outils.

Voici ce dont je parle sur Google Maps:

Entrez la description de l'image ici

Le problème

Le fait est que la feuille du bas ne fait pas encore partie de la bibliothèque de conception (bien que cela ait été demandé, ici ).

Non seulement cela, mais l'interface utilisateur semble assez complexe et nécessite une gestion de la barre d'outils sur plusieurs phases.

Ce que j'ai essayé

J'ai trouvé une bonne (assez) bibliothèque pour la feuille du bas ( ici ), et ajouté du contenu à son échantillon de fragment, pour avoir à peu près les mêmes vues que celles indiquées sur les échantillons de conception de matériaux (comme ici ), pour avoir un CollapsingToolbarLayout qui prendra soin des phases 2 + 3.

Dans l'application que je crée, je dois aussi déplacer une icône pendant que vous faites défiler, mais je pense que si je réussis avec le reste, cela devrait être facile. Voici le code:

fragment_my.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/main_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/detail_backdrop_height"

        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <ImageView
                android:id="@+id/backdrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingTop="24dp">

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/card_margin">

                <LinearLayout
                    style="@style/Widget.CardContent"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Info"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/cheese_ipsum"/>
                </LinearLayout>
            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/card_margin"
                android:layout_marginLeft="@dimen/card_margin"
                android:layout_marginRight="@dimen/card_margin">

                <LinearLayout
                    style="@style/Widget.CardContent"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Friends"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/cheese_ipsum"/>
                </LinearLayout>
            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/card_margin"
                android:layout_marginLeft="@dimen/card_margin"
                android:layout_marginRight="@dimen/card_margin">

                <LinearLayout
                    style="@style/Widget.CardContent"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Related"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/cheese_ipsum"/>
                </LinearLayout>
            </android.support.v7.widget.CardView>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        android:clickable="true"
        android:src="@android:drawable/ic_menu_send"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"/>

</android.support.design.widget.CoordinatorLayout>

MyFragment.java

public class MyFragment extends BottomSheetFragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.fragment_my, container, false);
        view.setMinimumHeight(getResources().getDisplayMetrics().heightPixels);
        CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) view.findViewById(R.id.collapsing_toolbar);
        collapsingToolbar.setTitle("AAA");
        final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
        final AppCompatActivity activity = (AppCompatActivity) getActivity();
        activity.setSupportActionBar(toolbar);
        activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        //toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                NavUtils.navigateUpFromSameTask(getActivity());
            }
        });
        final ImageView imageView = (ImageView) view.findViewById(R.id.backdrop);

        Glide.with(this).load(R.drawable.cheese_1).centerCrop().into(imageView);
        return view;
    }
}

BottomSheetFragmentActivity.java

public final class BottomSheetFragmentActivity extends AppCompatActivity {

    protected BottomSheetLayout bottomSheetLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom_sheet_fragment);
        bottomSheetLayout = (BottomSheetLayout) findViewById(R.id.bottomsheet);
        findViewById(R.id.bottomsheet_fragment_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new MyFragment().show(getSupportFragmentManager(), R.id.bottomsheet);
            }
        });
        bottomSheetLayout.setShouldDimContentView(false);
        bottomSheetLayout.setPeekOnDismiss(true);
        bottomSheetLayout.setPeekSheetTranslation(200);
        bottomSheetLayout.setInterceptContentTouch(false);
        bottomSheetLayout.setDefaultViewTransformer(new BaseViewTransformer() {
            @Override
            public void transformView(final float translation, final float maxTranslation, final float peekedTranslation, final BottomSheetLayout parent, final View view) {
                Log.d("AppLog", "translation:" + translation + " maxTranslation:" + maxTranslation + " peekedTranslation:" + peekedTranslation);
            }
        });
    }
}

Cela fonctionne presque bien. Le seul problème est le passage du n ° 3 au n ° 2:

entrez la description de l'image ici

La question

Quel est le problème avec le code? Que puis-je faire pour obtenir le comportement requis?

développeur android
la source
Ressemble à des transitions d'activité pour moi. Avez-vous essayé de créer 2 activités et d'appliquer des transitions matérielles entre elles? Et utilisé CoordinatorLayoutsur le 2ème écran?
SD
@SD Je suis sûr que ce n'est pas 2 activités, car vous pouvez continuer à toucher l'écran pour faire défiler et basculer entre les phases. Cela ne vous empêche pas de passer à l'activité suivante / précédente. Lors de l'ouverture d'une nouvelle activité, je ne pense pas qu'il soit possible de conserver les mêmes événements tactiles pour le mécanisme de défilement. Je ne sais même pas s'il est possible d'utiliser des fragments, mais c'est probablement plus possible que des activités.
développeur android
Ensuite, je pense que toutes les vues sont dans la même disposition, chacune ayant un comportement spécifique défini. Et tous les comportements sont déclenchés par l'interception de défilement vertical sur la disposition racine qui coordonne tout.
SD
@SD Savez-vous comment le faire fonctionner correctement? Est-ce mieux que ce que j'ai trouvé?
développeur android
1
Je pense que vous devriez jeter un œil à cette bibliothèque .
Savelii Zagurskii

Réponses:

18

Remarque : lisez les modifications en bas


OK, j'ai trouvé un moyen de le faire, mais j'ai dû changer le code de plusieurs classes, afin que la feuille du bas connaisse l'état de l'appBarLayout (développé ou non), et ignore le défilement au cas où il serait non développé:

BottomSheetLayout.java

Champs ajoutés:

private AppBarLayout mAppBarLayout;
private OnOffsetChangedListener mOnOffsetChangedListener;
private int mAppBarLayoutOffset;

init () - a ajouté ceci:

    mOnOffsetChangedListener = new OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
            mAppBarLayoutOffset = verticalOffset;
        }
    };

Ajout d'une fonction pour définir l'appBarLayout:

public void setAppBarLayout(final AppBarLayout appBarLayout) {
    if (mAppBarLayout == appBarLayout)
        return;
    if (mAppBarLayout != null)
        mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);
    mAppBarLayout = appBarLayout;
    mAppBarLayout.addOnOffsetChangedListener(mOnOffsetChangedListener);
}

onDetachedFromWindow () - a ajouté ceci:

    if (mAppBarLayout != null)
        mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);

onTouchEvent () - a ajouté ceci:

      ...
      if (bottomSheetOwnsTouch) {
        if (state == State.EXPANDED && scrollingDown && mAppBarLayout != null && mAppBarLayoutOffset != 0) {
            event.offsetLocation(0, sheetTranslation - getHeight());
            getSheetView().dispatchTouchEvent(event);
            return true;
        }
      ...

Ce sont les principaux changements. Maintenant pour ce qui les définit:

MyFragment.java

onCreateView () - a ajouté ceci:

    mBottomSheetLayout.setAppBarLayout((AppBarLayout) view.findViewById(R.id.appbar));

J'ai également ajouté cette fonction:

 public void setBottomSheetLayout(final BottomSheetLayout bottomSheetLayout) {
    mBottomSheetLayout = bottomSheetLayout;
}

Voici maintenant comment l'activité informe le fragment sur appBarLayout:

            final MyFragment myFragment = new MyFragment();
            myFragment.setBottomSheetLayout(bottomSheetLayout);
            myFragment.show(getSupportFragmentManager(), R.id.bottomsheet);

Le projet est maintenant disponible sur GitHub:

https://github.com/AndroidDeveloperLB/ThreePhasesBottomSheet

J'espère qu'il n'y a pas de bugs.


La solution a malheureusement des bugs, donc je ne marquerai pas cette réponse comme la bonne:

  1. Cela ne fonctionne bien que sur Android 6 et supérieur. D'autres ont un comportement étrange de montrer la feuille du bas agrandie pendant une infime fraction de temps, à chaque fois en la montrant.
  2. Les changements d'orientation ne sauvegardent pas du tout l'état du défilement, je l'ai donc désactivé.
  3. Rare problème de pouvoir faire défiler le contenu de la feuille du bas alors qu'il est encore réduit (en bas)
  4. Si un clavier a été affiché auparavant, la feuille du bas peut être en plein écran lorsque vous essayez d'être regardé.

Si quelqu'un peut vous aider, faites-le.


Pour le problème n ° 1, j'ai essayé d'ajouter un correctif en définissant la visibilité sur INVISIBLE lorsque la feuille du bas n'est pas encore consultée, mais cela ne fonctionne pas toujours, surtout si un clavier est affiché.


Pour le problème n ° 1, j'ai trouvé comment le résoudre, en enveloppant simplement (dans "fragment_my.xml") le CoordinatorLayout avec n'importe quelle vue que vous souhaitez utiliser (j'ai utilisé FrameLayout), et en mettant également une vue en taille réelle dans it (je viens de mettre "View"), comme tel:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--This full sized view, together with the FrameLayout above, are used to handle some weird UI issues on pre-Android-6 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <...CollapsingToolbarLayout 
    ...

Cela a probablement confondu le bottomSheet quand j'avais le CoordinatorLayout étant sa vue. J'ai mis à jour le projet, mais s'il y a un moyen d'avoir une meilleure solution, j'aimerais en savoir plus.


Ces derniers mois, Google a publié sa propre classe bottomSheet, mais comme je l'ai découvert, elle a beaucoup de problèmes, donc je ne peux même pas l'essayer.

développeur android
la source
mais qu'en est-il de cette image? cloud.githubusercontent.com/assets/5357526/11641271/… je veux mettre en œuvre ce genre de diapositive d'image avec la feuille de fond monte
Hardy
@HBdroid Je pense que c'est possible. peut-être pour la fonction "onOffsetChanged", modifier également la traduction de mBottomSheetBackgroundImageView? L'exigence sur mon cas était d'abord de gérer les 3 phases. Maintenant, c'est une question de quoi faire la transition et comment, et cela est très personnalisé et dépend de vos besoins. Cela nécessite également beaucoup de mathématiques ennuyeuses. Je suggère d'utiliser la fonction d'exécution instantanée pour le rendre rapide à essayer.
développeur android
je ne suis pas en mesure de trouver la solution s'il vous plaît aidez-moi
Hardy
@HBdroid Désolé, je ne sais pas. Essayez peut-être de supprimer cette application: layout_collapseMode = "parallax". essayez également de faire la traduction dans transformView.
développeur android
2
@Hardy avez-vous fini par créer la solution que vous vouliez? si oui, est-il open source et peut-il être partagé?
N Jay
15

GRANDE MISE À JOUR

Parce qu'il y avait comme 4 ou 5 questions sur le même sujet, MAIS avec des exigences DIFFÉRENTES, et j'ai essayé de répondre à toutes, mais un administrateur non poli les a supprimées / fermées, me faisant créer un ticket pour chacune et les changer en éviter le «copier-coller» Je vous laisse un lien vers la réponse complète dans laquelle vous pouvez trouver toutes les explications sur la façon d'obtenir un comportement complet comme Google Maps.


Répondre à votre question

Comment imiter le comportement en 3 phases de la feuille de fond de Google Maps?

Avec la bibliothèque de support 23.x.x +, vous pouvez le faire en modifiant la valeur par défaut BottomSheetBehavior, en ajoutant une autre statistique avec les étapes suivantes:

  1. Créez une classe Java et étendez-la à partir de CoordinatorLayout.Behavior<V>
  2. Copiez le code de collage du BottomSheetBehaviorfichier par défaut vers votre nouveau.
  3. Modifiez la méthode clampViewPositionVerticalavec le code suivant:

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset);
    }
    int constrain(int amount, int low, int high) {
        return amount < low ? low : (amount > high ? high : amount);
    }
  4. Ajouter un nouvel état:

    public static final int STATE_ANCHOR_POINT = X;
  5. Modifier les méthodes suivantes: onLayoutChild, onStopNestedScroll, BottomSheetBehavior<V> from(V view)et setState( en option)

Je vais ajouter ces méthodes modifiées et un lien vers l'exemple de projet .

Et voici à quoi ça ressemble:
CustomBottomSheetBehavior

MiguelHincapieC
la source
J'ai testé le repo github maintenant, et cela semble sympa. Mais la zone bleue supérieure semble parfois partielle. De plus, je ne sais pas où gérer les vues qui doivent se déplacer lorsque vous faites glisser la feuille du bas. Dans le repo que j'ai fait (ici: github.com/AndroidDeveloperLB/ThreePhasesBottomSheet ), l'image s'estompe et la petite image se déplace d'un endroit à un autre, y compris en modifiant sa taille. J'aimerais savoir où ajouter le traitement de ceux-ci.
développeur android
Salut, j'ai une version locale avec une image de parallaxe mais cela ne fonctionne pas encore bien (je peux la pousser si vous voulez la regarder). Vous pouvez ajouter n'importe quelle vue à l'intérieur du CoordinatorLayoutin activity_main.xml. Je suppose que vous avez une bonne expérience avec la mise en page du coordinateur, sinon jetez un œil à ce lien que j'ai trouvé
MiguelHincapieC
Je vais voir comment vous avez obtenu le comportement de la barre d'outils et je vais l'utiliser sur le mien: D. Au fait, il y a un petit comportement que je ne pourrais imiter que si j'utilise la bibliothèque de support 23.2: dans google maps si vous faites glisser l'image qui sous la barre d'outils, elle déplacera la feuille de fond, mais si vous passez à 23.4 ou minSdkVersion 14+, vous le ferez perdre ce comportement o_O '
MiguelHincapieC
@androiddeveloper je l'ai! maintenant il fonctionne avec l'effet de parallaxe d'image et les animations de barres d'outils ... Il ne me manque que la couleur qui prend la barre d'outils lorsque vous continuez à glisser vers le haut: D
MiguelHincapieC
@MiguelHincapieC Bonjour, je veux afficher uniquement la barre d'outils principale et j'ai supprimé la mise en page de la barre d'applications fusionnée, mais sur la feuille inférieure, la barre d'état du temps ne s'affiche pas et la fin de l'image parallex à la position de la barre d'état et je veux parallexer l'image collante sous la barre d'outils principale. pouvez-vous expliquer comment puis-je le faire
Vijay Rajput
0

Avez-vous essayé cela? http://android-developers.blogspot.in/2016/02/android-support-library-232.html?m=1 Ici, il est dit que nous pouvons simplement spécifier un comportement de mise en page de la feuille inférieure.

METTRE À JOUR:

Fondamentalement, le lien indique:

En attachant un BottomSheetBehavior à une vue enfant d'un CoordinatorLayout (c'est-à-dire en ajoutant app: layout_behavior = "android.support.design.widget.BottomSheetBehavior"), vous obtiendrez automatiquement la détection tactile appropriée pour effectuer la transition entre cinq états:

STATE_COLLAPSED: this collapsed state is the default and shows just a portion of the layout along the bottom. The height can be controlled with the app:behavior_peekHeight attribute (defaults to 0)
STATE_DRAGGING: the intermediate state while the user is directly dragging the bottom sheet up or down
STATE_SETTLING: that brief time between when the View is released and settling into its final position
STATE_EXPANDED: the fully expanded state of the bottom sheet, where either the whole bottom sheet is visible (if its height is less than the containing CoordinatorLayout) or the entire CoordinatorLayout is filled
STATE_HIDDEN: disabled by default (and enabled with the app:behavior_hideable attribute), enabling this allows users to swipe down on the bottom sheet to completely hide the bottom sheet
Keep in mind that scrolling containers in your bottom sheet must support nested scrolling (for example, NestedScrollView, RecyclerView, or ListView/ScrollView on API 21+).

Si vous souhaitez recevoir des rappels de changements d'état, vous pouvez ajouter un BottomSheetCallback:

// The View with the BottomSheetBehavior  
 View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
 BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);  
 behavior.setBottomSheetCallback(new BottomSheetCallback() {  
    @Override  
    public void onStateChanged(@NonNull View bottomSheet, int newState) {  
      // React to state change  
    }  
      @Override  
      public void onSlide(@NonNull View bottomSheet, float slideOffset) {  
       // React to dragging events  
   }  
 });  

Alors que BottomSheetBehavior capture le cas persistant de la feuille inférieure, cette version fournit également un BottomSheetDialog et BottomSheetDialogFragment pour remplir le cas d'utilisation des feuilles inférieures modales. Remplacez simplement AppCompatDialog ou AppCompatDialogFragment par leurs équivalents de feuille inférieure pour que votre boîte de dialogue soit conçue comme une feuille inférieure.

Vaibhav Sharma
la source
La question a été posée avant que Google ne montre sa classe de bibliothèque d'assistance. Si vous avez une solution fonctionnelle qui l'utilise, veuillez la montrer ici.
développeur android
@androiddeveloper Je n'ai pas lu la date de la question et j'ai donc suggéré cette réponse. Mais si vous souhaitez utiliser cette bibliothèque, vous pouvez l'utiliser. En ce qui concerne la solution de travail pour ce code, je ne l'ai pas. Désolé.
Vaibhav Sharma
0

J'ai également dû implémenter une vue similaire à la façon dont Google Maps affiche une feuille de fond pour un résultat trouvé.

Voici à quoi ressemble le mien:

Vue d'aperçu

Agrandir la vue défilée vers le haut

Agrandir la vue défilée vers le bas

Au début, j'ai défini une feuille de fond avec un en-tête et un contenu défilable, mais layout_height ne semblait pas envelopper le contenu ni de l'en-tête ni du contenu défilable malgré la spécification wrap_content.

Ce problème a disparu lorsque j'ai utilisé LinearLayoutau lieu de ConstraintLayoutpour la CoordinatorLayoutdisposition enfant de (et pour ses enfants).

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/buttonPeek"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Peek"
        app:layout_constraintEnd_toStartOf="@+id/buttonExpand"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonExpand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Expand"
        app:layout_constraintEnd_toStartOf="@+id/buttonClose"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/buttonPeek"
        app:layout_constraintTop_toTopOf="@+id/buttonPeek" />

    <Button
        android:id="@+id/buttonClose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Close"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/buttonExpand"
        app:layout_constraintTop_toTopOf="@+id/buttonExpand" />

    <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/layout_coordinator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:id="@+id/layout_coordinator_child"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:behavior_hideable="true"
            app:layout_behavior="@string/bottom_sheet_behavior">

            <LinearLayout
                android:id="@+id/layout_bottom_sheet_header"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#FFFF0000"
                android:orientation="vertical" >

                <TextView
                    android:id="@+id/headerTextView_a"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="a" />

                <TextView
                android:id="@+id/headerTextView_b"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="b" />

                <TextView
                android:id="@+id/headerTextView_c"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="c" />

                <TextView
                android:id="@+id/headerTextView_d"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="d" />

                <TextView
                android:id="@+id/headerTextView_e"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="e" />

                <TextView
                android:id="@+id/headerTextView_f"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="f" />

                <TextView
                android:id="@+id/headerTextView_g"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="g" />

                <TextView
                android:id="@+id/headerTextView_h"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="h" />

                <TextView
                android:id="@+id/headerTextView_i"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="i" />

                <TextView
                android:id="@+id/headerTextView_j"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="j" />

                <TextView
                android:id="@+id/headerTextView_k"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="k" />

            </LinearLayout>

            <androidx.core.widget.NestedScrollView
                android:id="@+id/layout_bottom_sheet_scrollable_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#FF00FF00"
                android:fillViewport="true" >

                <LinearLayout
                    android:id="@+id/layout_bottom_sheet_scrollable_content"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/textView0"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="0" />

                    <TextView
                        android:id="@+id/textView1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="1" />

                    <TextView
                        android:id="@+id/textView2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="2" />

                    <TextView
                        android:id="@+id/textView3"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="3" />

                    <TextView
                        android:id="@+id/textView4"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="4" />

                    <TextView
                        android:id="@+id/textView5"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="5" />

                    <TextView
                        android:id="@+id/textView6"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="6" />

                    <TextView
                        android:id="@+id/textView7"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="7" />

                    <TextView
                        android:id="@+id/textView8"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="8" />

                    <TextView
                        android:id="@+id/textView9"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="9" />

                    <TextView
                        android:id="@+id/textView10"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="10" />

                    <TextView
                        android:id="@+id/textView11"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="11" />

                    <TextView
                        android:id="@+id/textView12"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="12" />

                    <TextView
                        android:id="@+id/textView13"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="13" />

                    <TextView
                        android:id="@+id/textView14"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="14" />

                    <TextView
                        android:id="@+id/textView15"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="15" />

                    <TextView
                        android:id="@+id/textView16"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="16" />

                    <TextView
                        android:id="@+id/textView17"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="17" />

                    <TextView
                        android:id="@+id/textView18"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="18" />

                    <TextView
                        android:id="@+id/textView19"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="19" />

                    <TextView
                        android:id="@+id/textView20"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="20" />

                    <TextView
                        android:id="@+id/textView21"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="21" />

                    <TextView
                        android:id="@+id/textView22"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="22" />

                    <TextView
                        android:id="@+id/textView23"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="23" />

                    <TextView
                        android:id="@+id/textView24"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="24" />

                    <TextView
                        android:id="@+id/textView25"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="25" />

                    <TextView
                        android:id="@+id/textView26"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="26" />

                    <TextView
                        android:id="@+id/textView27"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="27" />

                    <TextView
                        android:id="@+id/textView28"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="28" />

                    <TextView
                        android:id="@+id/textView29"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="29" />

                    <TextView
                        android:id="@+id/textView30"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="30" />

                    <TextView
                        android:id="@+id/textView31"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="31" />

                    <TextView
                        android:id="@+id/textView32"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="32" />

                    <TextView
                        android:id="@+id/textView33"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="33" />

                    <TextView
                        android:id="@+id/textView34"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="34" />

                    <TextView
                        android:id="@+id/textView35"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="35" />

                    <TextView
                        android:id="@+id/textView36"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="36" />

                    <TextView
                        android:id="@+id/textView37"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="37" />

                    <TextView
                        android:id="@+id/textView38"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="38" />

                    <TextView
                        android:id="@+id/textView39"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="39" />

                    <TextView
                        android:id="@+id/textView40"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="40" />

                    <TextView
                        android:id="@+id/textView41"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="41" />

                    <TextView
                        android:id="@+id/textView42"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="42" />

                    <TextView
                        android:id="@+id/textView43"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="43" />

                    <TextView
                        android:id="@+id/textView44"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="44" />

                    <TextView
                        android:id="@+id/textView45"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="45" />

                    <TextView
                        android:id="@+id/textView46"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="46" />

                    <TextView
                        android:id="@+id/textView47"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="47" />

                    <TextView
                        android:id="@+id/textView48"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="48" />

                    <TextView
                        android:id="@+id/textView49"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="49" />

                </LinearLayout>

            </androidx.core.widget.NestedScrollView>
        </LinearLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

package com.example.bottomsheetwithscrollablecontent;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.google.android.material.bottomsheet.BottomSheetBehavior;

import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

public class MainActivity extends AppCompatActivity {
    private CoordinatorLayout layout_coordinator;
    private View layout_coordinator_child;
    private View layout_bottom_sheet_header;

    private BottomSheetBehavior behavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        layout_coordinator = findViewById(R.id.layout_coordinator);
        layout_coordinator_child = layout_coordinator.findViewById(R.id.layout_coordinator_child);
        layout_bottom_sheet_header = layout_coordinator.findViewById(R.id.layout_bottom_sheet_header);

        behavior = BottomSheetBehavior.from(layout_coordinator_child);

        Button buttonPeek = findViewById(R.id.buttonPeek);
        buttonPeek.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                behavior.setPeekHeight(layout_bottom_sheet_header.getHeight());
                behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            }
        });

        Button buttonExpand = findViewById(R.id.buttonExpand);
        buttonExpand.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

        Button buttonClose = findViewById(R.id.buttonClose);
        buttonClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
            }
        });
    }
}

app / build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.bottomsheetwithscrollablecontent"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0-beta01'
    implementation "com.google.android.material:material:1.1.0-alpha04"
}
Michael Osofsky
la source