Подтвердить что ты не робот

Как сместить ActionBar вместе с NavigationDrawer

Что я хочу сделать, это сместить ActionBar вместе с NavigationDrawer при открытии ящика. В настоящее время я не использую сторонние библиотеки и, если вообще возможно, хочу сохранить это. Все, что мне нужно, это реализация метода типа: getActionBarView.slide(dp);

Это код, который я использую для создания 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);

Но он не делает то, что я хочу, он производит это:

I am currently stuck with this

Я хочу достичь этого:

current screen shot from app

4b9b3361

Ответ 1

ОБРАТИТЕ ВНИМАНИЕ: Этот ответ был первоначально написан, когда Android 4.4 (KitKat) все еще был довольно новым. Начиная с Android 5.0 и особенно из-за введения ToolBar этот ответ не может быть считается актуальным больше! Но с технической точки зрения и для те из вас, кто хочет узнать о внутренней работе Android этот ответ может по-прежнему иметь большую ценность!

NavigationDrawer был специально разработан для размещения ниже ActionBar, и нет способа реализовать NavigationDrawer, чтобы сделать ActionBar перемещение с ним - если, возможно, не поиск View, который составляет ActionBar и оживить его вместе с NavigationDrawer, но я бы никогда не рекомендовал что-то подобное, поскольку это было бы трудно и подвержено ошибкам. По-моему, у вас есть только два варианта:

Поскольку вы сказали, что не хотите использовать библиотеку, реализующую пользовательское скользящее меню, это ваш единственный вариант, к счастью, это действительно не так сложно, как только вы знаете, как это сделать.


1) Основное объяснение

Вы можете перемещать весь контент Activity - я имею в виду все, включая ActionBar -, помещая маркер или дополнение на View, которое составляет Activity. Этот View является родительским элементом View с id android.R.id.content:

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

В Honeycomb (Android версии 3.0 - уровень API 11) или выше - другими словами, после того, как был введен ActionBar - вам нужно использовать поля для изменения позиции Activities, а в предыдущих версиях вам нужно использовать отступы, Чтобы упростить это, я рекомендую создавать вспомогательные методы, которые выполняют правильные действия для каждого уровня API. Сначала рассмотрим, как установить положение 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);
    }
}

Обратите внимание, что в обоих случаях есть отрицательный запас или отрицательное дополнение на противоположных сторонах. Это существенно увеличивает размер Activity за пределы его нормальных границ. Это предотвращает изменение фактического размера Activity, когда мы его где-то перемещаем.

Нам также необходимы два метода для получения текущего положения Activity. Один для позиции 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();
    }
} 

Также очень просто добавить анимацию. Единственная важная вещь здесь - это немного математики, чтобы оживить ее от прежней позиции до ее новой позиции.

// 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);

Вы можете отобразить View в месте, которое открывается, сместив Activity, добавив его к родительскому элементу 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);

И это почти все, что вам нужно для создания основного скользящего меню, которое работает на большинстве, если не на всех устройствах выше Eclair (Android 2.1 - API уровня 7).


2) Анимация Activity

Первая часть создания скользящего меню делает переход Activity в сторону. Поэтому мы должны сначала попытаться переместить Activity следующим образом:
enter image description here

Чтобы создать это, нам просто нужно добавить код выше:

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();
        }
    }
}

Вы можете использовать класс ActivitySlider следующим образом:

ActivitySlider slider = new ActivitySlider(activity);

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

3) Добавление скользящего меню

Теперь мы хотим открыть меню, когда Activity будет удаляться следующим образом: enter image description here
Как вы можете видеть, это также толкает ActionBar в сторону.

Класс ActivitySlider не нуждается в модификации, чтобы создать скользящее меню, в основном мы просто добавляем два метода: showMenu() и hideMenu(). Я буду придерживаться лучших практик и использовать Fragment в качестве скользящего меню. Первое, что нам нужно, это View - например, FrameLayout - как контейнер для нашего Fragment. Нам нужно добавить этот View к родительскому элементу View 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);

Поскольку мы устанавливаем видимость контейнера View в VISIBLE только в том случае, когда раздвижное меню фактически открыто, мы можем использовать следующий метод, чтобы проверить, открыто или закрыто меню:

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

Чтобы установить меню Fragment, мы добавим метод setter, который выполняет FragmentTransaction и добавляет меню Fragment в FrameLayout:

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

Я также склонен добавить второй сеттер, который для удобства создает Fragment из Class:

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

Есть еще одна важная вещь, которую нужно учитывать, когда дело доходит до меню Fragment. Мы работаем гораздо дальше в иерархии View, чем обычно. Таким образом, мы должны принимать во внимание такие вещи, как высота строки состояния. Если бы мы не учли это в верхней части меню Fragment, мы были бы скрыты за строкой состояния. Вы можете получить высоту строки состояния следующим образом:

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

Мы должны поместить верхнее поле в контейнер View меню Fragment следующим образом:

// 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);

Наконец, мы можем собрать все это вместе:

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();
        }
    }
}

Я использую статический вспомогательный метод в showMenu() для преобразования dip в пиксели. Вот код этого метода:

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;
}

Вы можете использовать эту новую версию класса ActivitySlider следующим образом:

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) Заключение и тестирование

Выполнение чего-то подобного на удивление легко, когда вы знаете, что вы можете просто поместить маржу или дополнение на View в Activity. Но сложность заключается в том, чтобы заставить его работать на множестве разных устройств. Реализации могут сильно измениться на нескольких уровнях API и могут иметь значительное влияние на то, как это ведет себя. Сказав, что любой код, который я написал здесь, должен работать на большинстве, если не на всех устройствах выше Eclair (Android 2.1 - API уровня 7) без проблем.
Конечно, решение, которое я разместил здесь, не является полным, он может использовать небольшую дополнительную полировку и очистку, поэтому не стесняйтесь улучшать код в соответствии с вашими потребностями.

Я тестировал все на следующих устройствах:

HTC

  • One M8 (Android 4.4.2 - KitKat): Работает
  • Сенсация (Android 4.0.3 - сэндвич с мороженым): Работа
  • Desire (Android 2.3.3 - Gingerbread): Работает
  • Один (Android 4.4.2 - KitKat): Работающий

Samsung

  • Galaxy S3 Mini (Android 4.1.2 - желе Bean): Работающий
  • Galaxy S4 Mini (Android 4.2.2 - желе Bean): Работающий
  • Galaxy S4 (Android 4.4.2 - KitKat): Работает
  • Galaxy S5 (Android 4.4.2 - KitKat): Работающий
  • Galaxy S Plus (Android 2.3.3 - Gingerbread): Работа
  • Galaxy Ace (Android 2.3.6 - Gingerbread): Работающий
  • Galaxy S2 (Android 4.1.2 - желе Bean): Работа
  • Galaxy S3 (Android 4.3 - желе Bean): Работает
  • Galaxy Note 2 (Android 4.3 - желе Bean): Работа
  • Galaxy Nexus (Android 4.2.1 - желе Bean): Работа

Motorola

  • Moto G (Android 4.4.2 - KitKat): Работа

LG

  • Nexus 5 (Android 4.4.2 - KitKat): Работает

ZTE

  • Blade (Android 2.1 - Eclair): Работает

Я надеюсь, что смогу помочь вам, и если у вас возникнут какие-либо дополнительные вопросы или что-то еще непонятное, не стесняйтесь спрашивать!