我想要做的是在打开抽屉时将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);
但它没有做我想要的,它产生了这个:
我想要实现的目标是:
答案 0 :(得分:59)
请注意:这个答案最初是在Android 4.4(KitKat)还很新的时候编写的。自Android 5.0以来尤其如此 因为引入
ToolBar
这个答案不可能 被认为是最新的!但从技术角度来看 那些想要了解Android内部工作原理的人 这个答案可能仍然具有很大的价值!
NavigationDrawer
专门设计为位于ActionBar
下方,并且没有办法实施NavigationDrawer
以使ActionBar
移动它 - 除非查看对于组成View
的{{1}}并将其与ActionBar
一起设置动画,但我绝不会推荐类似这样的内容,因为它很难且容易出错。在我看来,你只有两个选择:
既然你说你不想使用实现自定义滑动菜单的库是你唯一的选择,幸运的是,一旦你知道怎么做,这真的不是那么难。
您可以移动NavigationDrawer
的全部内容 - 我的意思是包括Activity
在内的所有内容 - 在构成{{1}的ActionBar
上添加边距或填充}。此View
是Activity
的父级,其ID为View
:
View
在Honeycomb(Android版本3.0 - API级别11)或更高版本上 - 换言之,在引入android.R.id.content
之后 - 您需要使用边距来更改View content = (View) activity.findViewById(android.R.id.content).getParent();
位置以及您需要的先前版本使用填充。为了简化这一点,我建议创建帮助方法,为每个API级别执行正确的操作。我们先来看看如何设置ActionBar
的位置:
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
的当前位置。一个用于x位置,一个用于y位置:
Activity
添加动画也很简单。这里唯一重要的是将其从之前的位置动画到新位置的一些数学运算
Activity
您可以通过将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
这就是创建基本滑动菜单所需的全部内容,该菜单适用于Eclair以上的大多数设备(Android 2.1 - API级别7)。
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);
创建滑动菜单的第一部分是让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()
作为滑动菜单。我们首先需要的是Fragment
- 例如View
- 作为FrameLayout
的容器。我们需要将此Fragment
添加到View
的{{1}}的父级:
View
由于我们只在滑动菜单实际打开时才将容器Activity
的可见性设置为VISIBLE,我们可以使用以下方法检查菜单是打开还是关闭:
// 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
,我们会添加一个执行public boolean isMenuVisible() {
return this.menuContainer.getVisibility() == View.VISIBLE;
}
的setter方法,并将菜单Fragment
添加到FragmentTransaction
:
Fragment
为了方便起见,我还倾向于添加第二个setter,它从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);
中使用静态辅助方法将dip转换为像素。以下是此方法的代码:
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
的{{1}}上添加边距或填充时,执行此类操作非常容易。但困难在于它可以在很多不同的设备上运行。实现可以在多个API级别之间进行大量更改,这可能会对此行为产生相当大的影响。说过我在这里发布的任何代码都应该适用于Eclair(Android 2.1 - API级别7)以上的大多数设备而没有任何问题。
当然我在这里发布的解决方案并不完整,它可以使用一些额外的抛光和清理,所以随时改进代码以满足您的需求!
我已在以下设备上测试过所有内容:
HTC
- One M8(Android 4.4.2 - KitKat):工作
- 感觉(Android 4.0.3 - 冰淇淋三明治):工作
- 欲望(Android 2.3.3 - Gingerbread):工作
- One(Android 4.4.2 - KitKat):工作
三星
- Galaxy S3 Mini(Android 4.1.2 - Jelly Bean):工作
- Galaxy S4 Mini(Android 4.2.2 - Jelly 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 - Jelly Bean):工作
- Galaxy S3(Android 4.3 - Jelly Bean):工作
- Galaxy Note 2(Android 4.3 - Jelly Bean):工作
- Galaxy Nexus(Android 4.2.1 - Jelly Bean):工作
摩托罗拉
- Moto G(Android 4.4.2 - KitKat):工作
LG
- Nexus 5(Android 4.4.2 - KitKat):工作
ZTE
- Blade(Android 2.1 - Eclair):工作
我希望我可以帮到你,如果你有任何其他问题或其他任何不清楚的地方,请随时提问!