Zoeken…


Invoering

Een onderste vel is een vel dat omhoog schuift vanaf de onderkant van het scherm.

Opmerkingen

Onderbladen schuiven omhoog vanaf de onderkant van het scherm om meer inhoud te onthullen.
Ze zijn toegevoegd aan de Android Support Library in versie 23.2.0.

BottomSheetBehavior zoals Google maps

2.1.x

Dit voorbeeld is afhankelijk van ondersteuningsbibliotheek 23.4.0. +.

BottomSheetBehavior wordt gekenmerkt door:

  1. Twee werkbalken met animaties die reageren op bewegingen van het ondervel.
  2. Een FAB die verbergt wanneer deze zich in de buurt van de "modale werkbalk" bevindt (degene die verschijnt wanneer u naar boven schuift).
  3. Een achtergrondafbeelding achter het onderste vel met een soort parallax-effect.
  4. Een titel (tekstweergave) op de werkbalk die verschijnt wanneer het onderste blad deze bereikt.
  5. De meldingssatusbalk kan de achtergrond transparant of full colour maken.
  6. Een aangepast ondervelgedrag met een "anker" -status.

Laten we ze nu een voor een bekijken:

werkbalken
Wanneer u die weergave opent in Google Maps, ziet u een werkbalk waarin u kunt zoeken, het is de enige die ik niet precies doe zoals Google Maps, omdat ik het meer generiek wilde doen. Hoe dan ook dat ToolBar is binnen een AppBarLayout en het werd verborgen wanneer u begint met het BottomSheet te slepen en het lijkt opnieuw wanneer de BottomSheet bereikt de COLLAPSED staat.
Om dit te bereiken moet u:

  • maak een Behavior en breid het uit vanuit AppBarLayout.ScrollingViewBehavior
  • layoutDependsOn en onDependentViewChanged methoden overschrijven. Als je dit doet, luister je naar bottomSheet-bewegingen.
  • maak enkele methoden om de AppBarLayout / ToolBar met animaties te verbergen en weer te geven.

Dit is hoe ik het deed voor de eerste werkbalk of 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();
}

Hier is het complete bestand als je het nodig hebt

De tweede werkbalk of "Modale" werkbalk:
Je moet dezelfde methoden overschrijven, maar in deze moet je voor meer gedrag zorgen:

  • toon / verberg de werkbalk met animaties
  • verander statusbalk kleur / achtergrond
  • toon / verberg de titel van het BottomSheet in de werkbalk
  • sluit het onderste blad of stuur het naar samengevouwen toestand

De code voor deze is een beetje uitgebreid, dus ik zal de link laten

De FAB

Dit is ook een aangepast gedrag, maar strekt zich uit van FloatingActionButton.Behavior . In onDependentViewChanged moet je kijken wanneer het de "offSet" bereikt of aangeven waar je het wilt verbergen. In mijn geval wil ik het verbergen wanneer het in de buurt van de tweede werkbalk is, dus ik ga in op FAB-ouder (een CoordinatorLayout) op zoek naar de AppBarLayout die de ToolBar bevat, dan gebruik ik de OffSet positie zoals 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;
}

Voltooi de koppeling Aangepast FAB-gedrag

De afbeelding achter het BottomSheet met parallax-effect :
Net als de anderen is het een aangepast gedrag, het enige "gecompliceerde" ding in deze is het kleine algoritme dat de afbeelding verankerd houdt aan de BottomSheet en voorkomt dat de afbeelding instort als standaard parallax-effect:

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


Het complete bestand voor achtergrondafbeelding met parallax-effect

Nu voor het einde: het aangepaste BottomSheet-gedrag
Om de 3 stappen te bereiken, moet u eerst begrijpen dat standaard BottomSheetBehavior 5 staten heeft: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN en voor het gedrag van Google Maps moet u een middenstatus toevoegen tussen ingeklapt en uitgebreid: STATE_ANCHOR_POINT .
Ik probeerde de standaard bottomSheetBehavior zonder succes uit te breiden, dus ik heb gewoon alle code gekopieerd en aangepast wat ik nodig heb.
Volg de volgende stappen om te bereiken waar ik het over heb:

  1. Maak een Java-klasse en breid deze uit vanuit CoordinatorLayout.Behavior<V>

  2. Kopieer en plak code van standaard BottomSheetBehavior bestand naar je nieuwe.

  3. Wijzig de methode clampViewPositionVertical met de volgende 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. Voeg een nieuwe status toe

    openbare statische finale int STATE_ANCHOR_POINT = X;

  5. Wijzig de volgende methoden: onLayoutChild , onStopNestedScroll , BottomSheetBehavior<V> from(V view) en setState (optioneel)



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



Link naar het hele project waar u alle aangepaste gedragingen kunt zien

En zo ziet het eruit:
[ CustomBottomSheetBehavior ]

Snelle installatie

Zorg ervoor dat de volgende afhankelijkheid is toegevoegd aan het build.gradle-bestand van uw app onder afhankelijkheden:

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

Dan kunt u het onderste blad gebruiken met behulp van deze opties:

Aanhoudende onderbladen

U kunt een persistent BottomSheetBehavior bereiken dat een BottomSheetBehavior aan een kind bevestigt View of a 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>

Vervolgens kunt u in uw code een referentie maken met:

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

U kunt de status van uw BottomSheetBehavior instellen met de methode setState () :

mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);

U kunt een van deze statussen gebruiken:

  • STATE_COLLAPSED : deze samengevouwen staat is de standaard en toont slechts een deel van de lay-out onderaan. De hoogte kan worden geregeld met de app:behavior_peekHeight attribuut (standaard ingesteld op 0)

  • STATE_EXPANDED : de volledig uitgebreide status van het onderste vel, waarbij het hele onderste vel zichtbaar is (als de hoogte kleiner is dan de bevattende CoordinatorLayout ) of de hele CoordinatorLayout is gevuld

  • STATE_HIDDEN : standaard uitgeschakeld (en ingeschakeld met de app:behavior_hideable kenmerk app:behavior_hideable ), waardoor gebruikers op het onderste vel naar beneden kunnen vegen om het onderste vel volledig te verbergen

Als u de BottomSheetCallback wilt ontvangen, kunt u een 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  
   }  
 });  

Modale onderbladen met BottomSheetDialogFragment

U kunt een modale onderbladen realiseren met een BottomSheetDialogFragment .

Het BottomSheetDialogFragment is een modaal onderste vel.
Dit is een versie van DialogFragment die een onderste blad toont met BottomSheetDialog plaats van een zwevend dialoogvenster.

Definieer gewoon het 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);
    }
}

Gebruik vervolgens deze code om het fragment te tonen:

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

Dit fragment maakt een BottomSheetDialog .

Modale onderbladen met BottomSheetDialog

De BottomSheetDialog is een dialoogvenster met de stijl van een onderste blad

Gebruik gewoon:

//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 dit geval hoeft u geen BottomSheet-gedrag te koppelen.

Open BottomSheet DialogFragment standaard in de uitgebreide modus.

Dialoogvenster BottomSheet wordt standaard geopend in STATE_COLLAPSED . Die kan worden gedwongen om te openen naar STATE_EXPANDED en het volledige apparaatscherm in te nemen met behulp van de volgende STATE_EXPANDED .

@NonNull @Override openbaar dialoogvenster onCreateDialog (bundle saveInstanceState) {

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

Hoewel dialooganimatie enigszins merkbaar is, maar de taak om DialogFragment op volledig scherm te openen zeer goed is.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow