Lollipop-Symbolleistenanimation erweitern / reduzieren (Telegramm-App)

75

Ich versuche herauszufinden, wie die Animation zum Erweitern / Reduzieren der Symbolleiste ausgeführt wird. Wenn Sie sich die Einstellungen der Telegramm-App ansehen, werden Sie feststellen, dass es eine Listenansicht und die Symbolleiste gibt. Wenn Sie nach unten scrollen, wird die Symbolleiste ausgeblendet und beim Scrollen nach oben erweitert. Es gibt auch die Animation des Profilbildes und des FAB. Hat jemand eine Ahnung davon? Glaubst du, sie haben alle Animationen darauf aufgebaut? Vielleicht fehlt mir etwas in den neuen APIs oder in der Support-Bibliothek.

Beim Öffnen des Spinners ist mir dasselbe Verhalten in der Google Kalender-App aufgefallen (ich glaube nicht, dass es sich um einen Spinner handelt, aber es sieht so aus): Die Symbolleiste wird erweitert und beim Scrollen wird sie ausgeblendet.

Nur zur Klarstellung: Ich brauche die QuickReturn-Methode nicht. Ich weiß, dass die Telegramm-App wahrscheinlich etwas Ähnliches verwendet. Die genaue Methode, die ich benötige, ist der Google Kalender-App-Effekt. Ich habe es mit versucht

android:animateLayoutChanges="true"

und die expand-Methode funktioniert ziemlich gut. Wenn ich in der ListView nach oben scrolle, wird die Symbolleiste natürlich nicht ausgeblendet.

Ich habe auch darüber nachgedacht, ein hinzuzufügen, GestureListeneraber ich möchte wissen, ob es APIs oder einfachere Methoden gibt, um dies zu erreichen.

Wenn es keine gibt, denke ich, werde ich mit dem gehen GestureListener. Hoffentlich einen reibungslosen Effekt der Animation.

Vielen Dank!

edoardotognoni
quelle

Antworten:

109

Bearbeiten:

Seit der Veröffentlichung der Android Design-Unterstützungsbibliothek gibt es eine einfachere Lösung. Überprüfen Sie Joaquins Antwort

- -

