当前位置: 首页 > 面试题库 >

如何将ActionBar和NavigationDrawer一起滑动

隆芷阳
2023-03-14
问题内容

我想做 的是打开抽屉时将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内部运作原理的人来说,这个答案可能仍然具有很大的价值!

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

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 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到的父ViewActivity

// 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:

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:

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:

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

4) Conclusion & Testing

做这样的事情是非常容易的,当你知道你可以简单地把保证金或填充上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

I hope I could help you and if you have any further questions or anything else
is unclear please feel free to ask!



 类似资料:
  • 问题内容: 我正在使用ActionBarSherlock,并尝试使用以下方法隐藏/显示全屏图像的ActionBar: 和 但这确实是跳跃和尴尬的。有没有办法使它更平滑,或者应用平移/不透明动画? 在进一步检查ABS源并观察其行为后,似乎至少定义了最少的动画。对于标准ActionBar,不能说相同的话。 为了完整起见,这是我从ImageView.click调用的相关代码: 值得注意的是,标志的设置/

  • 我实现了一个BaseActivity,它扩展了ActionBarActivity,并实现了一个NavigationDrawer。我的所有活动都继承自此基本活动。我现在想知道我是否可以实现一个导航抽屉(NavigationDrawer),并且仍然使用工具栏进行正确的向上导航,或者当我实现导航抽屉(NavigationDrawer)时,设备后退按钮应该作为向上导航按钮吗? 编辑: 使用工具栏自定义导航

  • 我知道这个问题以前被问过,但没有得到准确的解决方案。我想从我的活动中隐藏ActionBar和NavigationBar,或者为了使其全屏。我尝试了下面的代码在我的活动创建方法,但它显示动作栏几秒钟,而不是让它全屏。在银河S3(Android4.3),它甚至超过一秒钟。所以我怎么能让它完全不可见,我的活动完全从一开始就像全屏一样。我看到许多应用程序只在S3上运行,但它们没有动作栏,甚至没有几秒钟。

  • 我使用fetch获取数据,我想用chart.js显示它,而不是显示数据。我错在哪里?如果你帮忙我会很高兴的 我的代码在下面

  • 问题内容: 我想按数量加载最畅销的产品。这些是我的表: 这是我的Spring Data Repository: 我正在获取 HsqlException:找不到列:id 我认为此错误与id列无关,因为这两个表都存在。“按汇总查询”可用于Spring数据吗?因为对于Spring Data应该仅从数据库中选择产品属性,这对我来说似乎并不奇怪,并且使用此sql我们还选择了sum(po.quantity)。

  • 我在为我的React本机应用程序设置testID和accessibilityLabel时遇到了一个奇怪的问题,其目的是让Appium Inspector(Appium Desktop)可以看到iOS和Android应用程序上的“accessibility id”,问题如下: 如果testID是设置在一起的辅助标签,iOS的应用程序不显示"辅助ID"的组件,但android的应用程序。 如果我只设置