如何将操作栏与导航绘图一起滑动

我想做的是滑动抽屉何时打开。我目前没有使用任何第三方库,如果可能的话,我想保持这种方式。我需要的只是一个方法的实现,如:ActionBarNavigationDrawergetActionBarView.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


答案 1

请注意:这个答案最初是在Android 4.4(KitKat)还很新的时候写的。自Android 5.0以来,特别是由于引入这个答案不能再被认为是最新的了!但是从技术角度来看,对于那些想要了解Android内部运作的人来说,这个答案可能仍然有很大的价值!ToolBar

它被专门设计为位于 下面,没有办法实现 与它一起移动 - 除非可能寻找弥补它并随着 动画制作它,但我永远不会推荐这样的东西,因为它会很困难并且容易出错。在我看来,你只有两个选择:NavigationDrawerActionBarNavigationDrawerActionBarViewActionBarNavigationDrawer

  1. 使用像滑动菜单这样的库
  2. 实现自定义滑动菜单

既然你说你不想使用一个实现自定义滑动菜单的库是你唯一的选择,幸运的是,一旦你知道如何做,这真的不是那么难。


1) 基本说明

您可以通过在构成.这是 的父级,id 为 :ActivityActionBarViewActivityViewViewandroid.R.id.content

View content = (View) activity.findViewById(android.R.id.content).getParent();

在Honeycomb(Android版本3.0 - API级别11)或更高版本上 - 换句话说,在引入之后 - 您需要使用边距来更改位置,而在以前的版本上,您需要使用填充。为了简化这一点,我建议创建帮助器方法,为每个 API 级别执行正确的操作。我们先来看看如何设置的位置:ActionBarActivitiesActivity

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);
    }
}

请注意,在这两种情况下,相对的边距或负填充都存在。这实质上是要增加超出其正常范围的大小。这可以防止当我们将其滑动到某个地方时更改的实际大小。ActivityActivity

我们还需要两种方法来获取 .一个用于 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);

您可以在通过将其添加到的父级中而移开所显示的位置显示 :ViewActivityView

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

创建滑动菜单的第一部分是使移动远离。因此,我们应该首先尝试像这样移动:ActivityActivity
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) 添加滑动菜单

现在,我们想要显示一个菜单,当移动时,就像这样:enter image description here
如您所见,它也将推到一边。ActivityActionBar

该类不需要修改太多即可创建滑动菜单,基本上我们只需添加两个方法,和.我将坚持最佳实践,并使用a作为滑动菜单。我们需要的第一件事是 - 例如a - 作为我们的容器。我们需要将它添加到的父级:ActivitySlidershowMenu()hideMenu()FragmentViewFrameLayoutFragmentViewViewActivity

// 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 并将菜单添加到 :FragmentFragmentTransactionFragmentFrameLayout

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

为了方便起见,我还倾向于添加第二个 setter,它将从 a 实例化:FragmentClass

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

在菜单方面,还有一件重要的事情需要考虑 。我们在层次结构中的操作比平时要高得多。因此,我们必须考虑状态栏的高度等因素。如果我们没有考虑到这一点,菜单的顶部将隐藏在状态栏后面。您可以像这样获取状态栏的高度:FragmentViewFragment

Rect rectangle = new Rect();
Window window = this.activity.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
final int statusBarHeight = rectangle.top;

我们必须在菜单的容器上放置一个上边距,如下所示:ViewFragment

// 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)以上的大多数(如果不是全部)设备,而不会出现任何问题。
当然,我在这里发布的解决方案并不完整,它可能需要一些额外的抛光和清理,所以请随时改进代码以满足您的需求!ViewActivity

我已经在以下设备上测试了所有内容:

宏达电

  • 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):加工

我希望我能帮助你,如果你有任何其他问题或其他不清楚的事情,请随时询问!


答案 2

推荐