草庐IT

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

coder 2023-11-30 原文

Android Studio 2.1.3

我有我想要遵循的设计。

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

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

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

这可能吗?

非常感谢任何建议

最佳答案

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

此解决方案使用自定义 ViewSwitcher 子类充当 DrawerLayout 的左抽屉,并包含两个子 View,一个是主抽屉 View,另一个是在它上方打开的第二个抽屉。

DoubleDrawerView 类是一个相对简单的 ViewSwitcher,它加载自己的 Animation,并适本地调整它们以产生一秒钟的效果抽屉开合过头。它跟踪自己的状态,以便在设备旋转等后可以正确恢复。

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>

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

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

关于android - 打开第二个抽屉布局作为第一个抽屉布局的子抽屉布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39009874/

有关android - 打开第二个抽屉布局作为第一个抽屉布局的子抽屉布局的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  9. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  10. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

随机推荐