Hier ist, wie ich es gemacht habe, es gibt wahrscheinlich viele andere Lösungen, aber diese hat für mich funktioniert.

  1. Zunächst müssen Sie eine Toolbarmit transparentem Hintergrund verwenden. Das Ausdehnen und Zusammenbrechen Toolbarist eigentlich eine Fälschung , die unter dem Transparenten liegt Toolbar. (Sie können auf dem ersten Screenshot unten sehen - dem mit den Rändern -, dass sie dies auch im Telegramm getan haben).

    Wir behalten nur das Ist Toolbarfür den NavigationIconund den Überlauf MenuItem.

    1. Transparente Symbolleiste - 2. Erweiterte Kopfzeile - 3. Reduzierte Kopfzeile

  2. Alles, was sich im zweiten Screenshot im roten Rechteck befindet (dh die Fälschung Toolbarund die FloatingActionButton), ist tatsächlich eine Kopfzeile , die Sie zu den Einstellungen ListView(oder ScrollView) hinzufügen .

    Sie müssen also ein Layout für diesen Header in einer separaten Datei erstellen, die folgendermaßen aussehen könnte:

     <!-- The headerView layout. Includes the fake Toolbar & the FloatingActionButton -->
    
     <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <RelativeLayout
            android:id="@+id/header_container"
            android:layout_width="match_parent"
            android:layout_height="@dimen/header_height"
            android:layout_marginBottom="3dp"
            android:background="@android:color/holo_blue_dark">
    
            <RelativeLayout
                android:id="@+id/header_infos_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:padding="16dp">
    
                <ImageView
                    android:id="@+id/header_picture"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_marginRight="8dp"
                    android:src="@android:drawable/ic_dialog_info" />
    
                <TextView
                    android:id="@+id/header_title"
                    style="@style/TextAppearance.AppCompat.Title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/header_picture"
                    android:text="Toolbar Title"
                    android:textColor="@android:color/white" />
    
                <TextView
                    android:id="@+id/header_subtitle"
                    style="@style/TextAppearance.AppCompat.Subhead"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/header_title"
                    android:layout_toRightOf="@+id/header_picture"
                    android:text="Toolbar Subtitle"
                    android:textColor="@android:color/white" />
    
            </RelativeLayout>
        </RelativeLayout>
    
        <FloatingActionButton
            android:id="@+id/header_fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="10dp"
            android:src="@drawable/ic_open_in_browser"/>
    
    </FrameLayout>
    

    (Beachten Sie, dass Sie negative Ränder / Polster verwenden können, damit die Fabrik auf 2 überspannt wird. Views)

  3. Jetzt kommt der interessante Teil. Um die Erweiterung unserer Fälschung zu animieren Toolbar, implementieren wir die ListView onScrollListener.

    // The height of your fully expanded header view (same than in the xml layout)
    int headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
    // The height of your fully collapsed header view. Actually the Toolbar height (56dp)
    int minHeaderHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
    // The left margin of the Toolbar title (according to specs, 72dp)
    int toolbarTitleLeftMargin = getResources().getDimensionPixelSize(R.dimen.toolbar_left_margin);
    // Added after edit
    int minHeaderTranslation;
    
    private ListView listView;
    
    // Header views
    private View headerView;
    private RelativeLayout headerContainer;
    private TextView headerTitle;
    private TextView headerSubtitle;
    private FloatingActionButton headerFab;
    
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View rootView = inflater.inflate(R.layout.listview_fragment, container, false);
        listView = rootView.findViewById(R.id.listview);
    
        // Init the headerHeight and minHeaderTranslation values
    
        headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
        minHeaderTranslation = -headerHeight + 
            getResources().getDimensionPixelOffset(R.dimen.action_bar_height);
    
        // Inflate your header view
        headerView = inflater.inflate(R.layout.header_view, listview, false);
    
        // Retrieve the header views
        headerContainer = (RelativeLayout) headerView.findViewById(R.id.header_container);
        headerTitle = (TextView) headerView.findViewById(R.id.header_title);
        headerSubtitle = (TextView) headerView.findViewById(R.id.header_subtitle);
        headerFab = (TextView) headerView.findViewById(R.id.header_fab);;
    
        // Add the headerView to your listView
        listView.addHeaderView(headerView, null, false);
    
        // Set the onScrollListener
        listView.setOnScrollListener(this);        
    
        // ...
    
        return rootView;
    }
    
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState)
    {
        // Do nothing
    }
    
    
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        Integer scrollY = getScrollY(view);
    
        // This will collapse the header when scrolling, until its height reaches
        // the toolbar height
        headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
    
        // Scroll ratio (0 <= ratio <= 1). 
        // The ratio value is 0 when the header is completely expanded, 
        // 1 when it is completely collapsed
        float offset = 1 - Math.max(
            (float) (-minHeaderTranslation - scrollY) / -minHeaderTranslation, 0f);
    
    
        // Now that we have this ratio, we only have to apply translations, scales,
        // alpha, etc. to the header views
    
        // For instance, this will move the toolbar title & subtitle on the X axis 
        // from its original position when the ListView will be completely scrolled
        // down, to the Toolbar title position when it will be scrolled up.
        headerTitle.setTranslationX(toolbarTitleLeftMargin * offset);
        headerSubtitle.setTranslationX(toolbarTitleLeftMargin * offset);
    
        // Or we can make the FAB disappear when the ListView is scrolled 
        headerFab.setAlpha(1 - offset);
    }
    
    
    // Method that allows us to get the scroll Y position of the ListView
    public int getScrollY(AbsListView view)
    {
        View c = view.getChildAt(0);
    
        if (c == null)
            return 0;
    
        int firstVisiblePosition = view.getFirstVisiblePosition();
        int top = c.getTop();
    
        int headerHeight = 0;
        if (firstVisiblePosition >= 1)
            headerHeight = this.headerHeight;
    
        return -top + firstVisiblePosition * c.getHeight() + headerHeight;
    }
    

Beachten Sie, dass es einige Teile dieses Codes gibt, die ich nicht getestet habe. Sie können also Fehler hervorheben. Aber insgesamt weiß ich, dass diese Lösung funktioniert, obwohl ich sicher bin, dass sie verbessert werden kann.

EDIT 2:

Der obige Code enthielt einige Fehler (die ich bis heute nicht getestet habe ...), daher habe ich einige Zeilen geändert, damit er funktioniert:

  1. Ich habe eine andere Variable eingeführt, minHeaderTranslation, die minHeaderHeight ersetzt hat.
  2. Ich habe den auf die Header-Ansicht angewendeten Y-Übersetzungswert geändert von:

        headerView.setTranslationY(Math.max(-scrollY, minHeaderTranslation));
    

    zu:

        headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
    

    Der vorherige Ausdruck funktionierte überhaupt nicht, das tut mir leid ...

  3. Die Verhältnisberechnung wurde ebenfalls geändert, sodass sie nun vom unteren Rand der Symbolleiste (anstelle des oberen Bildschirms) zum vollständig erweiterten Header wechselt.

MathieuMaree
quelle
5
Verschwindet der Header nicht, wenn Sie zu weit nach unten scrollen? Afaik-Header werden ebenfalls recycelt. Selbst wenn Sie Ihren Header so übersetzen, dass er wie oben angeheftet aussieht, würde er meiner Meinung nach einfach verschwinden, sobald die tatsächliche Position außer Sichtweite gerät.
Kuno
Du hast recht, Kuno. Ich habe es nicht versucht, aber das ist das erwartete Verhalten. Deshalb habe ich eine Symbolleiste in einem FrameLayout verwendet. Der Hauptinhalt befindet sich ÜBER der Symbolleiste mit einem marginTop von x. Um die Animation abzuspielen, übersetze ich einfach den
Hauptinhalt
@FedeAmura Sweet, aber ich denke, die FAB ist zu niedrig, wenn es am unteren Rand des Bildschirms ist :)
MathieuMaree
3
Mit der neuen Android Design Support Library ist jetzt alles einfacher . Sie können genau diesen Effekt erzielen, indem Sie diesem Tutorial des Suleiman-Benutzers folgen , das auf der Arbeit von Chris Banes für die Cheesesquare-Beispiel-App basiert . <br> <br> ** BEARBEITEN ** <br> Einige Benutzer fragten, ob sie dasselbe verwenden können Idee aber mit einem Symbol. [Saulmm Github Benutzer versuchte so etwas] ( github.com/saulmm
Joaquin Iurchuk
Diese Bibliothek ist ein guter Ansatz, um die grundlegende Animation und das Verhalten zu erhalten, aber das CollapsingToolbarLayout unterstützt derzeit nur eine Zeichenfolge als Titel. Kein Symbol
Sonic
8

Verwenden Sie die Design-Support-Bibliothek http://android-developers.blogspot.in/2015/05/android-design-support-library.html

Fügen Sie dies in build.gradle ein

compile 'com.android.support:design:22.2.0'    
compile 'com.android.support:appcompat-v7:22.2.+'

Für die Recycler-Ansicht ist dies ebenfalls enthalten

compile 'com.android.support:recyclerview-v7:22.2.0' 

    <!-- AppBarLayout allows your Toolbar and other views (such as tabs provided by TabLayout) 
    to react to scroll events in a sibling view marked with a ScrollingViewBehavior.-->
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <!-- specify tag app:layout_scrollFlags -->
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"/>

        <!-- specify tag app:layout_scrollFlags -->
        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:scrollbars="horizontal"
            android:layout_below="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"/>

        <!--  app:layout_collapseMode="pin" will help to pin this view at top when scroll -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="Title"
            android:gravity="center"
            app:layout_collapseMode="pin" />

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

    <!-- This will be your scrolling view. 
    app:layout_behavior="@string/appbar_scrolling_view_behavior" tag connects this features -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/list"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v7.widget.RecyclerView>

</android.support.design.widget.CoordinatorLayout>

Ihre Aktivität sollte AppCompatActivity erweitern

public class YourActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.your_layout);

        //set toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

}

Ihr App-Thema sollte so sein

    <resources>
            <!-- Base application theme. -->   
            <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
            </style>
    </resources>
deniz
quelle
Sehr schön, liebe dieses Design Unterstützung lib
luksfarris
Und ich hasse es. Ändert sich ständig. Leider muss ich ihm folgen.
Yar
1

Dies ist meine Implementierung:

collapsedHeaderHeightund expandedHeaderHeightsind woanders definiert, mit der Funktion getAnimationProgresskann ich den Fortschritt Erweitern / Reduzieren erhalten, basierend auf diesem Wert mache ich meine Animation und zeige / verstecke den realen Header.

  listForumPosts.setOnScrollListener(new AbsListView.OnScrollListener() {

        /**
         * @return [0,1], 0 means header expanded, 1 means header collapsed
         */
        private float getAnimationProgress(AbsListView view, int firstVisibleItem) {
            if (firstVisibleItem > 0)
                return 1;

            // should not exceed 1
            return Math.min(
                    -view.getChildAt(0).getTop() / (float) (expandedHeaderHeight - collapsedHeaderHeight), 1);
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // at render beginning, the view could be empty!
            if (view.getChildCount() > 0) {
                float animationProgress = getAnimationProgress(view, firstVisibleItem);
                imgForumHeaderAvatar.setAlpha(1-animationProgress);
                if (animationProgress == 1) {
                    layoutForumHeader.setVisibility(View.VISIBLE);
                } else {
                    layoutForumHeader.setVisibility(View.GONE);
                }
            }
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // do nothing
        }

    }
cn123h
quelle