简体   繁体   中英

Android DrawerLayout icon animation

I'm trying to animate the icons in my DrawerLayout using an AnimationDrawable . I created a new project selecting Navigation Drawer Activity as the main activity. I created an animation from a set of XML files and the animation runs in the previewer in Android Studio. I set the animation XML as the icon on the menu item and add a DrawerListener.onDrawerOpened(View V) that gets the menu item, gets its icon and calls start() as described here https://developer.android.com/guide/topics/graphics/drawable-animation . This seemed pretty straightforward to implement but also too simple which is why I wasn't too surprised when it didn't work. It just shows the first image in the series and never changes to the other images. If I run the app in the debugger I see that the icon is indeed an AnimationDrawable and that the icon's instance variables mAnimating and mRunning change from false to true when I call start() on it. Obviously there is something more I need to be doing but I don't know what. I did notice that the icon's mAnimationRunnable is set to null and I'm guessing this might have something to do with it.

Animation ld.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
    <item android:drawable="@drawable/ic_untitled_1" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_2" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_3" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_4" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_5" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_6" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_7" android:duration="500" />
    <item android:drawable="@drawable/ic_untitled_8" android:duration="500" />
</animation-list>

Drawer activity_main_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_home"
            android:icon="@drawable/ld"
            android:title="@string/menu_home" />
        <item
            android:id="@+id/nav_settings"
            android:icon="@drawable/ic_menu_gallery"
            android:title="@string/menu_settings" />
        <item
            android:id="@+id/nav_timers"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="@string/menu_timers" />
    </group>
</menu>

Main layout activity_main.xml

<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        android:id="@+id/app_bar_main"
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>

Main activity onCreate MainActivity.java

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        com.company.databinding.ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        setSupportActionBar(binding.appBarMain.toolbar);

        DrawerLayout drawer = binding.drawerLayout;
        NavigationView navigationView = binding.navView;

        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_settings, R.id.nav_timers).setOpenableLayout(drawer).build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(navigationView, navController);

        binding.drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
            @Override
            public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
            }

            @Override
            public void onDrawerOpened(View drawerView) {
              ((AnimationDrawable)navigationView.getMenu().getItem(0).getIcon()).start();
            }

            @Override
            public void onDrawerClosed(@NonNull View drawerView) {
               ((AnimationDrawable)navigationView.getMenu().getItem(0).getIcon()).stop();
            }

            @Override
            public void onDrawerStateChanged(int newState) {
            }
        });
    }

I haven't been able to make the AnimationDrawable start() as a menu icon but it does start if I display it in an ImageView in the navigation header. So I manually animated it myself. Here is my MenuIconAnimator class. I still use a DrawerListener and in onDrawerOpened(View drawerView) I create an instance of my animator class passing it the menu item and then I call start() on it. This swaps the menu icon based on the parameters setup in the animation XML. And then in onDrawerClosed(@NonNull View drawerView) I call the method stopThread() on the MenuIconAnimator and the thread stops.

Here are the relevant DrawerListener methods. menuIconAnimator is an instance variable on my activity class.

            @Override
            public void onDrawerOpened(View drawerView) {
                menuIconAnimator = new MenuIconAnimator(navigationView.getMenu().getItem(0));
                menuIconAnimator.start();
            }

            @Override
            public void onDrawerClosed(@NonNull View drawerView) {
                menuIconAnimator.stopThread();
            }

Here is my MenuIconAnimator class.

package com.leridiandynamics.smartrecirculationcontrol2.ui;

import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.view.MenuItem;

public class MenuIconAnimator extends Thread{
    Boolean running=true;
    MenuItem menuItem;

    public MenuIconAnimator(MenuItem menuItem){
        this.menuItem = menuItem;
    }

    public void stopThread(){
        running = false;
    }

    public void run() {
        // Get the icon that is set on the menuItem. This is an AnimationDrawable.
        // Hold onto it while we manually animate each frame by setting the frame
        // drawable as the menuItem icon. 
        AnimationDrawable animationDrawable = ((AnimationDrawable)menuItem.getIcon());
        Handler mainHandler = new Handler(Looper.getMainLooper());
        while (running) {
            try {
                // loop through each frame setting it as the icon on the menu item and then sleep 
                // the thread for the duration of the frame. 
                for (int i=0; i<animationDrawable.getNumberOfFrames();i++){
                    Drawable drawable = animationDrawable.getFrame(i);
                    mainHandler.post(() -> menuItem.setIcon(drawable));
                    sleep(animationDrawable.getDuration(i));
                    if (!running) break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // restore the original animationDrawable on the menuItem so it is available the next time 
        // the MenuIconAnimator is called.
        mainHandler.post(() -> menuItem.setIcon(animationDrawable));
    }
}

UPDATE: It seems that if you have the animation running too quickly the menu is not very responsive to user input. Maybe that is why animation doesn't work on the menu items out of the box.

UPDATE 2: I have discovered that if the menu item's icon is changed while the user is tapping the menu item, the tap does not register. Finger touch, icon change, finger release...the menu item selection does not register.

UPDATE 3: This does not appear to be a viable solution to making AnimationDrawable work on a MenuItem . When the icon is changed the entire menu appears to redraw and any touch events are reset. I submitted an issue with Google regarding AnimationDrawable not animating on a MenuItem . https://issuetracker.google.com/issues/226640828 Will see if this gets addressed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM