web-dev-qa-db-ja.com

ナビゲーションドロワーと一緒にアクションバーをスライドさせる方法

私がやりたいのは、引き出しが開いたときにActionBarNavigationDrawerと一緒にスライドさせることです。現在、サードパーティのライブラリを使用していません。可能な限り、そのままにしておきたいと思います。必要なのは、次のようなメソッドの実装だけです。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

15
Detoxic-Soul

ご注意ください:この回答は、もともとAndroid 4.4(KitKat)がまだかなり新しいときに書かれました。 Android 5.0以降、特にToolBarの導入により、この回答は最新のものとは見なされなくなりました。しかし、技術的な観点から、そしてAndroidの内部の仕組みについて学びたい人にとって、この答えはまだ多くの価値を持っているかもしれません!

NavigationDrawerActionBarの下に配置するように特別に設計されており、NavigationDrawerを実装してActionBarを一緒に移動させる方法はありません。 [SOME_VARIABLE] _は、Viewを構成し、ActionBarと一緒にアニメーション化しますが、このようなものは難しく、エラーが発生しやすいため、お勧めしません。私の意見では、2つの選択肢しかありません。

  1. SlidingMenuのようなライブラリ を使用する
  2. カスタムスライドメニューの実装

カスタムスライディングメニューを実装するライブラリを使用したくないと言ったので、幸いなことに、それを行う方法を知っていれば、これはそれほど難しいことではありません。


1)基本的な説明

NavigationDrawerを構成するActivityにマージンまたはパディングを配置することで、ActionBarのコンテンツ全体を移動できます。つまりViewを含むすべてのものです。このActivityは、IDが_Android.R.id.content_のViewの親です。

_View content = (View) activity.findViewById(Android.R.id.content).getParent();
_

Honeycomb(Androidバージョン3.0-APIレベル11)以降(つまり、Viewが導入された後)では、マージンを使用してActionBarの位置を変更する必要があり、以前のバージョンでは、パディング。これを単純化するために、APIレベルごとに正しいアクションを実行するヘルパーメソッドを作成することをお勧めします。まず、Activitiesの位置を設定する方法を見てみましょう。

_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の現在の位置を取得するために2つのメソッドが必要です。 1つはx位置用、もう1つは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);
_

ActivityViewの親に追加して、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()の2つのメソッドを追加するだけです。ベストプラクティスに固執し、スライドメニューとしてFragmentを使用します。最初に必要なのは、ViewのコンテナとしてのFrameLayout-たとえばFragment-です。このViewViewActivityの親に追加する必要があります。

_// 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を設定するには、FragmentTransactionを実行するセッターメソッドを追加し、メニューFragmentFrameLayoutに追加します。

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

また、便宜上、FragmentからClassをインスタンス化する2番目のセッターを追加する傾向があります。

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

メニューFragmentに関して、考慮すべきもう1つの重要なことがあります。 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()で静的ヘルパーメソッドを使用して、ディップをピクセルに変換します。このメソッドのコードは次のとおりです。

_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)結論とテスト

ViewActivityにマージンまたはパディングを配置するだけでよいことがわかっている場合、このようなことを行うのは驚くほど簡単です。しかし、難しいのは、さまざまなデバイスで動作させることです。実装は複数のAPIレベル間で大きく変化する可能性があり、これが動作に大きな影響を与える可能性があります。ここに投稿したコードは、Eclair(Android 2.1-APIレベル7)より上のすべてのデバイスではなくても、ほとんど問題なく動作するはずです。
もちろん、ここに投稿したソリューションは完全ではありません。少し余分な研磨とクリーンアップが必要になる可能性があるため、ニーズに合わせてコードを自由に改善してください。

私は次のデバイスですべてをテストしました:

HTC

  • One M8(Android 4.4.2 --KitKat):動作中
  • センセーション(Android 4.0.3-アイスクリームサンドイッチ):動作中
  • 欲望(Android 2.3.3-ジンジャーブレッド):動作中
  • One(Android 4.4.2 --KitKat):動作中

サムスン

  • Galaxy S3 Mini(Android 4.1.2-ゼリービーン):動作中
  • Galaxy S4 Mini(Android 4.2.2-ゼリービーン):動作中
  • Galaxy S4(Android 4.4.2 --KitKat):動作中
  • Galaxy S5(Android 4.4.2 --KitKat):動作中
  • Galaxy S Plus(Android 2.3.3-ジンジャーブレッド):動作中
  • Galaxy Ace(Android 2.3.6-ジンジャーブレッド):動作中
  • Galaxy S2(Android 4.1.2-ゼリービーン):動作中
  • Galaxy S3(Android 4.3-ゼリービーン):動作中
  • Galaxy Note 2(Android 4.3-Jelly Bean):動作中
  • Galaxy Nexus(Android 4.2.1-ジェリービーンズ):動作中

モトローラ

  • Moto G(Android 4.4.2 --KitKat):動作中

LG

  • Nexus 5(Android 4.4.2 --KitKat):動作中

ZTE

  • ブレード(Android 2.1-エクレア):動作中

お役に立てれば幸いです。ご不明な点やご不明な点がございましたら、お気軽にお問い合わせください。

59
Xaver Kapeller