Jak przesuwać Pasek czynności wraz z nawigacją

To, co chcę zrobić, to wsunąć ActionBar wraz z NavigationDrawer, gdy szuflada jest otwarta. Obecnie nie korzystam z bibliotek stron trzecich i jeśli to możliwe, chcę, aby tak zostało. Wszystko czego potrzebuję to implementacja metody jak: getActionBarView.slide(dp);

Jest to kod, którego obecnie używam do tworzenia NavigationDrawer:

mDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {

    public void onDrawerClosed(View view) {
        invalidateOptionsMenu();

        // calling onPrepareOptionsMenu() to hide action bar icons
    }

    @Override
    public void onDrawerSlide(View drawerView, float slideOffset) {
        if (getDeviceType(getApplicationContext()) == DEVICE_TYPE_PHONE) {
            drawerLayout.setScrimColor(Color.parseColor("#00FFFFFF"));
            float moveFactor = (listView.getWidth() * slideOffset);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                all_menu_container_parent.setTranslationX(moveFactor);
            } else {
                TranslateAnimation anim = new TranslateAnimation(lastTranslate, moveFactor, 0.0f, 0.0f);
                anim.setDuration(0);
                anim.setFillAfter(true);
                all_menu_container_parent.startAnimation(anim);

                lastTranslate = moveFactor;
            }
        }
    }

    public void onDrawerOpened(View drawerView) {
        // calling onPrepareOptionsMenu() to hide action bar icons
    }
};
drawerLayout.setDrawerListener(mDrawerToggle);

Ale nie robi tego, co chcę, produkuje to:

Obecnie utknąłem z tym

Chcę osiągnąć to:

bieżący zrzut ekranu z aplikacji

Author: Xaver Kapeller, 2014-05-21

1 answers

Uwaga:[75]} ta odpowiedź została pierwotnie napisana, gdy Android 4.4 (KitKat) był jeszcze całkiem nowy. Od Androida 5.0, a zwłaszcza ze względu na wprowadzenie ToolBar tej odpowiedzi nie można uważany za up-to-date już! Ale z technicznego punktu widzenia i dla Ci z Was, którzy chcą dowiedzieć się o wewnętrznym działaniu Androida ta odpowiedź może nadal mieć wiele wartości!

{[17] } został specjalnie zaprojektowany, aby znajdować się pod ActionBar i nie ma sposobu, aby zaimplementować NavigationDrawer, aby ActionBar poruszać się z nim-chyba że może szukasz View, który tworzy ActionBar i animowanie go obok NavigationDrawer, ale nigdy nie polecam czegoś takiego, ponieważ byłoby to trudne i podatne na błędy. Moim zdaniem masz tylko dwie opcje:

  1. używając biblioteka jak SlidingMenu
  2. implementacja niestandardowego menu przesuwnego

Skoro powiedziałeś, że nie chcesz korzystać z biblioteki implementującej niestandardowe menu przesuwne jest jedyną opcją, na szczęście nie jest to naprawdę trudne, gdy wiesz, jak to zrobić.


1) Podstawowe Wyjaśnienie

Możesz przenieść całą zawartość Activity - mam na myśli wszystko, łącznie z ActionBar - poprzez umieszczenie marginesu lub paddingu na View, który składa się na Activity. To View jest rodzicem View o id android.R.id.content:

View content = (View) activity.findViewById(android.R.id.content).getParent();

Na Honeycomb (Android w wersji 3.0 - poziom API 11) lub powyżej-innymi słowy po wprowadzeniu ActionBar - należy użyć marginesów, aby zmienić pozycję Activities, a w poprzednich wersjach użyć paddingu. Aby to uprościć, zalecam stworzenie metod pomocniczych, które wykonują poprawną akcję dla każdego poziomu API. Najpierw przyjrzyjmy się, jak ustawić pozycję Activity:

