Suche…


Einführung

Ein unteres Blatt ist ein Blatt, das vom unteren Rand des Bildschirms nach oben verschoben wird.

Bemerkungen

Die unteren Blätter werden vom unteren Rand des Bildschirms nach oben verschoben, um mehr Inhalt anzuzeigen.
Sie wurden in der Version 23.2.0 der Android Support Library hinzugefügt.

BottomSheetBehavior wie Google maps

2.1.x

Dieses Beispiel hängt von der Support Library 23.4.0. + Ab.

BottomSheetBehavior zeichnet sich aus durch:

  1. Zwei Symbolleisten mit Animationen, die auf die unteren Blattbewegungen reagieren.
  2. Ein FAB, das ausgeblendet wird, wenn es sich in der Nähe der "modalen Symbolleiste" befindet (die beim Hochrutschen angezeigt wird).
  3. Ein Hintergrundbild hinter dem unteren Blatt mit einem Parallaxeffekt.
  4. Ein Titel (TextView) in der Symbolleiste, der angezeigt wird, wenn das unterste Blatt es erreicht.
  5. Die Benachrichtigungs-Satus-Leiste kann ihren Hintergrund transparent oder vollfarbig gestalten.
  6. Ein benutzerdefiniertes unteres Blattverhalten mit dem Status "Anker".

Jetzt wollen wir sie einzeln prüfen:

Werkzeugleisten
Wenn Sie diese Ansicht in Google Maps öffnen, sehen Sie eine Symbolleiste, in der Sie suchen können. Dies ist die einzige, die ich nicht genau wie Google Maps mache, weil ich es generischer machen wollte. Die ToolBar befindet sich jedoch in einem AppBarLayout und wurde ausgeblendet, wenn Sie das BottomSheet ziehen, und es wird wieder angezeigt, wenn das COLLAPSED den COLLAPSED .
Um dies zu erreichen, müssen Sie:

  • Erstellen Sie ein Behavior und erweitern Sie es von AppBarLayout.ScrollingViewBehavior
  • Überschreiben onDependentViewChanged Methoden layoutDependsOn und onDependentViewChanged . Wenn Sie es tun, werden Sie auf die Bewegungen des unteren Blattes achten.
  • Erstellen Sie einige Methoden, um AppBarLayout / ToolBar mit Animationen ein- und auszublenden.

So habe ich es für die erste Symbolleiste oder ActionBar gemacht:

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency instanceof NestedScrollView;
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                                      View dependency) {

    if (mChild == null) {
        initValues(child, dependency);
        return false;
    }

    float dVerticalScroll = dependency.getY() - mPreviousY;
    mPreviousY = dependency.getY();

    //going up
    if (dVerticalScroll <= 0 && !hidden) {
        dismissAppBar(child);
        return true;
    }

    return false;
}

private void initValues(final View child, View dependency) {

    mChild = child;
    mInitialY = child.getY();

    BottomSheetBehaviorGoogleMapsLike bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(dependency);
    bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, @BottomSheetBehaviorGoogleMapsLike.State int newState) {
            if (newState == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED ||
                    newState == BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN)
                showAppBar(child);
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {

        }
    });
}

private void dismissAppBar(View child){
    hidden = true;
    AppBarLayout appBarLayout = (AppBarLayout)child;
    mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_shortAnimTime));
    mToolbarAnimation.y(-(mChild.getHeight()+25)).start();
}

private void showAppBar(View child) {
    hidden = false;
    AppBarLayout appBarLayout = (AppBarLayout)child;
    mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_mediumAnimTime));
    mToolbarAnimation.y(mInitialY).start();
}

Hier ist die komplette Datei, wenn Sie es brauchen

Die zweite Symbolleiste oder "Modal" -Symbolleiste:
Sie müssen die gleichen Methoden überschreiben, aber in diesem Fall müssen Sie sich um mehr Verhalten kümmern:

  • Werkzeugleiste mit Animationen anzeigen / ausblenden
  • Farbe / Hintergrund der Statusleiste ändern
  • Ein- / Ausblenden des BottomSheet-Titels in der Symbolleiste
  • Schließen Sie das bottomSheet oder senden Sie es in den minimierten Zustand

Der Code für dieses ist ein wenig umfangreich, also werde ich den Link zulassen

