Android
Nedre blad
Sök…
Introduktion
Ett bottenark är ett ark som glider upp från skärmens underkant.
Anmärkningar
Bottenarken glider upp från botten av skärmen för att avslöja mer innehåll.
De lades till Android Support Library i version 23.2.0.
BottomSheetBehavare som Google maps
Detta exempel beror på Support Library 23.4.0. +.
BottomSheetBehavior kännetecknas av:
- Två verktygsfält med animationer som svarar på bottenarkets rörelser.
- En FAB som döljer när det är nära "modalverktygsfältet" (det som visas när du skjuter upp).
- En bakgrundsbild bakom bottenarket med någon form av parallaxeffekt.
- En titel (TextView) i verktygsfältet som visas när det nedre arket når det.
- Meddelandesatusfältet kan vända sin bakgrund till transparent eller fullfärg.
- Ett anpassat bottenarkbeteende med ett "ankar" -läge.
Låt oss nu kolla dem en efter en:
verktygsfält
När du öppnar den vyn i Google Maps kan du se ett verktygsfält där du kan söka, det är det enda som jag inte gör precis som Google Maps, eftersom jag ville göra det mer generiskt. Hur som helst att ToolBar
finns i en AppBarLayout
och det gömdes när du börjar dra i bottenarket och det visas igen när bottenarket når COLLAPSED
tillståndet.
För att uppnå det måste du:
- skapa ett
Behavior
och utvidga det frånAppBarLayout.ScrollingViewBehavior
- åsidosätta
layoutDependsOn
ochonDependentViewChanged
metoder. Genom att göra det lyssnar du på rörelser i bottenarket. - skapa några metoder för att dölja och dölja AppBarLayout / Toolbar med animationer.
Så här gjorde jag det för första verktygsfältet eller ActionBar:
@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();
}
Här är den kompletta filen om du behöver den
Det andra verktygsfältet eller "Modal" -verktygsfältet:
Du måste åsidosätta samma metoder, men i den här måste du ta hand om fler beteenden:
- visa / dölja verktygsfältet med animationer
- ändra statusfärgens färg / bakgrund
- visa / dölj bottenbladets titel i verktygsfältet
- stäng bottenarket eller skicka det till kollapsat tillstånd
Koden för den här är lite omfattande, så jag låter länken
FAB
Detta är ett anpassat beteende också, men sträcker sig från FloatingActionButton.Behavior
. I onDependentViewChanged
måste du titta när det når "offSet" eller peka på var du vill dölja det. I mitt fall vill jag gömma det när det är nära det andra verktygsfältet, så jag gräver in FAB-förälder (en koordinatorLayout) och letar efter AppBarLayout som innehåller verktygsfältet, sedan använder jag ToolBar-positionen som 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;
}
Komplett anpassad FAB-beteendelänk
Bilden bakom bottenarket med parallaxeffekt :
Liksom de andra är det ett anpassat beteende, det enda "komplicerade" i det här är den lilla algoritmen som håller bilden förankrad i bottenarket och undviker att bilden kollapsar som standardparallaxeffekt:
@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;
}
Den kompletta filen för bakgrundsbild med parallaxeffekt
Nu till slut: Det anpassade bottenarkbeteendet
För att uppnå de tre stegen måste du först förstå att standard BottomSheetBehavior har 5 tillstånd: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN
och för Google Maps-beteende måste du lägga till ett mellanläge mellan kollapsat och expanderat: STATE_ANCHOR_POINT
.
Jag försökte utvidga standard bottomSheetBehavior utan framgång, så jag kopierade bara in alla koder och ändrade det jag behöver.
Följ de följande stegen för att uppnå det jag talar om:
Skapa en Java-klass och utvidga den från
CoordinatorLayout.Behavior<V>
Kopiera klistra in kod från standard
BottomSheetBehavior
fil till din nya.Ändra
clampViewPositionVertical
med följande kod:@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); }
Lägg till ett nytt tillstånd
public static final int STATE_ANCHOR_POINT = X;
Ändra nästa metoder:
onLayoutChild
,onStopNestedScroll
,BottomSheetBehavior<V> from(V view)
ochsetState
(valfritt)
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;
}
Länk till hela projektet där du kan se alla anpassade beteenden
Och här ser det ut:
[ ]
Snabbinställning
Se till att följande beroende läggs till i appens build.gradle-fil under beroenden:
compile 'com.android.support:design:25.3.1'
Sedan kan du använda bottenarket med hjälp av dessa alternativ:
-
BottomSheetBehavior
som ska användas medCoordinatorLayout
-
BottomSheetDialog
som är en dialogruta med ett bottenarkbeteende -
BottomSheetDialogFragment
som är en förlängning avDialogFragment
, som skapar enBottomSheetDialog
stället för en standarddialog.
Ihållande bottenark
Du kan uppnå en beständig underlakan fästa en BottomSheetBehavior
till ett barn Vy över en CoordinatorLayout
:
<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>
Sedan kan du skapa en referens i din kod med:
// The View with the BottomSheetBehavior
View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
BottomSheetBehavior mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
Du kan ställa in statusen för din BottomSheetBehavior med metoden setState () :
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
Du kan använda ett av dessa tillstånd:
STATE_COLLAPSED
: detta kollapsade tillstånd är standard och visar bara en del av layouten längst ner. Höjden kan styras medapp:behavior_peekHeight
attribut (standard till 0)STATE_EXPANDED
: det fullständigt utvidgade tillståndet för bottenarket, där antingen hela bottenarket är synligt (om dess höjd är mindre än den innehållandeCoordinatorLayout
) eller helaCoordinatorLayout
är fylldSTATE_HIDDEN
: inaktiverad som standard (och aktiverad medapp:behavior_hideable
attribut), vilket gör det möjligt för användare att dra ner på bottenarket för att helt dölja bottenarket
Om du vill ta emot återuppringningar av tillståndsändringar kan du lägga till ett BottomSheetCallback
:
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
}
});
Modala bottenark med BottomSheetDialogFragment
Du kan förverkliga modala bottenark med hjälp av ett BottomSheetDialogFragment
.
BottomSheetDialogFragment
är ett modalt bottenark.
Detta är en version av DialogFragment
som visar ett bottenark med hjälp av BottomSheetDialog
stället för en flytande dialog.
Definiera bara fragmentet:
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);
}
}
Använd sedan den här koden för att visa fragmentet:
MyBottomSheetDialogFragment mySheetDialog = new MyBottomSheetDialogFragment();
FragmentManager fm = getSupportFragmentManager();
mySheetDialog.show(fm, "modalSheetDialog");
Detta fragment skapar en BottomSheetDialog
.
Modala bottenark med BottomSheetDialog
BottomSheetDialog
är en dialog som är utformad som ett bottenark
Använd bara:
//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();
I det här fallet behöver du inte bifoga ett BottomSheet-beteende.
Öppna BottomSheet DialogFragment i Expanded-läge som standard.
BottomSheet DialogFragment öppnas som standard i STATE_COLLAPSED
. Vilket kan tvingas öppna för STATE_EXPANDED
och ta upp hela enhetsskärmen med hjälp av följande kodmall.
@NonNull @Override public Dialog onCreateDialog (Bundle sparadInstanceState) {
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;
}
Även om dialoganimering är något märkbar men gör uppgiften att öppna DialogFragment i helskärm mycket bra.