public void setActivityPosition(int x, int y) {
    // With this if statement we can check if the devices API level is above Honeycomb or below
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        // On Honeycomb or abvoe we set a margin
        FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
        contentParams.setMargins(x, y, -x, -y);
        this.content.setLayoutParams(contentParams);
    } else {
        // And on devices below Honeycomb we set a padding
        this.content.setPadding(x, y, -x, -y);
    }
}

Zauważ, że w obu przypadkach istnieje albo ujemny margines, albo ujemna wyściółka po przeciwnych stronach. Ma to zasadniczo zwiększyć rozmiar Activity poza jej normalnymi granicami. Zapobiega to zmianie rzeczywistego rozmiaru Activity, gdy przesuniemy go gdzieś.

Potrzebujemy dodatkowo dwóch metod, aby uzyskać bieżącą pozycję Activity. Jeden dla pozycji x, jeden dla pozycji y:

public int getActivityPositionX() {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        // On Honeycomb or above we return the left margin
        FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
        return contentParams.leftMargin;
    } else {
        // On devices below Honeycomb we return the left padding
        return this.content.getPaddingLeft();
    }
}

public int getActivityPositionY() {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        // On Honeycomb or above we return the top margin
        FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
        return contentParams.topMargin;
    } else {
        // On devices below Honeycomb we return the top padding
        return this.content.getPaddingTop();
    }
} 

Dodawanie animacji jest również bardzo proste. Jedyną ważną rzeczą jest tutaj odrobina matematyki, aby animować ją z poprzedniej pozycji do nowej pozycji

// We get the current position of the Activity
final int currentX = getActivityPositionX();
final int currentY = getActivityPositionY();

// The new position is set
setActivityPosition(x, y);

// We animate the Activity to slide from its previous position to its new position
TranslateAnimation animation = new TranslateAnimation(currentX - x, 0, currentY - y, 0);
animation.setDuration(500);
this.content.startAnimation(animation);

Możesz wyświetlić View w miejscu, które jest odsłaniając Activity przez dodanie go do rodzica View:

final int currentX = getActivityPositionX();

FrameLayout menuContainer = new FrameLayout(context);

// The width of the menu is equal to the x position of the `Activity`
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(currentX, ViewGroup.LayoutParams.MATCH_PARENT);
menuContainer.setLayoutParams(params);

ViewGroup parent = (ViewGroup) content.getParent();
parent.addView(menuContainer);

I to prawie wszystko, czego potrzebujesz, aby utworzyć podstawowe menu przesuwne, które działa na większości, jeśli nie wszystkich urządzeń powyżej Eclair (Android 2.1-poziom API 7).


2) animowanie Activity

Pierwszą częścią tworzenia menu przesuwnego jest przesunięcie Activity z drogi. Jako takie powinniśmy najpierw spróbować przenieść Activity wokół tak:
Tutaj wpisz opis obrazka

Aby stworzyć to my wystarczy umieścić powyższy kod razem:

import android.os.Build;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;

public class ActivitySlider {

    private final FragmentActivity activity;
    private final View content;

    public ActivitySlider(FragmentActivity activity) {
        this.activity = activity;

        // Here we get the content View from the Activity.
        this.content = (View) activity.findViewById(android.R.id.content).getParent();
    }

    public void slideTo(int x, int y) {

        // We get the current position of the Activity
        final int currentX = getActivityPositionX();
        final int currentY = getActivityPositionY();

        // The new position is set
        setActivityPosition(x, y);

        // We animate the Activity to slide from its previous position to its new position
        TranslateAnimation animation = new TranslateAnimation(currentX - x, 0, currentY - y, 0);
        animation.setDuration(500);
        this.content.startAnimation(animation);
    }

