打开第二个抽屉布局作为第一个抽屉布局的子抽屉布局

Open second drawerlayout as a sub drawerlayout over the first

Android Studio 2.1.3

我有这个设计,我正在尝试遵循。

在第一个抽屉布局上,我有一个设置选项。

当用户点击时,它会打开第二个抽屉布局,如下所示。

用户可以通过单击箭头返回到第一个 Main Menu

这可能吗?

非常感谢任何建议

不清楚您希望如何实现抽屉 UI,因此以下解决方案相当通用,因为它应该适用于 NavigationViews、RecyclerViews 或几乎任何你想要的 View 类型。

此解决方案使用自定义 ViewSwitcher subclass 作为 DrawerLayout 的左抽屉,并容纳两个子 View,其中一个是主抽屉抽屉 View,另一个是在它上方打开的第二个抽屉。

DoubleDrawerView class 是一个相对简单的 ViewSwitcher 加载它自己的 Animations,并适当地调整它们以产生第二个抽屉打开的效果和关闭第一个。它跟踪自己的状态,以便在设备旋转等后可以正确恢复。

public class DoubleDrawerView extends ViewSwitcher {
    private static final int NONE = -1;
    private static final int MAIN_VIEW_INDEX = 0;
    private static final int DRAWER_VIEW_INDEX = 1;

    private Animation slideInAnimation, slideOutAnimation, noAnimation;
    private boolean animating = false;

    private Animation.AnimationListener listener = new Animation.AnimationListener() {
        @Override
        public void onAnimationEnd(Animation anim) {
            animating = false;
        }

        @Override
        public void onAnimationStart(Animation anim) {}

        @Override
        public void onAnimationRepeat(Animation anim) {}
    };

    public DoubleDrawerView(Context context) {
        this(context, null);
    }

    public DoubleDrawerView(Context context, AttributeSet attrs) {
        super(context, attrs);

        slideInAnimation = AnimationUtils.loadAnimation(context, R.anim.slide_in_left);
        slideOutAnimation = AnimationUtils.loadAnimation(context, R.anim.slide_out_left);
        noAnimation = AnimationUtils.loadAnimation(context, R.anim.none);
        noAnimation.setAnimationListener(listener);
    }

    public void openInnerDrawer() {
        if (getDisplayedChild() != DRAWER_VIEW_INDEX) {
            setChildAndAnimate(DRAWER_VIEW_INDEX, true);
        }
    }

    public void closeInnerDrawer() {
        if (getDisplayedChild() != MAIN_VIEW_INDEX) {
            setChildAndAnimate(MAIN_VIEW_INDEX, true);
        }
    }

    public boolean isInnerDrawerOpen() {
        return getDisplayedChild() == DRAWER_VIEW_INDEX;
    }

    private void setChildAndAnimate(int whichChild, boolean doAnimate) {
        if (doAnimate) {
            setAnimationForChild(whichChild);
        }
        else {
            setAnimationForChild(NONE);
        }
        animating = doAnimate;
        setDisplayedChild(whichChild);
    }