Die FAB

Dies ist auch ein benutzerdefiniertes Verhalten, erstreckt sich jedoch von FloatingActionButton.Behavior . In onDependentViewChanged Sie nachsehen, wenn es das "offSet" erreicht, oder an die Stelle zeigen, an der Sie es ausblenden möchten. In meinem Fall möchte ich es ausblenden, wenn es sich in der Nähe der zweiten Symbolleiste befindet. Ich greife in FAB (ein CoordinatorLayout) nach dem AppBarLayout, das die Toolbar enthält. Dann verwende ich die Toolbar-Position wie OffSet :

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {

    if (offset == 0)
        setOffsetValue(parent);

    if (dependency.getY() <=0)
        return false;

    if (child.getY() <= (offset + child.getHeight()) && child.getVisibility() == View.VISIBLE)
        child.hide();
    else if (child.getY() > offset && child.getVisibility() != View.VISIBLE)
        child.show();

    return false;
}

Vollständige Verknüpfung zum benutzerdefinierten FAB-Verhalten

Das Bild hinter dem BottomSheet mit Parallaxeffekt :
Wie bei den anderen handelt es sich um ein benutzerdefiniertes Verhalten. Das einzige "Komplizierte" in diesem Fall ist der kleine Algorithmus, der das Bild im BottomSheet verankert und den Bildeinbruch wie einen standardmäßigen Parallaxeffekt verhindert:

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                                      View dependency) {

    if (mYmultiplier == 0) {
        initValues(child, dependency);
        return true;
    }

    float dVerticalScroll = dependency.getY() - mPreviousY;
    mPreviousY = dependency.getY();

    //going up
    if (dVerticalScroll <= 0 && child.getY() <= 0) {
        child.setY(0);
        return true;
    }

    //going down
    if (dVerticalScroll >= 0 && dependency.getY() <= mImageHeight)
        return false;

    child.setY( (int)(child.getY() + (dVerticalScroll * mYmultiplier) ) );

    return true;
}


Die komplette Datei für das Hintergrundbild mit Parallax-Effekt

Nun zum Schluss: Das Verhalten von Custom BottomSheet
Um die 3 Schritte zu erreichen, müssen Sie zunächst verstehen, dass das standardmäßige BottomSheetBehavior 5 STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN hat: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN und für das Verhalten von Google Maps einen mittleren Status zwischen collapsed und STATE_ANCHOR_POINT : STATE_ANCHOR_POINT .
Ich habe versucht, das Standard-bottomSheetBehavior ohne Erfolg zu erweitern. Daher kopiere ich den gesamten Code und modifizierte, was ich brauche.
Um zu erreichen, worüber ich spreche, folgen Sie den nächsten Schritten:

  1. Erstellen Sie eine Java-Klasse, und erweitern Sie sie aus CoordinatorLayout.Behavior<V>

  2. Kopieren Sie den Einfügecode aus der Standarddatei BottomSheetBehavior in Ihre neue.

  3. Ändern Sie die Methode clampViewPositionVertical mit folgendem Code:

    @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. Fügen Sie einen neuen Status hinzu

    public static final int STATE_ANCHOR_POINT = X;

  5. Ändern Sie die nächsten Methoden: onLayoutChild , onStopNestedScroll , BottomSheetBehavior<V> from(V view) und setState (optional).



public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
    // First let the parent lay it out
    if (mState != STATE_DRAGGING && mState != STATE_SETTLING) {
        if (ViewCompat.getFitsSystemWindows(parent) &&
                !ViewCompat.getFitsSystemWindows(child)) {
            ViewCompat.setFitsSystemWindows(child, true);
        }
        parent.onLayoutChild(child, layoutDirection);
    }
    // Offset the bottom sheet
    mParentHeight = parent.getHeight();
    mMinOffset = Math.max(0, mParentHeight - child.getHeight());
    mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);

    //if (mState == STATE_EXPANDED) {
    //    ViewCompat.offsetTopAndBottom(child, mMinOffset);
    //} else if (mHideable && mState == STATE_HIDDEN...
    if (mState == STATE_ANCHOR_POINT) {
        ViewCompat.offsetTopAndBottom(child, mAnchorPoint);
    } else if (mState == STATE_EXPANDED) {
        ViewCompat.offsetTopAndBottom(child, mMinOffset);
    } else if (mHideable && mState == STATE_HIDDEN) {
        ViewCompat.offsetTopAndBottom(child, mParentHeight);
    } else if (mState == STATE_COLLAPSED) {
        ViewCompat.offsetTopAndBottom(child, mMaxOffset);
    }
    if (mViewDragHelper == null) {
        mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);
    }
    mViewRef = new WeakReference<>(child);
    mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
    return true;
}


public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
    if (child.getTop() == mMinOffset) {
        setStateInternal(STATE_EXPANDED);
        return;
    }
    if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) {
        return;
    }
    int top;
    int targetState;
    if (mLastNestedScrollDy > 0) {
        //top = mMinOffset;
        //targetState = STATE_EXPANDED;
        int currentTop = child.getTop();
        if (currentTop > mAnchorPoint) {
            top = mAnchorPoint;
            targetState = STATE_ANCHOR_POINT;
        }
        else {
            top = mMinOffset;
            targetState = STATE_EXPANDED;
        }
    } else if (mHideable && shouldHide(child, getYVelocity())) {
        top = mParentHeight;
        targetState = STATE_HIDDEN;
    } else if (mLastNestedScrollDy == 0) {
        int currentTop = child.getTop();
        if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
            top = mMinOffset;
            targetState = STATE_EXPANDED;
        } else {
            top = mMaxOffset;
            targetState = STATE_COLLAPSED;
        }
    } else {
        //top = mMaxOffset;
        //targetState = STATE_COLLAPSED;
        int currentTop = child.getTop();
        if (currentTop > mAnchorPoint) {
            top = mMaxOffset;
            targetState = STATE_COLLAPSED;
        }
        else {
            top = mAnchorPoint;
            targetState = STATE_ANCHOR_POINT;
        }
    }
    if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
        setStateInternal(STATE_SETTLING);
        ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
    } else {
        setStateInternal(targetState);
    }
    mNestedScrolled = false;
}

public final void setState(@State int state) {
    if (state == mState) {
        return;
    }
    if (mViewRef == null) {
        // The view is not laid out yet; modify mState and let onLayoutChild handle it later
        /**
         * New behavior (added: state == STATE_ANCHOR_POINT ||)
         */
        if (state == STATE_COLLAPSED || state == STATE_EXPANDED ||
                state == STATE_ANCHOR_POINT ||
                (mHideable && state == STATE_HIDDEN)) {
            mState = state;
        }
        return;
    }
    V child = mViewRef.get();
    if (child == null) {
        return;
    }
    int top;
    if (state == STATE_COLLAPSED) {
        top = mMaxOffset;
    } else if (state == STATE_ANCHOR_POINT) {
        top = mAnchorPoint;
    } else if (state == STATE_EXPANDED) {
        top = mMinOffset;
    } else if (mHideable && state == STATE_HIDDEN) {
        top = mParentHeight;
    } else {
        throw new IllegalArgumentException("Illegal state argument: " + state);
    }
    setStateInternal(STATE_SETTLING);
    if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
        ViewCompat.postOnAnimation(child, new SettleRunnable(child, state));
    }
}


public static <V extends View> BottomSheetBehaviorGoogleMapsLike<V> from(V view) {
    ViewGroup.LayoutParams params = view.getLayoutParams();
    if (!(params instanceof CoordinatorLayout.LayoutParams)) {
        throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
    }
    CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
            .getBehavior();
    if (!(behavior instanceof BottomSheetBehaviorGoogleMapsLike)) {
        throw new IllegalArgumentException(
                "The view is not associated with BottomSheetBehaviorGoogleMapsLike");
    }
    return (BottomSheetBehaviorGoogleMapsLike<V>) behavior;
}



Verknüpfen Sie mit dem gesamten Projekt, in dem Sie alle benutzerdefinierten Verhaltensweisen sehen können

Und so sieht es aus:
[ CustomBottomSheetBehavior ]

Schnelle Einrichtung

Stellen Sie sicher, dass die folgende Abhängigkeit zur build.gradle-Datei Ihrer App unter Abhängigkeiten hinzugefügt wird:

compile 'com.android.support:design:25.3.1'

Dann können Sie das untere Blatt mit diesen Optionen verwenden:

Persistente untere Blätter

Sie können ein beständiges Bottom-Sheet erreichen, indem Sie ein BottomSheetBehavior an eine BottomSheetBehavior Ansicht eines CoordinatorLayout anhängen:

<android.support.design.widget.CoordinatorLayout >

    <!-- .....   -->

    <LinearLayout
       android:id="@+id/bottom_sheet"
       android:elevation="4dp"
       android:minHeight="120dp"
       app:behavior_peekHeight="120dp"
       ...
       app:layout_behavior="android.support.design.widget.BottomSheetBehavior">

           <!-- .....   -->

       </LinearLayout>

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

Dann können Sie in Ihrem Code eine Referenz erstellen mit:

 // The View with the BottomSheetBehavior  
 View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
 BottomSheetBehavior mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);  

Sie können den Status Ihres BottomSheetBehavior mithilfe der setState () -Methode festlegen :

mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);

Sie können einen dieser Zustände verwenden:

  • STATE_COLLAPSED : Dieser STATE_COLLAPSED Status ist die Standardeinstellung und zeigt nur einen Teil des Layouts am unteren Rand. Die Höhe kann mit dem app:behavior_peekHeight Attribut (Standardeinstellung 0) gesteuert werden.

  • STATE_EXPANDED : Der vollständig erweiterte Zustand des untersten Blattes, wobei entweder das gesamte unterste Blatt sichtbar ist (wenn seine Höhe geringer als das enthaltene CoordinatorLayout ) oder das gesamte CoordinatorLayout gefüllt ist

  • STATE_HIDDEN : Standardmäßig deaktiviert (und mit dem Attribut app:behavior_hideable aktiviert). STATE_HIDDEN können Benutzer das untere Blatt nach unten streichen, um das untere Blatt vollständig auszublenden

Wenn Sie Rückrufe von BottomSheetCallback erhalten möchten, können Sie BottomSheetCallback hinzufügen:

mBottomSheetBehavior.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  
   }  
 });  

Modale untere Blätter mit BottomSheetDialogFragment

Sie können modale untere Arbeitsblätter mit einem BottomSheetDialogFragment .

Das BottomSheetDialogFragment ist ein modales unteres Blatt.
Dies ist eine Version von DialogFragment , die ein unteres Blatt mit BottomSheetDialog anstelle eines schwebenden Dialogfelds zeigt.

Definieren Sie einfach das Fragment:

public class MyBottomSheetDialogFragment extends BottomSheetDialogFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.my_fragment_bottom_sheet, container);
    }
}

Verwenden Sie dann diesen Code, um das Fragment anzuzeigen:

MyBottomSheetDialogFragment mySheetDialog = new MyBottomSheetDialogFragment();
FragmentManager fm = getSupportFragmentManager();
mySheetDialog.show(fm, "modalSheetDialog");

Dieses Fragment erstellt einen BottomSheetDialog .

Modale untere Blätter mit BottomSheetDialog

Der BottomSheetDialog ist ein Dialog, der als unteres Blatt gestaltet ist

Benutz einfach:

//Create a new BottomSheetDialog
BottomSheetDialog dialog = new BottomSheetDialog(context);
//Inflate the layout R.layout.my_dialog_layout
dialog.setContentView(R.layout.my_dialog_layout);
//Show the dialog
dialog.show();

In diesem Fall müssen Sie kein BottomSheet-Verhalten hinzufügen.

Öffnen Sie BottomSheet DialogFragment standardmäßig im erweiterten Modus.

BottomSheet STATE_COLLAPSED wird standardmäßig in STATE_COLLAPSED . Welche kann erzwungen werden, um STATE_EXPANDED zu STATE_EXPANDED und den gesamten STATE_EXPANDED mit Hilfe der folgenden STATE_EXPANDED .

@NonNull @Override public Dialog onCreateDialog (Bundle savedInstanceState) {

    BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

    dialog.setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            BottomSheetDialog d = (BottomSheetDialog) dialog;

            FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
            BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    });

    // Do something with your dialog like setContentView() or whatever
    return dialog;
}

Die Dialoganimation ist zwar etwas auffällig, macht aber das Öffnen des DialogFragment im Vollbildmodus sehr gut.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow