wake-up-neo.com

Schieben Sie das Bild mit der Official Support Library 23.x. + bottomSheet wie bei Google Maps

Update
Ich möchte das gleiche Verhalten wie Google Maps ausführen mit Support Library 23.x. + und ohne JEDE 3. Bibliothek

HINWEIS: Dies ist keine doppelte Frage, weil:

  1. Ich möchte Verhalten, Unterstützungsbibliothek und ohne JEDE Bibliothek von Drittanbietern verwenden (ich habe sie in den Fragentitel und die obige Beschreibung eingefügt).
  2. Ich wollte ALLE Verhaltensweisen, das Sie in der nächsten GIF sehen. Die anderen Fragen stellen ein oder zwei Verhaltensweisen und verwenden JEDE WEISE, um dies zu erreichen.

     like you can see in this gif

Ich habe bereits die offizielle bottomSheet-Funktion (sogar innerhalb eines Registers und eines Pagers). 

Was macht mich verrückt wie erreicht man das Image-Verhalten, das vom BottomSheet kommt, wenn man das offizielle bottomSheet verwendet?.

Ich habe versucht, Anker wie FAB ohne Erfolg zu verwenden.
Ich habe etwas über die Verwendung eines Scroll-Listeners gelesen, aber ppl sagte, es sei nicht glatt und schneller wie bei Google Maps.

Mein XML (ich glaube nicht, dass es helfen wird, aber trotzdem):

<?xml version="1.0" encoding="utf-8"?>
<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    tools:context=".ui.MasterActivity">

    <Android.support.design.widget.AppBarLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:layout_scrollFlags="scroll|enterAlways|snap">

            <Button
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                style="?android:attr/borderlessButtonStyle"
                Android:text="Departure"
                Android:layout_gravity="center"
                Android:id="@+id/buttonToolBar"
                />


        </Android.support.v7.widget.Toolbar>

        <Android.support.design.widget.TabLayout
            Android:id="@+id/tabs"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            app:tabBackground="@Android:color/white"
            app:tabTextColor="@color/colorAccent"
            app:tabSelectedTextColor="@color/colorAccent"/>

    </Android.support.design.widget.AppBarLayout>

    <Android.support.v4.view.ViewPager
        Android:id="@+id/viewpager"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />


    <Android.support.v4.widget.NestedScrollView
        Android:id="@+id/asdf"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:orientation="vertical"
        app:behavior_peekHeight="100dp"
        Android:fitsSystemWindows="true"
            app:layout_behavior="Android.support.design.widget.BottomSheetBehavior">

        <LinearLayout
            Android:id="@+id/qwert"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:orientation="vertical"
            Android:paddingBottom="16dp"
            Android:background="@Android:color/white"
            Android:padding="15dp">

            <TextView
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:text="BOOTOMSHEET TITLE"
                    Android:textAppearance="@style/TextAppearance.AppCompat.Title" />

            <Button
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:text="Button1"/>

            <TextView
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:text="text 2"
                Android:layout_margin="10dp"/>

            <TextView
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:text="text 3"
                Android:layout_margin="10dp"/>

            <TextView
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:text="text 4"
                Android:layout_margin="10dp"/>


            <FrameLayout
                Android:layout_width="match_parent"
                Android:layout_height="320dp"
                Android:background="@color/colorAccent">

                <TextView
                    Android:layout_width="wrap_content"
                    Android:layout_height="wrap_content"
                    Android:layout_gravity="center"
                    Android:text="Your remaining content here"
                    Android:textColor="@Android:color/white" />

            </FrameLayout>
        </LinearLayout>
    </Android.support.v4.widget.NestedScrollView>


    <Android.support.design.widget.FloatingActionButton
        Android:layout_height="wrap_content"
        Android:layout_width="wrap_content"
        app:layout_anchor="@id/asdf"
        app:layout_anchorGravity="top|right|end"
        Android:src="@drawable/abc_ic_search_api_mtrl_alpha_copy"
        Android:layout_margin="@dimen/fab_margin"
        Android:clickable="true"/>

</Android.support.design.widget.CoordinatorLayout>
39
MiguelHincapieC

Wenn Sie dies mit der Support Library 23.4.0. + Erreichen möchten, werde ich Ihnen sagen, wie ich es bekommen habe und wie es funktioniert.

anmerkung: Ich entschuldige mich für mein Englisch, ich habe versucht, eine Programmierantwort zu geben (kurz und gemischt mit nützlichem Code), aber es scheint nicht gut genug zu sein ...

Soweit ich sehen kann, hat die Aktivität/das Fragment das folgende Verhalten:

  1. 2 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".

note2: Diese Antwort spricht über 6 Dinge, nicht über 1 oder 2 wie andere Fragen, können Sie den Unterschied jetzt sehen?

Ok, jetzt lass uns einen bye one überprüfen:

Toolbars
Wenn Sie diese Ansicht in Google Maps öffnen, sehen Sie eine Symbolleiste, in der Sie suchen können. Es ist die einzige, die ich nicht wie Google Maps mache, weil ich es generischer machen wollte. Jedenfalls ist ToolBar in einer AppBarLayout enthalten und wurde beim Ziehen des BottomSheet ausgeblendet. Wenn BottomSheet den COLLAPSED-Status erreicht, wird es erneut angezeigt.
Um dies zu erreichen, benötigen Sie:

  • eine Behavior erstellen und aus AppBarLayout.ScrollingViewBehavior erweitern
  • Überschreiben Sie layoutDependsOn- und onDependentViewChanged-Methoden. Wenn Sie dies 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();
}

die komplette Datei, wenn Sie es brauchen

Die zweite Symbolleiste oder "Modale" Symbolleiste:
Sie müssen dieselben Methoden überschreiben, aber in dieser Methode müssen Sie sich um mehr Verhalten kümmern:

  • toolbar mit Animationen anzeigen/ausblenden
  • farbe/Hintergrund der Staturleiste ä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 lasse ich den Link

Die FAB

Dies ist auch ein benutzerdefiniertes Verhalten, erstreckt sich aber von FloatingActionButton.Behavior. In onDependentViewChanged müssen Sie nachsehen, wenn es das "offSet" erreicht, oder auf den Ort zeigen, an dem Sie es ausblenden möchten. In meinem Fall möchte ich es verbergen, wenn es sich in der Nähe der zweiten Symbolleiste befindet. Ich greife in FAB-übergeordnete Elemente (ein CoordiantorLayout) nach dem AppBarLayout, das die Toolbar enthält, und benutze 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 ist dies ein benutzerdefiniertes Verhalten. Der einzige "komplizierte" Vorgang in diesem Abschnitt ist der kleine Algorithmus, der das Bild im BottomSheet verankert und den Bildausfall 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;
}


komplette Datei für Hintergrundbild mit Parallaxe-Effekt

Nun zum Schluss: Das Verhalten des benutzerdefinierten BottomSheet
Um die 3 Schritte zu erreichen, müssen Sie zunächst verstehen, dass BottomSheetBehavior standardmäßig 5 Status hat: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN und für das Verhalten von Google Maps müssen Sie einen mittleren Status zwischen reduziert und erweitert hinzufügen: STATE_ANCHOR_POINT.
Ich habe versucht, das Standard-bottomSheetBehavior ohne Erfolg zu erweitern, also kopiere ich einfach den gesamten Code und ändere, was ich brauche.
Um das 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 BottomSheetBehavior-Standarddatei in Ihre neue.
  3. Ändern Sie die Methode clampViewPositionVertical mit dem folgenden 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;
}



Link zum Lochprojekt in dem Sie alle benutzerdefinierten Verhaltensweisen sehen können

note3: Beim nächsten Mal fügen Sie einen Kommentar hinzu und bitten Sie höflich um Änderung der Antwort. Oder fragen Sie, warum diese Antwort EINIGES Gleiches hat als andere Antworten von mir auf dasselbe Thema, bevor Sie es schließen oder als doppelt markieren.

Und so sieht es aus:
[CustomBottomSheetBehavior]

69
MiguelHincapieC

Sie können den Effekt mit einem Koordinator-Layout-Verhalten erzielen. Sie müssen eine CoordinatorLayout.Behaviour-Klasse erweitern und eine Abhängigkeit über eine Ansicht im Koordinatorlayout schreiben, wobei das Bild, das die Ansicht enthält, als untergeordnetes Element verbleibt. Vereinfachen Sie das Anpassen des benutzerdefinierten Verhaltens an das Bild mit der Ansicht . Um Unterstützung beim Schreiben von benutzerdefinierten Verhaltensweisen zu erhalten, folgen Sie dem Link Schreiben von benutzerdefinierten Verhaltensweisen

1