Ricerca…


introduzione

Un foglio inferiore è un foglio che scorre dal bordo inferiore dello schermo.

Osservazioni

I fogli in basso scorrono verso l'alto dalla parte inferiore dello schermo per rivelare più contenuti.
Sono stati aggiunti alla libreria di supporto Android nella versione v23.2.0.

BottomSheetBehavior come le mappe di Google

2.1.x

Questo esempio dipende dalla libreria di supporto 23.4.0. +.

BottomSheetBehavior è caratterizzato da:

  1. Due barre degli strumenti con animazioni che rispondono ai movimenti del foglio inferiore.
  2. Un FAB che si nasconde quando è vicino alla "barra degli strumenti modale" (quella che appare quando si fa scorrere verso l'alto).
  3. Un'immagine sullo sfondo dietro il foglio inferiore con una sorta di effetto di parallasse.
  4. Un titolo (TextView) nella barra degli strumenti che appare quando il foglio di base lo raggiunge.
  5. La barra di notifica satus può trasformare il suo sfondo in trasparente o a colori.
  6. Un comportamento del foglio di fondo personalizzato con uno stato di "ancora".

Ora controlliamoli uno per uno:

ToolBars
Quando apri la visualizzazione in Google Maps, puoi vedere una barra degli strumenti in cui puoi effettuare ricerche, è l'unica che non sto facendo esattamente come Google Maps, perché volevo farlo più generico. In ogni caso che ToolBar è all'interno di un AppBarLayout ed ottenuto nascosto quando si inizia a trascinare il BottomSheet e appare di nuovo quando il BottomSheet raggiungere il COLLAPSED Stato.
Per realizzarlo è necessario:

  • creare un Behavior ed estenderlo da AppBarLayout.ScrollingViewBehavior
  • sovrascrivere i metodi layoutDependsOn e onDependentViewChanged . Facendolo ascolterai i movimenti del bottomSheet.
  • creare alcuni metodi per nascondere e mostrare AppBarLayout / ToolBar con animazioni.

Ecco come l'ho fatto per la prima barra degli strumenti o 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();
}

Ecco il file completo se ne hai bisogno

La seconda barra degli strumenti o la barra degli strumenti "Modale":
Devi sovrascrivere gli stessi metodi, ma in questo devi occuparti di più comportamenti:

  • mostra / nascondi la barra degli strumenti con animazioni
  • cambia colore / sfondo della barra di stato
  • mostra / nasconde il titolo BottomSheet nella barra degli strumenti
  • chiudi il bottomSheet o invialo allo stato compresso

Il codice per questo è un po 'esteso, quindi lascerò il link

Il FAB

Anche questo è un comportamento personalizzato, ma si estende da FloatingActionButton.Behavior . In onDependentViewChanged devi guardare quando raggiunge "offSet" o puntare dove vuoi nasconderlo. Nel mio caso, voglio nasconderlo quando è vicino alla seconda barra degli strumenti, quindi scaverò nel genitore FAB (un CoordinatorLayout) cercando l'AppBarLayout che contiene la barra degli strumenti, quindi uso la posizione di ToolBar come 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;
}

Completa il collegamento Comportamento FAB personalizzato

L'immagine dietro il BottomSheet con effetto di parallasse :
Come gli altri, è un comportamento personalizzato, l'unica cosa "complicata" in questo è il piccolo algoritmo che mantiene l'immagine ancorata al BottomSheet ed evita il collasso dell'immagine come l'effetto di parallasse predefinito:

@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;
}


Il file completo per l'immagine di sfondo con effetto di parallasse

Ora per la fine: il comportamento del documento di base personalizzato
Per raggiungere i 3 passaggi, devi prima capire che il predefinito BottomSheetBehavior ha 5 stati: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN e per il comportamento di Google Maps devi aggiungere uno stato intermedio tra collassato ed espanso: STATE_ANCHOR_POINT .
Ho provato ad estendere il bottomSheetBehavior predefinito senza successo, quindi ho appena copiato tutto il codice e ho modificato ciò di cui ho bisogno.
Per ottenere ciò di cui sto parlando, segui i seguenti passi:

  1. Creare una classe Java ed estenderla da CoordinatorLayout.Behavior<V>

  2. Copia il codice incolla dal file BottomSheetBehavior predefinito a quello nuovo.

  3. Modificare il metodo clampViewPositionVertical con il seguente codice:

    @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. Aggiungi un nuovo stato

    public static final int STATE_ANCHOR_POINT = X;

  5. Modificare i seguenti metodi: onLayoutChild , onStopNestedScroll , BottomSheetBehavior<V> from(V view) e setState (opzionale)



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;
}



Collegamento all'intero progetto in cui è possibile visualizzare tutti i comportamenti personalizzati

Ed ecco come appare:
[ CustomBottomSheetBehavior ]

Configurazione rapida

Assicurati che la seguente dipendenza venga aggiunta al file build.gradle dell'app in dipendenze:

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

Quindi puoi utilizzare il foglio in basso usando queste opzioni:

Fogli inferiori persistenti

È possibile ottenere un foglio inferiore persistente allegando un BottomSheetBehavior foglio inferiore a un bambino Vista di un CoordinatorLayout BottomSheetBehavior :

<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>

Quindi nel tuo codice puoi creare un riferimento usando:

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

Puoi impostare lo stato di BottomSheetBehavior usando il metodo setState () :

mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);

Puoi utilizzare uno di questi stati:

  • STATE_COLLAPSED : questo stato compresso è l'impostazione predefinita e mostra solo una parte del layout lungo il fondo. L'altezza può essere controllata con l' app:behavior_peekHeight attributo app:behavior_peekHeight (predefinito su 0)

  • STATE_EXPANDED : lo stato completamente espanso del foglio inferiore, dove è visibile l'intero foglio inferiore (se la sua altezza è inferiore al CoordinatorLayout contenente) o l'intero CoordinatorLayout è pieno

  • STATE_HIDDEN : disabilitato per impostazione predefinita (e abilitato con l' app:behavior_hideable attributo app:behavior_hideable ), abilitando questo consente agli utenti di scorrere verso il basso sul foglio inferiore per nascondere completamente il foglio inferiore

Se desideri ricevere i callback delle modifiche di stato, puoi aggiungere un 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  
   }  
 });  

Fogli di fondo modali con BottomSheetDialogFragment

È possibile realizzare un foglio inferiore modale utilizzando un oggetto BottomSheetDialogFragment .

BottomSheetDialogFragment è un foglio inferiore modale.
Questa è una versione di DialogFragment che mostra un foglio inferiore utilizzando BottomSheetDialog anziché una finestra di dialogo mobile.

Basta definire il frammento:

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

Quindi utilizzare questo codice per mostrare il frammento:

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

Questo frammento creerà un BottomSheetDialog .

Fogli di fondo modali con BottomSheetDialog

BottomSheetDialog è una finestra di dialogo disegnata come un foglio inferiore

Basta usare:

//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 questo caso non è necessario allegare un comportamento BottomSheet.

Apri il parametro BottomFragment BottomSheet in modalità estesa per impostazione predefinita.

BottomSheet DialogFragment si apre in STATE_COLLAPSED per impostazione predefinita. Quale può essere forzato per aprirsi a STATE_EXPANDED e occupare lo schermo di dispositivo completo con l'aiuto del seguente modello di codice.

@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;
}

Sebbene l'animazione delle finestre di dialogo sia leggermente visibile, l'operazione di apertura di DialogFragment a schermo intero è molto buona.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow