私がやりたいのは、引き出しが開いたときに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);
しかし、それは私が望むことをしません、それはこれを生み出します:
私が達成したいのはこれです:
ご注意ください:この回答は、もともとAndroid 4.4(KitKat)がまだかなり新しいときに書かれました。 Android 5.0以降、特に
ToolBar
の導入により、この回答は最新のものとは見なされなくなりました。しかし、技術的な観点から、そしてAndroidの内部の仕組みについて学びたい人にとって、この答えはまだ多くの価値を持っているかもしれません!
NavigationDrawer
はActionBar
の下に配置するように特別に設計されており、NavigationDrawer
を実装してActionBar
を一緒に移動させる方法はありません。 [SOME_VARIABLE] _は、View
を構成し、ActionBar
と一緒にアニメーション化しますが、このようなものは難しく、エラーが発生しやすいため、お勧めしません。私の意見では、2つの選択肢しかありません。
カスタムスライディングメニューを実装するライブラリを使用したくないと言ったので、幸いなことに、それを行う方法を知っていれば、これはそれほど難しいことではありません。
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);
_
Activity
を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)より上のすべてのデバイスではないにしても、ほとんどのデバイスで機能する基本的なスライドメニューを作成するために必要なのはこれだけです。
Activity
のアニメーション化スライドメニューを作成する最初の部分は、Activity
を邪魔にならないようにすることです。そのため、最初にActivity
を次のように移動してみてください。
これを作成するには、上記のコードをまとめる必要があります。
_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);
_
ここで、Activity
が次のように邪魔にならないように移動したときに、メニューを表示します。
ご覧のとおり、ActionBar
も横に押し出されます。
スライドメニューを作成するためにActivitySlider
クラスをそれほど変更する必要はありません。基本的には、showMenu()
とhideMenu()
の2つのメソッドを追加するだけです。ベストプラクティスに固執し、スライドメニューとして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
を設定するには、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
をインスタンス化する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();
_
View
のActivity
にマージンまたはパディングを配置するだけでよいことがわかっている場合、このようなことを行うのは驚くほど簡単です。しかし、難しいのは、さまざまなデバイスで動作させることです。実装は複数の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-エクレア):動作中
お役に立てれば幸いです。ご不明な点やご不明な点がございましたら、お気軽にお問い合わせください。