如何将操作栏与导航抽屉一起滑动

时间:2014-05-21 12:41:12

标签: java android android-actionbar android-animation navigation-drawer

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

但它没有做我想要的,它产生了这个:

I am currently stuck with this

我想要实现的目标是:

current screen shot from app

1 个答案:

答案 0 :(得分:59)

  

请注意:这个答案最初是在Android 4.4(KitKat)还很新的时候编写的。自Android 5.0以来尤其如此   因为引入ToolBar这个答案不可能   被认为是最新的!但从技术角度来看   那些想要了解Android内部工作原理的人   这个答案可能仍然具有很大的价值!

NavigationDrawer专门设计为位于ActionBar下方,并且没有办法实施NavigationDrawer以使ActionBar移动它 - 除非查看对于组成View的{​​{1}}并将其与ActionBar一起设置动画,但我绝不会推荐类似这样的内容,因为它很难且容易出错。在我看来,你只有两个选择:

  1. 使用library like the SlidingMenu
  2. 实施自定义滑动菜单
  3. 既然你说你不想使用实现自定义滑动菜单的库是你唯一的选择,幸运的是,一旦你知道怎么做,这真的不是那么难。


    1)基本解释

    您可以移动NavigationDrawer的全部内容 - 我的意思是包括Activity在内的所有内容 - 在构成{{1}的ActionBar上添加边距或填充}。此ViewActivity的父级,其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)。


    2)动画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
    enter image description here

    要创建它,我们只需将上面的代码放在一起:

    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

    3)添加滑动菜单

    现在我们想要在ActivitySlider slider = new ActivitySlider(activity); // This would move the Activity 400 pixel to the right and 100 pixel down slider.slideTo(400, 100); 移开方式时显示一个菜单: enter image description here
    正如您所看到的那样,它也会将Activity推向一边。

    创建滑动菜单时,ActionBar类不需要修改太多,基本上我们只需要添加两个方法ActivitySlidershowMenu()。我将坚持最佳实践并使用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;
    }
    

    4)结论&amp;测试

    当您知道可以简单地在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):工作
    •   

    我希望我可以帮到你,如果你有任何其他问题或其他任何不清楚的地方,请随时提问!