web-dev-qa-db-fra.com

Développer / Réduire l'animation de la barre d'outils Lollipop (application Telegram)

J'essaie de comprendre comment l'animation de développement/réduction de la barre d'outils est effectuée. Si vous consultez les paramètres de l'application Telegram, vous verrez qu'il existe une vue en liste et la barre d'outils. Lorsque vous faites défiler l'écran vers le bas, la barre d'outils se réduit et lorsque vous faites défiler vers le haut, elle se développe. Il y a aussi l'animation de la photo de profil et du FAB. Est-ce que quelqu'un a une idée à ce sujet? Pensez-vous qu'ils ont construit toutes les animations par-dessus? Peut-être qu'il me manque quelque chose des nouvelles API ou de la bibliothèque de support.

J'ai remarqué le même comportement sur l'application Google Agenda, lorsque vous ouvrez Spinner (je ne pense pas que ce soit une spinner, mais ça ressemble à): la barre d'outils s'agrandit et lorsque vous faites défiler l'écran vers le haut, il s'effondre.

Juste pour clarifier: je n'ai pas besoin de la méthode QuickReturn. Je sais que l'application Telegram utilise probablement quelque chose de similaire. La méthode exacte dont j'ai besoin est l'effet de l'application Google Agenda. J'ai essayé avec

Android:animateLayoutChanges="true"

et la méthode expand fonctionne plutôt bien. Mais évidemment, si je fais défiler la liste, la barre d’outils ne s’effondre pas.

J'ai aussi pensé à ajouter un GestureListener mais je veux savoir s'il existe des API ou des méthodes plus simples pour y parvenir.

S'il n'y en a pas, je pense que je vais aller avec le GestureListener. J'espère avoir un effet fluide de l'animation.

Merci!

72
edoardotognoni

Modifier:

Depuis la sortie de la Android bibliothèque de support de conception, il existe une solution plus simple. Vérifiez réponse de joaquin

-

Voici comment je l'ai fait, il y a probablement beaucoup d'autres solutions mais celle-ci a fonctionné pour moi.

  1. Tout d’abord, vous devez utiliser un Toolbar avec un arrière-plan transparent. Le Toolbar en expansion & réduction est en fait un faux situé sous le Toolbar transparent. (Vous pouvez voir sur la première capture d'écran ci-dessous - celle avec les marges - que c'est aussi comme cela qu'ils l'ont fait dans Telegram).

    Nous conservons uniquement le Toolbar réel pour le NavigationIcon et le débordement MenuItem.

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

  2. Tout ce qui se trouve dans le rectangle rouge sur la deuxième capture d'écran (c'est-à-dire que le faux Toolbar et le FloatingActionButton) est en fait un en-tête que vous ajoutez aux paramètres ListView (ou ScrollView).

    Donc, vous devez créer une mise en page pour cet en-tête dans un fichier séparé qui pourrait ressembler à ceci:

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

    (Notez que vous pouvez utiliser des marges/marges négatives pour que la fab soit à cheval sur 2 Views)

  3. Maintenant vient la partie intéressante. Pour animer l'expansion de notre faux Toolbar, nous implémentons le 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;
    }
    

Notez qu'il y a certaines parties de ce code que je n'ai pas testées, alors n'hésitez pas à mettre en évidence les erreurs. Mais dans l’ensemble, je sais que cette solution fonctionne, même si je suis sûr qu’elle peut être améliorée.

EDIT 2:

Il y avait quelques erreurs dans le code ci-dessus (que je n'avais pas testées jusqu'à aujourd'hui ...), alors j'ai changé quelques lignes pour que cela fonctionne:

  1. J'ai introduit une autre variable, minHeaderTranslation, qui a remplacé minHeaderHeight;
  2. J'ai modifié la valeur de traduction en Y appliquée à l'en-tête. Vue de:

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

    à :

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

    L'expression précédente ne fonctionnait pas du tout, j'en suis désolée ...

  3. Le calcul du ratio a également changé, de sorte qu'il évolue désormais de la barre d'outils inférieure (au lieu du haut de l'écran) vers l'en-tête complet.

110
MathieuMaree

Consultez également CollapsingTitleLayout écrit par Chris Banes dans Android équipe: https://plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN

enter image description here

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

26
hidro

Utiliser la bibliothèque de support de conception http://Android-developers.blogspot.in/2015/05/Android-design-support-library.html

inclure ceci dans build.gradle

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

pour la vue recycleur inclure ceci aussi

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>

Votre activité doit s'étendre 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);
    }

}

Votre thème d'application devrait être comme ça

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

Ceci est ma mise en œuvre:

collapsedHeaderHeight et expandedHeaderHeight sont définis ailleurs, avec la fonction getAnimationProgress je peux obtenir la progression de développement/réduction, en me basant sur cette valeur, je fais mon animation et affiche/masque le réel entête.

  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