我想做 的是打开抽屉时将ActionBar随同一起滑动NavigationDrawer。我目前没有使用任何第三方库,如果可能的话,我想保持这种状态。我需要的是方法的实现,例如:getActionBarView.slide(dp);
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)仍然很新时编写的。从Android5.0开始,尤其是由于引入了ToolBarthis,此答案不再被认为是 最新的!但是从技术角度来看,对于那些想了解Android内部运作原理的人来说,这个答案可能仍然具有很大的价值!
ToolBarthis
在NavigationDrawer专门设计为位于其下方的ActionBar,有没有办法实现NavigationDrawer,使 在ActionBar移动与它-除非也许在寻找View使了ActionBar和动画它旁边的NavigationDrawer,但我绝不会推荐这样的东西,因为它会困难且容易出错。我认为您只有两种选择:
使用类似SlidingMenu的库实施自定义滑动菜单既然您说过不想使用实现自定义滑动菜单的库是唯一的选择,幸运的是,一旦知道如何做,这实际上就不那么困难了。
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:
Honeycomb
Activities
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(Android2.1-API级别7)以上的大多数(如果不是所有)设备上运行。
2)动画 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(); } } }
You can use the ActivitySlider class like this:
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移开时显示一个菜单,如下所示:在此处输入图片说明如您所见,菜单也将菜单推ActionBar到了侧面。
ActivitySlider无需为创建滑动菜单而对类进行大量修改,基本上我们只需添加两个方法,showMenu()和hideMenu()。我将遵循最佳做法,并使用a Fragment作为滑动菜单。我们需要的第一件事是View-例如 FrameLayout-作为我们的容器Fragment。我们需要把它添加View到的父View的Activity:
a Fragment
View
// 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);
Since we set the visibility of the container View to VISIBLE only when the sliding menu is actually open we can use the following method to check if the menu is open or closed:
public boolean isMenuVisible() { return this.menuContainer.getVisibility() == View.VISIBLE; }
To set the menu Fragment we add a setter method that performs a FragmentTransaction and adds the menu Fragment to the FrameLayout:
Fragment
FragmentTransaction
FrameLayout
public void setMenuFragment(Fragment fragment) { FragmentManager manager = this.activity.getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); transaction.replace(R.id.flMenuContainer, fragment); transaction.commit(); }
I also tend to add a second setter which instantiates the Fragment from a Class for convenience:
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;
We have to put a top margin on the container View of the menu Fragment like this:
// 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);
Finally we can put all this together:
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(); } } }
I use a static helper method in showMenu() to convert dip to pixels. Here is the code of this method:
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; }
You can use this new version of the ActivitySlider class like this:
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): Working Sensation (Android 4.0.3 - Ice Cream Sandwich): Working Desire (Android 2.3.3 - Gingerbread): Working One (Android 4.4.2 - KitKat): Working Samsung Galaxy S3 Mini (Android 4.1.2 - Jelly Bean): Working Galaxy S4 Mini (Android 4.2.2 - Jelly Bean): Working Galaxy S4 (Android 4.4.2 - KitKat): Working Galaxy S5 (Android 4.4.2 - KitKat): Working Galaxy S Plus (Android 2.3.3 - Gingerbread): Working Galaxy Ace (Android 2.3.6 - Gingerbread): Working Galaxy S2 (Android 4.1.2 - Jelly Bean): Working Galaxy S3 (Android 4.3 - Jelly Bean): Working Galaxy Note 2 (Android 4.3 - Jelly Bean): Working Galaxy Nexus (Android 4.2.1 - Jelly Bean): Working Motorola Moto G (Android 4.4.2 - KitKat): Working LG Nexus 5 (Android 4.4.2 - KitKat): Working ZTE Blade (Android 2.1 - Eclair): Working
HTC
Samsung
Motorola
LG
ZTE
I hope I could help you and if you have any further questions or anything else is unclear please feel free to ask!