    private void setAnimationForChild(int whichChild) {
        if (whichChild == DRAWER_VIEW_INDEX) {
            setInAnimation(slideInAnimation);
            setOutAnimation(noAnimation);
        }
        else if (whichChild == MAIN_VIEW_INDEX) {
            setInAnimation(noAnimation);
            setOutAnimation(slideOutAnimation);
        }
        else {
            setInAnimation(null);
            setOutAnimation(null);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (animating) {
            return true;
        }
        else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.whichChild = getDisplayedChild();
        return ss;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        setChildAndAnimate(ss.whichChild, false);
    }

    private static class SavedState extends BaseSavedState {
        int whichChild;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            whichChild = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(whichChild);
        }

        public static final Parcelable.Creator<SavedState>
            CREATOR = new Parcelable.Creator<SavedState>() {

            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}

DoubleDrawerView 为其 Animation 使用以下 XML 文件。这些应该在您项目的 res/anim/ 文件夹中。

slide_in_left.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="-100%p" android:toXDelta="0"
    android:duration="@android:integer/config_mediumAnimTime"/>

slide_out_left.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0" android:toXDelta="-100%p"
    android:duration="@android:integer/config_mediumAnimTime"/>

none.xml

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="1.0" android:toAlpha="1.0"
    android:duration="@android:integer/config_mediumAnimTime" />

此示例的布局是标准 DrawerLayout,其抽屉有一个 DoubleDrawerView,其中有两个简单的 NavigationView。请注意,主抽屉 View 必须在 DoubleDrawerView 中排在第一位,第二个内部抽屉 View

之后。

activity_main.xml

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.example.doubledrawer.DoubleDrawerView
        android:id="@+id/double_drawer_view"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="left">

        <android.support.design.widget.NavigationView
            android:id="@+id/main_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:menu="@menu/navigation_main" />

        <android.support.design.widget.NavigationView
            android:id="@+id/settings_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:menu="@menu/navigation_settings" />

    </com.example.doubledrawer.DoubleDrawerView>

</android.support.v4.widget.DrawerLayout>

为了完整的剪切和粘贴示例,一些简单的 res/menu/ 文件用于上面的 NavigationViews。

navigation_main.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group
        android:id="@+id/group_screens"
        android:checkableBehavior="single">
        <item
            android:id="@+id/menu_screen_1"
            android:title="Screen 1" />
        <item
            android:id="@+id/menu_screen_2"
            android:title="Screen 2"/>
    </group>

    <item
        android:id="@+id/menu_open_settings"
        android:title="Open Settings" />

</menu>

navigation_settings.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/menu_close_settings"
        android:title="Back to Main" />

    <group
        android:id="@+id/group_settings">
        <item
            android:id="@+id/menu_setting_1"
            android:title="Setting 1" />
        <item
            android:id="@+id/menu_setting_2"
            android:title="Setting 2" />
    </group>

</menu>

在示例 Activity 中,我们只是获取对 DoubleDrawerViewNavigationView 的引用,并实现一个 OnNavigationItemSelectedListener 来相应地打开和关闭内部抽屉。

public class MainActivity extends AppCompatActivity
    implements NavigationView.OnNavigationItemSelectedListener {

    private DrawerLayout drawerLayout;
    private DoubleDrawerView doubleDrawerView;
    private NavigationView mainNavigationView, settingsNavigationView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        doubleDrawerView = (DoubleDrawerView) findViewById(R.id.double_drawer_view);
        mainNavigationView = (NavigationView) findViewById(R.id.main_navigation_view);
        settingsNavigationView = (NavigationView) findViewById(R.id.settings_navigation_view);

        mainNavigationView.setNavigationItemSelectedListener(this);
        settingsNavigationView.setNavigationItemSelectedListener(this);

        drawerLayout.openDrawer(Gravity.LEFT);
    }

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_open_settings:
                doubleDrawerView.openInnerDrawer();
                break;

            case R.id.menu_close_settings:
                doubleDrawerView.closeInnerDrawer();
                break;

                // Additional cases as needed
                // This example simply Toasts the title for the extra sample items

            default:
                Toast.makeText(this, item.getTitle(), Toast.LENGTH_SHORT).show();
        }
        return true;
    }
}

此解决方案使用两个 DrawerLayout,一个嵌套在另一个中,以代替自定义 View。这可能更容易实现,但它需要在 Activity 本身中使用更专门的代码,因此将更紧密地耦合到它所使用的任何 类。

Activity的布局,activity_main.xml

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/inner_drawer_layout"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="left">

        <android.support.design.widget.NavigationView
            android:id="@+id/main_navigation_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:menu="@menu/navigation_main" />

        <android.support.design.widget.NavigationView
            android:id="@+id/settings_navigation_view"
            android:layout_width="240dp"
            android:layout_height="match_parent"
            android:layout_gravity="left"
            app:menu="@menu/navigation_settings" />

    </android.support.v4.widget.DrawerLayout>

</android.support.v4.widget.DrawerLayout>

上面的示例 NavigationViews 使用与 中所示相同的菜单文件。

Activity 中,我们获取对两个 DrawerLayout 的引用,并在启动时在内部设置稀松布颜色和锁定模式。我们还需要自己处理后退按钮按下,因为添加第二个 DrawerLayout 会扰乱第一个对它的处理。在打开和关闭内层抽屉时,我们需要适当设置锁定模式,以防止内层抽屉被拖动。

public class MainActivity extends AppCompatActivity
    implements NavigationView.OnNavigationItemSelectedListener {

    private DrawerLayout drawerLayout, innerDrawerLayout;
    private NavigationView mainNavigationView, settingsNavigationView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        innerDrawerLayout = (DrawerLayout) findViewById(R.id.inner_drawer_layout);
        mainNavigationView = (NavigationView) findViewById(R.id.main_navigation_view);
        settingsNavigationView = (NavigationView) findViewById(R.id.settings_navigation_view);

        mainNavigationView.setNavigationItemSelectedListener(this);
        settingsNavigationView.setNavigationItemSelectedListener(this);

        innerDrawerLayout.setScrimColor(Color.TRANSPARENT);
        innerDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);

        drawerLayout.openDrawer(Gravity.LEFT);
    }

    @Override
    public void onBackPressed() {
        if (drawerLayout.isDrawerOpen(Gravity.LEFT)) {
            drawerLayout.closeDrawer(Gravity.LEFT);
        }
        else {
            super.onBackPressed();
        }
    }

    private void openInnerDrawer() {
        innerDrawerLayout.openDrawer(Gravity.LEFT);
        innerDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
    }

    private void closeInnerDrawer() {
        innerDrawerLayout.closeDrawer(Gravity.LEFT);
        innerDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
    }

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_open_settings:
                openInnerDrawer();
                break;

            case R.id.menu_close_settings:
                closeInnerDrawer();
                break;

            // Additional cases as needed
            // This example simply Toasts the title for the extra sample items

            default:
                Toast.makeText(this, item.getTitle(), Toast.LENGTH_SHORT).show();
        }
        return true;
    }
}