Android
Untere Blätter
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
Dieses Beispiel hängt von der Support Library 23.4.0. + Ab.
BottomSheetBehavior zeichnet sich aus durch:
- Zwei Symbolleisten mit Animationen, die auf die unteren Blattbewegungen reagieren.
- Ein FAB, das ausgeblendet wird, wenn es sich in der Nähe der "modalen Symbolleiste" befindet (die beim Hochrutschen angezeigt wird).
- Ein Hintergrundbild hinter dem unteren Blatt mit einem Parallaxeffekt.
- Ein Titel (TextView) in der Symbolleiste, der angezeigt wird, wenn das unterste Blatt es erreicht.
- Die Benachrichtigungs-Satus-Leiste kann ihren Hintergrund transparent oder vollfarbig gestalten.
- 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 vonAppBarLayout.ScrollingViewBehavior
- Überschreiben
onDependentViewChanged
MethodenlayoutDependsOn
undonDependentViewChanged
. 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:
Erstellen Sie eine Java-Klasse, und erweitern Sie sie aus
CoordinatorLayout.Behavior<V>
Kopieren Sie den Einfügecode aus der Standarddatei
BottomSheetBehavior
in Ihre neue.Ä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); }
Fügen Sie einen neuen Status hinzu
public static final int STATE_ANCHOR_POINT = X;
Ändern Sie die nächsten Methoden:
onLayoutChild
,onStopNestedScroll
,BottomSheetBehavior<V> from(V view)
undsetState
(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:
[ ]
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:
-
BottomSheetBehavior
, das mitCoordinatorLayout
-
BottomSheetDialog
ist ein Dialog mit einem Verhalten der unteren Tabelle -
BottomSheetDialogFragment
ist eine Erweiterung vonDialogFragment
, die einenBottomSheetDialog
anstelle eines Standarddialogfelds erstellt.
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
: DieserSTATE_COLLAPSED
Status ist die Standardeinstellung und zeigt nur einen Teil des Layouts am unteren Rand. Die Höhe kann mit demapp: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 enthalteneCoordinatorLayout
) oder das gesamteCoordinatorLayout
gefüllt istSTATE_HIDDEN
: Standardmäßig deaktiviert (und mit dem Attributapp: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.