    public void setActivityPosition(int x, int y) {
        // With this if statement we can check if the devices API level is above Honeycomb or below
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // On Honeycomb or above we set a margin
            FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
            contentParams.setMargins(x, y, -x, -y);
            this.content.setLayoutParams(contentParams);
        } else {
            // And on devices below Honeycomb we set a padding
            this.content.setPadding(x, y, -x, -y);
        }
    }

    public int getActivityPositionX() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // On Honeycomb or above we return the left margin
            FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
            return contentParams.leftMargin;
        } else {
            // On devices below Honeycomb we return the left padding
            return this.content.getPaddingLeft();
        }
    }

    public int getActivityPositionY() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // On Honeycomb or above we return the top margin
            FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
            return contentParams.topMargin;
        } else {
            // On devices below Honeycomb we return the top padding
            return this.content.getPaddingTop();
        }
    }
}

Możesz użyć klasy ActivitySlider w następujący sposób:

ActivitySlider slider = new ActivitySlider(activity);

// This would move the Activity 400 pixel to the right and 100 pixel down
slider.slideTo(400, 100);

3) Dodawanie menu przesuwnego

Teraz chcemy ujawnić menu, gdy Activity przesunie się w ten sposób: Tutaj wpisz opis obrazka
Jak widać również popycha ActionBar na bok.

Klasa ActivitySlider nie musi być tak bardzo modyfikowana, aby utworzyć przesuwne menu, w zasadzie dodajemy tylko dwie metody, showMenu() i hideMenu(). Będę trzymać się najlepszych praktyk i użyj Fragment jako menu przesuwne. Pierwszą rzeczą, której potrzebujemy, jest View - na przykład FrameLayout - jako kontener dla naszego Fragment. Musimy dodać to View do rodzica View z Activity:

// We get the View of the Activity
View content = (View) activity.findViewById(android.R.id.content).getParent();

// And its parent
ViewGroup parent = (ViewGroup)  content.getParent();

// The container for the menu Fragment is a FrameLayout
// We set an id so we can perform FragmentTransactions later on
FrameLayout menuContainer = new FrameLayout(this.activity);
menuContainer.setId(R.id.flMenuContainer);

// The visibility is set to GONE because the menu is initially hidden
menuContainer.setVisibility(View.GONE);

// The container for the menu Fragment is added to the parent
parent.addView(menuContainer);

Ponieważ ustawiamy widoczność kontenera View na widoczną tylko wtedy, gdy menu przesuwne jest rzeczywiście otwarte, możemy użyć następującej metody, aby sprawdzić, czy menu jest otwarte czy zamknięte:

public boolean isMenuVisible() {
    return this.menuContainer.getVisibility() == View.VISIBLE;
}

Aby ustawić menu Fragment dodajemy metodę setter, która wykonuje FragmentTransaction i dodaje menu Fragment do FrameLayout:

public void setMenuFragment(Fragment fragment) {
    FragmentManager manager = this.activity.getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.replace(R.id.flMenuContainer, fragment);
    transaction.commit();
}

Dodaję również drugi setter, który tworzy instancję Fragment Z Class dla wygody:

public <T extends Fragment> void setMenuFragment(Class<T> cls) {
    Fragment fragment = Fragment.instantiate(this.activity, cls.getName());
    setMenuFragment(fragment);
}

Jest jeszcze jedna ważna rzecz do rozważenia, jeśli chodzi o menu Fragment. Działamy znacznie dalej w hierarchii View niż zwykle. Jako takie musimy wziąć pod uwagę takie rzeczy jak wysokość paska stanu. Gdybyśmy nie uwzględnili tego na górze menu Fragment czy bylibyśmy ukryci za pasek stanu. Możesz uzyskać wysokość paska stanu w następujący sposób:

Rect rectangle = new Rect();
Window window = this.activity.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
final int statusBarHeight = rectangle.top;

Musimy umieścić górny margines na pojemniku View menu Fragment w następujący sposób:

// These are the LayoutParams for the menu Fragment
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT);

// We put a top margin on the menu Fragment container which is equal to the status bar height
params.setMargins(0, statusBarHeight, 0, 0);
menuContainer.setLayoutParams(fragmentParams);

Wreszcie możemy to wszystko połączyć:

import android.graphics.Rect;
import android.os.Build;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import at.test.app.R;
import at.test.app.helper.LayoutHelper;

