web-dev-qa-db-ger.com

Lollipop-Symbolleistenanimation erweitern / reduzieren (Telegramm-App)

Ich versuche herauszufinden, wie die Animation zum Erweitern/Reduzieren der Symbolleiste ausgeführt wird. Wenn Sie sich die Einstellungen der Telegramm-App ansehen, sehen Sie, dass es eine Listenansicht und die Symbolleiste gibt. Beim Scrollen nach unten 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 der Support-Bibliothek.

Ich habe das gleiche Verhalten in der Google Kalender-App festgestellt, als Sie den Spinner geöffnet haben (ich glaube nicht, dass es sich um einen Spinner handelt, aber er sieht so aus): Die Symbolleiste wird erweitert, und wenn Sie nach oben scrollen, wird er ausgeblendet.

Nur zur Verdeutlichung: 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. Aber offensichtlich, wenn ich in der ListView nach oben scrolle, wird die Symbolleiste nicht ausgeblendet.

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

Wenn es keine gibt, denke ich, werde ich mit dem GestureListener gehen. Ich hoffe, dass die Animation reibungslos funktioniert.

Vielen Dank!

72
edoardotognoni

Bearbeiten:

Seit der Veröffentlichung der Android Design Support Library) 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 funktionierte für mich.

  1. Zunächst müssen Sie eine Toolbar mit einem transparenten Hintergrund verwenden. Das expandierende & komprimierende Toolbar ist eigentlich ein falsches , das unter dem transparenten Toolbar liegt. (Sie können auf dem ersten Screenshot unten sehen - der mit den Rändern -, dass dies auch so war, wie sie es in Telegram gemacht haben).

    Wir behalten nur die tatsächliche Toolbar für die NavigationIcon und den Überlauf MenuItem.

    1. Transparent Toolbar - 2. Expanded header - 3. Collapsed header

  2. Alles, was auf dem zweiten Screenshot im roten Rechteck steht (dh die gefälschte Toolbar und die FloatingActionButton), ist tatsächlich eine Kopfzeile , die Sie zu den Einstellungen hinzufügen ListView (oder ScrollView).

    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/Auffüllungen verwenden können, damit die Fab auf 2 gespreizt wird Views)

  3. Nun kommt der interessante Teil. Um die Erweiterung unserer gefälschten Toolbar zu animieren, implementieren wir die ListViewonScrollListener.

    // 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. Insgesamt weiß ich jedoch, dass diese Lösung funktioniert, auch wenn ich mir sicher bin, dass sie verbessert werden kann.

EDIT 2:

Es gab einige Fehler im obigen Code (den ich bis heute nicht getestet habe ...), daher habe ich ein paar Zeilen geändert, damit es funktioniert:

  1. Ich führte eine andere Variable ein, minHeaderTranslation, die minHeaderHeight ersetzte.
  2. Ich habe den Y-Umrechnungswert geändert, der auf die Header-Ansicht angewendet wurde von:

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

    zu:

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

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

  3. Die Verhältnisberechnung wurde ebenfalls geändert, sodass sie nun von der unteren Symbolleiste (anstelle des oberen Bildschirmrandes) zur vollständig erweiterten Kopfzeile übergeht.

110
MathieuMaree

Schauen Sie sich auch CollapsingTitleLayout an, das von Chris Banes in Android team: https://plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN geschrieben wurde

enter image description here

Code: https://Gist.github.com/chrisbanes/91ac8a20acfbdc410a68

26
hidro

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.+'

aus Sicht des Recyclers auch

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 sich erweitern AppCompatActivity

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 aussehen

    <resources>
            <!-- Base application theme. -->   
            <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
            </style>
    </resources>
8
deniz

Dies ist meine Implementierung:

collapsedHeaderHeight und expandedHeaderHeight werden an einer anderen Stelle definiert. Mit der Funktion getAnimationProgress kann ich den Expand/Collapse-Fortschritt abrufen. Auf der Grundlage dieses Werts kann ich meine Animation erstellen und den Real ein-/ausblenden 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
        }

    }
1
cn123h