请注意:这个答案最初是在Android 4.4(KitKat)还很新的时候写的。自Android 5.0以来,特别是由于引入这个答案不能再被认为是最新的了!但是从技术角度来看,对于那些想要了解Android内部运作的人来说,这个答案可能仍然有很大的价值!ToolBar
它被专门设计为位于 下面,没有办法实现 与它一起移动 - 除非可能寻找弥补它并随着 动画制作它,但我永远不会推荐这样的东西,因为它会很困难并且容易出错。在我看来,你只有两个选择:NavigationDrawer
ActionBar
NavigationDrawer
ActionBar
View
ActionBar
NavigationDrawer
- 使用像滑动菜单这样的库
- 实现自定义滑动菜单
既然你说你不想使用一个实现自定义滑动菜单的库是你唯一的选择,幸运的是,一旦你知道如何做,这真的不是那么难。
1) 基本说明
您可以通过在构成.这是 的父级,id 为 :Activity
ActionBar
View
Activity
View
View
android.R.id.content
View content = (View) activity.findViewById(android.R.id.content).getParent();
在Honeycomb(Android版本3.0 - API级别11)或更高版本上 - 换句话说,在引入之后 - 您需要使用边距来更改位置,而在以前的版本上,您需要使用填充。为了简化这一点,我建议创建帮助器方法,为每个 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
Activity
我们还需要两种方法来获取 .一个用于 x 位置,一个用于 y 位置: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
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
要创建这个,我们只需要将上面的代码放在一起:
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
ActionBar
该类不需要修改太多即可创建滑动菜单,基本上我们只需添加两个方法,和.我将坚持最佳实践,并使用a作为滑动菜单。我们需要的第一件事是 - 例如a - 作为我们的容器。我们需要将它添加到的父级: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);
由于我们仅在滑动菜单实际打开时才将容器的可见性设置为 VISIBLE,因此我们可以使用以下方法来检查菜单是打开还是关闭:View
public boolean isMenuVisible() {
return this.menuContainer.getVisibility() == View.VISIBLE;
}
为了设置菜单,我们添加了一个 setter 方法,该方法执行 a 并将菜单添加到 :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();
}
为了方便起见,我还倾向于添加第二个 setter,它将从 a 实例化: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()
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)结论和测试
当您知道可以简单地在 .但困难在于使其在许多不同的设备上工作。实现可能会在多个 API 级别之间发生很大变化,这可能会对其行为方式产生重大影响。话虽如此,我在这里发布的任何代码都应该适用于Eclair(Android 2.1 - API级别7)以上的大多数(如果不是全部)设备,而不会出现任何问题。
当然,我在这里发布的解决方案并不完整,它可能需要一些额外的抛光和清理,所以请随时改进代码以满足您的需求!View
Activity
我已经在以下设备上测试了所有内容:
宏达电
-
One M8 (Android 4.4.2 - KitKat):加工
-
感觉 (安卓 4.0.3 - 冰淇淋三明治):加工
-
欲望 (安卓 2.3.3 - 姜饼):加工
-
一个 (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):加工
断续器
-
Nexus 5 (Android 4.4.2 - KitKat):加工
中兴通讯
-
Blade (Android 2.1 - Eclair):加工
我希望我能帮助你,如果你有任何其他问题或其他不清楚的事情,请随时询问!