public class ActivitySlider {

    private final FragmentActivity activity;
    private final View content;
    private final FrameLayout menuContainer;

    public ActivitySlider(FragmentActivity activity) {
        this.activity = activity;

        // We get the View of the Activity
        this.content = (View) activity.findViewById(android.R.id.content).getParent();

        // And its parent
        ViewGroup parent = (ViewGroup) this.content.getParent();

        // The container for the menu Fragment is added to the parent. We set an id so we can perform FragmentTransactions later on
        this.menuContainer = new FrameLayout(this.activity);
        this.menuContainer.setId(R.id.flMenuContainer);

        // We set visibility to GONE because the menu is initially hidden
        this.menuContainer.setVisibility(View.GONE);
        parent.addView(this.menuContainer);
    }

    public <T extends Fragment> void setMenuFragment(Class<T> cls) {
        Fragment fragment = Fragment.instantiate(this.activity, cls.getName());
        setMenuFragment(fragment);
    }

    public void setMenuFragment(Fragment fragment) {
        FragmentManager manager = this.activity.getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.replace(R.id.flMenuContainer, fragment);
        transaction.commit();
    }

    public boolean isMenuVisible() {
        return this.menuContainer.getVisibility() == View.VISIBLE;
    }

    // We pass the width of the menu in dip to showMenu()
    public void showMenu(int dpWidth) {

        // We convert the width from dip into pixels
        final int menuWidth = LayoutHelper.dpToPixel(this.activity, dpWidth);

        // We move the Activity out of the way
        slideTo(menuWidth, 0);

        // We have to take the height of the status bar at the top into account!
        Rect rectangle = new Rect();
        Window window = this.activity.getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
        final int statusBarHeight = rectangle.top;

        // These are the LayoutParams for the menu Fragment
        FrameLayout.LayoutParams fragmentParams = new FrameLayout.LayoutParams(menuWidth, ViewGroup.LayoutParams.MATCH_PARENT);

        // We put a top margin on the menu Fragment container which is equal to the status bar height
        fragmentParams.setMargins(0, statusBarHeight, 0, 0);
        this.menuContainer.setLayoutParams(fragmentParams);

        // Perform the animation only if the menu is not visible
        if(!isMenuVisible()) {

            // Visibility of the menu container View is set to VISIBLE
            this.menuContainer.setVisibility(View.VISIBLE);

            // The menu slides in from the right
            TranslateAnimation animation = new TranslateAnimation(-menuWidth, 0, 0, 0);
            animation.setDuration(500);
            this.menuContainer.startAnimation(animation);
        }
    }

    public void hideMenu() {

        // We can only hide the menu if it is visible
        if(isMenuVisible()) {

            // We slide the Activity back to its original position
            slideTo(0, 0);

            // We need the width of the menu to properly animate it
            final int menuWidth = this.menuContainer.getWidth();

            // Now we need an extra animation for the menu fragment container
            TranslateAnimation menuAnimation = new TranslateAnimation(0, -menuWidth, 0, 0);
            menuAnimation.setDuration(500);
            menuAnimation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    // As soon as the hide animation is finished we set the visibility of the fragment container back to GONE
                    menuContainer.setVisibility(View.GONE);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
            this.menuContainer.startAnimation(menuAnimation);
        }
    }

    public void slideTo(int x, int y) {

        // We get the current position of the Activity
        final int currentX = getActivityPositionX();
        final int currentY = getActivityPositionY();

        // The new position is set
        setActivityPosition(x, y);

        // We animate the Activity to slide from its previous position to its new position
        TranslateAnimation animation = new TranslateAnimation(currentX - x, 0, currentY - y, 0);
        animation.setDuration(500);
        this.content.startAnimation(animation);
    }

    public void setActivityPosition(int x, int y) {
        // With this if statement we can check if the devices API level is above Honeycomb or below
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // On Honeycomb or above we set a margin
            FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
            contentParams.setMargins(x, y, -x, -y);
            this.content.setLayoutParams(contentParams);
        } else {
            // And on devices below Honeycomb we set a padding
            this.content.setPadding(x, y, -x, -y);
        }
    }

    public int getActivityPositionX() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // On Honeycomb or above we return the left margin
            FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
            return contentParams.leftMargin;
        } else {
            // On devices below Honeycomb we return the left padding
            return this.content.getPaddingLeft();
        }
    }

    public int getActivityPositionY() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // On Honeycomb or above we return the top margin
            FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams();
            return contentParams.topMargin;
        } else {
            // On devices below Honeycomb we return the top padding
            return this.content.getPaddingTop();
        }
    }
}

Używam statycznej metody pomocniczej w showMenu() do konwersji dip na piksele. Oto Kod tej metody:

public static int dpToPixel(Context context, int dp) {
    float scale = getDisplayDensityFactor(context);
    return (int) (dp * scale + 0.5f);
}

private static float getDisplayDensityFactor(Context context) {
    if (context != null) {
        Resources res = context.getResources();
        if (res != null) {
            DisplayMetrics metrics = res.getDisplayMetrics();
            if(metrics != null) {
                return metrics.density;
            }
        }
    }
    return 1.0f;
}

Możesz użyć nowej wersji klasy ActivitySlider w następujący sposób:

ActivitySlider slider = new ActivitySlider(activity);
slider.setMenuFragment(MenuFragment.class);

// The menu is shown with a width of 200 dip
slider.showMenu(200);

...

// Hide the menu again
slider.hideMenu();

4) Wnioski I Testy

Robiąc coś takiego jest zaskakująco proste, gdy wiesz, że możesz po prostu umieścić margines lub wyściółkę na View Activity. Ale trudność polega na tym, że działa na wielu różnych urządzeniach. Implementacje mogą się wiele zmienić na wielu poziomach API, co może mieć znaczny wpływ na to, jak to się zachowuje. Mimo tego, każdy kod, który tu opublikowałem, powinien działać na większości, jeśli nie wszystkich urządzeń powyżej Eclair (Android 2.1 - poziom API 7) bez żadnych problemów.
Oczywiście rozwiązanie I opublikowane tutaj nie jest kompletna, może użyć trochę dodatkowego polerowania i czyszczenia, więc nie krępuj się, aby poprawić kod do własnych potrzeb!

Przetestowałem wszystko na następujących urządzeniach:

HTC

  • One M8 (Android 4.4.2 - KitKat): Działa
  • Sensation (Android 4.0.3-Ice Cream Sandwich): Working
  • Desire (Android 2.3.3-Gingerbread): Działa
  • One (Android 4.4.2-KitKat): Działa

Samsung

  • Galaxy S3 Mini (Android 4.1.2-Jelly Bean): Działa
  • Galaxy S4 Mini (Android 4.2.2-Jelly Bean): Działa
  • Galaxy S4 (Android 4.4.2-KitKat): Działa
  • Galaxy S5 (Android 4.4.2-KitKat): Działa
  • Galaxy S Plus (Android 2.3.3-Gingerbread): Działa
  • Galaxy Ace (Android 2.3.6-Pierniki): Robocze
  • Galaxy S2 (Android 4.1.2-Jelly Bean): Działa
  • Galaxy S3 (Android 4.3-Jelly Bean): Działa
  • Galaxy Note 2 (Android 4.3-Jelly Bean): Działa
  • Galaxy Nexus (Android 4.2.1-Jelly Bean): Działa

Motorola

  • Moto G (Android 4.4.2 - KitKat): Działa

LG

  • Nexus 5 (Android 4.4.2 - KitKat): Działa

ZTE

  • Blade (Android 2.1-Eclair): Działa

Mam nadzieję, że mogę ci pomóc i jeśli masz dodatkowe pytania lub cokolwiek innego jest niejasne, prosimy o zadawanie!

 60
Author: Xaver Kapeller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-28 10:18:01