技术博客
惊喜好礼享不停
技术博客
探索 Material Dialogs:打造极致用户体验的 Android 对话框

探索 Material Dialogs:打造极致用户体验的 Android 对话框

作者: 万维易源
2024-09-29
Material DialogsMaterial Design对话框库代码示例Android API

摘要

本文将介绍Material Dialogs,这是一个遵循Material Design设计风格的高度可定制且易于使用的对话框库。它不仅兼容Android API 8及以上版本,而且提供了丰富的功能来替代Android默认的对话框。通过详细的代码示例,本文旨在帮助开发者更好地理解和应用Material Dialogs。

关键词

Material Dialogs, Material Design, 对话框库, 代码示例, Android API

一、Material Dialogs 简介

1.1 Material Design 设计理念在 Android 中的应用

Material Design 是由 Google 提出的一种设计语言,它强调的是统一、直观以及美观的设计风格。自2014年首次推出以来,Material Design 已经成为了 Android 平台上应用程序界面设计的标准之一。这一设计理念不仅仅局限于视觉上的美感,更重要的是它为用户提供了更加一致且易于理解的操作体验。通过引入卡片式布局、阴影效果以及明确的层次结构,Material Design 让每一个交互都变得更加自然流畅。对于 Android 开发者而言,这意味着他们可以利用这一套成熟的设计规范来快速构建既符合现代审美又具有良好用户体验的应用程序。

随着 Android 系统版本不断更新迭代,Material Design 也在持续进化,以适应不同设备和屏幕尺寸的需求。如今,即使是 Android API 8 这样的较早版本,也能够支持大部分 Material Design 的特性,这使得开发者能够在广泛的设备上实现一致的设计风格。不仅如此,Google 还提供了丰富的开发工具和库来简化 Material Design 的实现过程,让开发者能够更加专注于功能开发而非繁琐的样式调整。

1.2 Material Dialogs 的优势与特点

Material Dialogs 库正是基于上述设计理念而诞生的一款强大工具。相较于传统的 Android 对话框,Material Dialogs 不仅提供了更加丰富多样的定制选项,还确保了所有对话框都严格遵循 Material Design 的指导原则。这意味着开发者可以通过简单的几行代码就能创建出既美观又实用的对话框,无需担心样式不统一或不符合设计规范的问题。

Material Dialogs 的一大亮点在于其高度的灵活性。无论是简单的信息提示还是复杂的表单输入,开发者都可以轻松地通过该库来实现。更重要的是,Material Dialogs 支持多种类型的控件组合,允许开发者根据实际需求自由搭配不同的组件,从而创造出满足特定场景需求的对话框。此外,该库还内置了许多便捷的功能,比如动画效果、主题切换等,进一步提升了用户体验。

总之,Material Dialogs 以其出色的易用性和强大的定制能力,成为了 Android 开发者不可或缺的工具之一。通过使用 Material Dialogs,不仅可以显著提高开发效率,还能确保最终产品的质量达到高标准。

二、Material Dialogs 的基础使用

2.1 初始化和显示对话框的基本步骤

在开始使用 Material Dialogs 创建对话框之前,首先需要确保项目中已正确集成了该库。这通常涉及到添加依赖项到项目的 build.gradle 文件中。例如,可以通过以下方式添加 Material Dialogs 的最新版本:

dependencies {
    implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
}

接下来,为了初始化一个基本的对话框,开发者只需几行简洁的代码即可实现。以下是一个简单的示例,展示了如何创建并显示一个带有标题和消息文本的基本对话框:

new MaterialDialog.Builder(context)
    .title("欢迎使用")
    .content("感谢您选择我们的应用!")
    .positiveText("确定")
    .onPositive((dialog, which) -> {
        // 处理用户点击“确定”按钮后的逻辑
    })
    .show();

在这个例子中,MaterialDialog.Builder 类被用来配置对话框的各种属性,如标题、内容以及按钮文本。通过调用 show() 方法,对话框即刻呈现在用户面前。值得注意的是,Material Dialogs 还允许开发者为每个按钮设置点击监听器,以便于执行相应的操作处理。

2.2 自定义对话框样式与主题

尽管 Material Dialogs 默认提供了一套美观大方的设计方案,但有时候开发者可能希望进一步自定义对话框的外观,使其更贴合应用程序的整体风格。幸运的是,Material Dialogs 具备极高的可定制性,允许开发者从字体颜色到背景图像等多个方面进行个性化设置。

例如,如果想要改变对话框的背景色,可以通过如下方式实现:

new MaterialDialog.Builder(context)
    .title("提示")
    .content("这是一条重要通知。")
    .positiveText("知道了")
    .backgroundColor(Color.parseColor("#FF5722")) // 设置背景颜色
    .show();

除了直接修改颜色值外,开发者还可以通过定义自定义的主题来统一整个应用程序中所有对话框的样式。这通常涉及到创建一个新的 XML 文件,在其中指定一系列样式属性,然后将其作为参数传递给 MaterialDialog.Builder 的构造函数。这种方式不仅有助于保持一致性,还能极大地简化后期维护工作。

通过这些简单而强大的功能,Material Dialogs 成为了 Android 开发者手中不可或缺的利器,帮助他们在短时间内打造出既符合 Material Design 规范又能充分展现个性化的对话框界面。

三、实战代码示例

3.1 创建简单的列表对话框

在日常的 Android 应用开发中,列表对话框是一种非常常见的交互形式,它允许用户从多个选项中选择一个或多个条目。Material Dialogs 为开发者提供了简便的方式来创建这种类型的对话框,不仅增强了用户体验,同时也使得代码更加简洁明了。下面是一个简单的示例,展示了如何使用 Material Dialogs 构建一个列表对话框:

// 定义列表项
String[] items = {"选项一", "选项二", "选项三"};

new MaterialDialog.Builder(context)
    .title("请选择一项")
    .items(items)
    .itemsCallbackSingleChoice(-1, (dialog, view, which, text) -> {
        // 用户选择了某个选项后触发此回调
        Toast.makeText(context, "您选择了:" + text, Toast.LENGTH_SHORT).show();
        return true;
    })
    .negativeText("取消")
    .show();

在这个示例中,我们首先定义了一个字符串数组 items 来存储列表项的内容。接着,通过 MaterialDialog.Builderitems 方法设置了列表项,并通过 itemsCallbackSingleChoice 方法指定了当用户选择某个选项时应执行的操作。这里,-1 表示初始时没有选中任何项。当用户做出选择后,回调函数会接收到所选选项的索引和文本内容,并在此基础上执行相应的逻辑处理。此外,我们还添加了一个“取消”按钮,以便用户可以随时退出对话框而不做任何选择。

通过这样的设计,开发者不仅能够轻松地向用户展示一系列可供选择的项,还能在用户做出选择后立即响应,提供即时反馈。这对于提升应用的互动性和用户体验来说至关重要。

3.2 实现带有输入框的对话框

在许多应用场景下,我们需要让用户输入一些信息,比如用户名、密码或者评论内容等。Material Dialogs 同样支持创建带有输入框的对话框,使得数据收集过程变得既高效又优雅。下面是一个具体的实现示例:

new MaterialDialog.Builder(context)
    .title("请输入您的名字")
    .inputType(InputType.TYPE_CLASS_TEXT)
    .input("提示", "默认值", (dialog, input) -> {
        String name = input.toString();
        // 用户输入的名字保存在变量name中
        Toast.makeText(context, "您好," + name + "!", Toast.LENGTH_SHORT).show();
    })
    .positiveText("确认")
    .negativeText("取消")
    .show();

在这个例子中,我们使用了 input 方法来创建一个输入框,并通过 inputType 参数指定了输入类型。这里设置为 TYPE_CLASS_TEXT 表示普通文本输入。同时,我们还提供了两个额外的参数——一个是提示信息,用于告知用户应该输入什么类型的内容;另一个是默认值,当对话框首次显示时会在输入框内预填充该值。当用户点击“确认”按钮后,输入的内容会被传递给回调函数,在那里我们可以对其进行进一步处理,比如验证、存储等操作。

通过这种方式,开发者可以方便地收集用户输入的信息,并根据需要采取下一步行动,极大地增强了应用的功能性和可用性。

3.3 打造带有选择项的复选框对话框

有时候,我们需要让用户从多个选项中进行多选,这时候就可以使用带有复选框的对话框来实现这一功能。Material Dialogs 提供了灵活的配置选项,使得创建这样的对话框变得异常简单。以下是一个具体的实现案例:

// 定义复选框选项
String[] options = {"选项A", "选项B", "选项C"};
boolean[] checkedItems = new boolean[options.length];

new MaterialDialog.Builder(context)
    .title("请选择您感兴趣的领域")
    .items(options)
    .itemsCallbackMultiChoice(null, (dialog, indices, text) -> {
        StringBuilder selectedOptions = new StringBuilder();
        for (int i : indices) {
            if (selectedOptions.length() > 0) {
                selectedOptions.append(", ");
            }
            selectedOptions.append(text[i]);
        }
        Toast.makeText(context, "您选择了:" + selectedOptions, Toast.LENGTH_SHORT).show();
        return true;
    })
    .negativeText("取消")
    .positiveText("确定")
    .show();

在这个示例中,我们首先定义了一个字符串数组 options 来存储各个复选框的标签,并创建了一个布尔型数组 checkedItems 用于记录每个选项是否被选中。然后,通过 items 方法设置了复选框的标签,并通过 itemsCallbackMultiChoice 方法指定了当用户选择或取消选择某个选项时应执行的操作。这里,我们遍历了所有被选中的选项索引,并将它们对应的标签拼接成一个字符串,最后通过 Toast 显示出来。

通过这样的设计,开发者可以轻松地让用户从多个选项中进行多选,并获取用户的选择结果,从而更好地了解用户需求或偏好,为用户提供更加个性化的服务。

四、高级功能与应用

4.1 使用自定义布局

在 Material Dialogs 的世界里,自定义布局无疑为开发者们打开了一扇通往无限可能的大门。想象一下,当你不再受限于预设的模板,而是可以根据具体的应用场景和设计需求随心所欲地创造对话框时,那将是一种怎样的体验?张晓深知这一点的重要性。她认为,通过自定义布局,不仅能更好地融入应用的整体风格,还能在细节处体现设计者的匠心独运,使用户感受到更加贴心的服务。

为了实现这一目标,Material Dialogs 提供了强大的自定义功能。开发者可以通过设置 customView 方法来加载自定义的视图布局文件。这样一来,无论是复杂的表单输入,还是精美的图片展示,甚至是动态的内容更新,都能在一个对话框中得以完美呈现。例如,假设你需要创建一个用于上传照片的对话框,不仅要有选择图片的按钮,还需要实时预览所选图片的效果。这时,自定义布局的优势便显现出来了:

LayoutInflater inflater = LayoutInflater.from(context);
View customView = inflater.inflate(R.layout.custom_dialog_layout, null);

new MaterialDialog.Builder(context)
    .customView(customView, false)
    .positiveText("上传")
    .negativeText("取消")
    .onPositive((dialog, which) -> {
        // 获取用户选择的照片并上传
    })
    .show();

在这段代码中,我们首先使用 LayoutInflater 加载了一个自定义的布局文件 custom_dialog_layout,然后通过 customView 方法将其设置为对话框的内容区域。这样一来,开发者便可以在 custom_dialog_layout.xml 文件中自由发挥,添加任何所需的 UI 组件,如 ImageView、EditText 等,从而实现更加丰富的交互效果。

通过这种方式,Material Dialogs 不仅帮助开发者解决了传统对话框功能单一、样式固定的问题,还极大地提高了应用界面的灵活性与美观度,使得每一次对话框的出现都能带给用户耳目一新的感觉。

4.2 实现底部sheets和全屏对话框

除了常规的对话框外,Material Dialogs 还支持创建底部sheets和全屏对话框,这两种特殊类型的对话框在特定场景下能提供更好的用户体验。底部sheets 通常用于展示一系列选项或操作,而全屏对话框则适用于需要用户集中注意力处理的任务。这两种对话框的共同特点是它们能够更加自然地融入当前页面,减少用户的认知负担。

对于底部sheets,Material Dialogs 提供了专门的方法来实现这一效果。通过简单的配置,开发者就能轻松创建出一个从屏幕底部滑出的对话框,非常适合用于展示菜单或选项列表。例如:

new MaterialDialog.Builder(context)
    .title("请选择一项")
    .items(Arrays.asList("选项一", "选项二", "选项三"))
    .itemsCallbackSingleChoice(-1, (dialog, view, which, text) -> {
        Toast.makeText(context, "您选择了:" + text, Toast.LENGTH_SHORT).show();
        return true;
    })
    .negativeText("取消")
    .adapterStyle(R.style.MaterialDialogAdapterStyle_Sheet)
    .show();

在这里,我们通过设置 adapterStyle 属性为 R.style.MaterialDialogAdapterStyle_Sheet,告诉 Material Dialogs 我们希望创建的是一个底部sheet。这样,当对话框显示时,它就会以一种平滑的动画效果从屏幕底部滑出,给用户带来更加自然的交互体验。

而对于全屏对话框,Material Dialogs 同样提供了相应的支持。通过调整对话框的宽度和高度,我们可以轻松地创建出占据整个屏幕的对话框,这对于需要展示大量信息或进行复杂操作的场景尤为适用。例如:

new MaterialDialog.Builder(context)
    .title("详细信息")
    .customView(R.layout.full_screen_dialog_layout, false)
    .positiveText("关闭")
    .show();

在这个例子中,我们使用了一个自定义的布局文件 full_screen_dialog_layout 作为对话框的内容区域。通过适当的设计,这个布局可以包含任意数量的 UI 组件,从而实现丰富的功能和视觉效果。当对话框显示时,它将占据整个屏幕,确保用户能够专注于当前任务,不受其他元素干扰。

通过这些高级功能,Material Dialogs 不仅帮助开发者解决了传统对话框在某些特定场景下的局限性,还为 Android 应用带来了更加现代化和人性化的交互体验。无论是底部sheets 还是全屏对话框,都能在提升应用可用性的同时,增强用户的满意度。

五、优化与性能

5.1 对话框的内存管理与优化

在移动应用开发中,内存管理一直是一个不容忽视的重要环节。尤其是在资源有限的移动设备上,合理有效地管理内存不仅能够提升应用的运行效率,还能显著改善用户体验。对于Material Dialogs这样一个高度可定制且功能丰富的对话框库来说,如何在保证其强大特性的前提下,做到对内存资源的有效利用,显得尤为重要。张晓深知这一点的重要性,她认为良好的内存管理不仅能够避免因内存泄漏导致的崩溃问题,还能让应用运行得更加流畅。

在Material Dialogs的设计中,内存管理主要涉及两方面:一是对话框本身的生命周期管理,二是对话框内部UI组件的优化。首先,对于对话框的生命周期管理,开发者应当确保在对话框不再需要时及时释放其所占用的资源。这通常意味着在对话框关闭时,不仅要移除其视图,还要清理掉所有相关的监听器和回调函数。例如,当一个Material Dialog被关闭后,可以通过调用dialog.dismiss()方法来释放其资源。此外,如果对话框中包含了图片或其他大容量的数据,那么在对话框销毁时,还应确保这些数据也被妥善处理,避免造成不必要的内存开销。

其次,针对对话框内部UI组件的优化,则需要开发者从细节入手,尽可能减少不必要的视图渲染。比如,在创建自定义对话框时,可以考虑使用懒加载机制来延迟加载那些非立即可见的视图组件,这样既能减轻初次加载时的压力,也能有效降低内存消耗。另外,对于那些频繁更新内容的对话框,如实时预览功能,开发者还应关注其刷新频率,避免过度绘制导致的性能下降。

通过这些细致入微的优化措施,Material Dialogs不仅能在视觉上给人以愉悦的感受,更能从技术层面保障应用的稳定性和流畅度,让每一次对话框的弹出都成为一次完美的用户体验之旅。

5.2 对话框的性能监控与调试

在确保了对话框的内存管理之后,接下来便是如何对其性能进行有效的监控与调试。性能监控是发现和解决性能瓶颈的关键步骤,而调试则是进一步定位问题根源的过程。对于Material Dialogs而言,由于其高度的定制化特性,性能问题往往更加复杂多样,因此建立一套完善的性能监控体系显得尤为重要。

张晓建议,开发者可以从以下几个方面着手进行性能监控:首先是FPS(Frames Per Second)的监测,这是衡量应用流畅度的一个重要指标。通过集成Android Studio中的Profiler工具,开发者可以实时查看应用运行时的帧率情况,一旦发现帧率波动较大或低于预期值,就需要及时排查原因。其次是CPU和内存的使用情况,这两个指标直接关系到应用的响应速度和稳定性。借助Android Studio提供的Memory Profiler和CPU Profiler工具,开发者可以详细分析应用在运行过程中内存分配和CPU占用的具体情况,从而找出潜在的性能瓶颈。

在调试阶段,张晓推荐使用Logcat来辅助定位问题。通过在关键位置插入日志输出语句,开发者可以追踪到对话框创建、显示及销毁等各个环节的具体执行流程,进而发现可能导致性能下降的因素。此外,对于那些难以复现的偶发性问题,还可以考虑使用Crashlytics等第三方服务来进行远程监控和错误报告,以便于快速响应并修复问题。

通过这一系列的监控与调试手段,Material Dialogs不仅能够始终保持最佳状态,还能帮助开发者不断提升自身的技术水平,让每一次对话框的呈现都成为一次技术与艺术完美结合的典范。

六、与 Android API 的兼容性

6.1 兼容 Android API 8 及以上版本

Material Dialogs 的一大亮点在于其广泛的兼容性,它能够支持从 Android API 8(即 Android 2.2 Froyo)起的所有版本。这意味着,无论用户手持的是何种设备,开发者都能够利用 Material Dialogs 来创建既美观又实用的对话框,而无需担心兼容性问题。这对于那些希望覆盖更广泛用户群体的应用来说,无疑是一个巨大的优势。

张晓深知,尽管 Android 系统已经发展到了更高的版本,但在全球范围内,仍然有相当一部分用户使用着较为老旧的设备。根据 StatCounter 的数据显示,截至 2023 年初,仍有超过 5% 的 Android 用户停留在 API 8 至 API 15 之间的版本上。因此,Material Dialogs 的这一特性不仅体现了其对历史遗留系统的尊重,也为开发者提供了一个强有力的工具,让他们能够在不牺牲用户体验的前提下,触及更多的潜在用户。

更重要的是,Material Dialogs 在设计之初就充分考虑到了跨版本兼容的问题。它不仅能够无缝地与最新的 Android 版本协同工作,还能在较低版本的系统上保持一致的表现。这意味着,无论是在最新的旗舰机型上,还是在几年前的老款手机上,Material Dialogs 都能呈现出同样高质量的对话框体验。这对于那些希望在不同设备间保持一致用户体验的应用来说,无疑是一个巨大的福音。

6.2 适配不同版本的注意事项

尽管 Material Dialogs 在兼容性方面表现出色,但在实际开发过程中,开发者仍需注意一些细节,以确保对话框在不同版本的 Android 系统上都能正常工作。张晓提醒道,适配不同版本的 Android 系统不仅仅是关于兼容性的问题,更是关于用户体验的一次全面考量。

首先,开发者需要注意的是,不同版本的 Android 系统可能存在一些细微的差异,特别是在 UI 组件的样式和行为上。例如,在较新版本的 Android 系统中,Material Design 引入了一些新的动画效果和过渡效果,而在较老的版本中,这些效果可能无法完全实现。因此,在设计对话框时,开发者应尽量采用通用的样式和效果,以确保在所有版本的系统上都能保持一致的视觉体验。

其次,对于那些依赖于特定系统功能的对话框,开发者需要特别留意这些功能在不同版本中的实现情况。例如,某些高级的输入控件或自定义布局可能在较新版本的 Android 系统中表现良好,但在较老的版本中可能会出现问题。在这种情况下,开发者可以考虑采用条件编译的方式,根据不同版本的系统选择不同的实现方案,从而确保对话框在所有设备上都能正常工作。

最后,张晓强调,测试是确保对话框在不同版本的 Android 系统上正常工作的关键。开发者应尽可能在多种设备和系统版本上进行测试,以发现并解决潜在的问题。此外,还可以利用模拟器和真机测试相结合的方式,全面覆盖各种可能的情况。通过这样的努力,开发者不仅能够确保对话框在不同版本的 Android 系统上都能正常工作,还能为用户提供更加稳定和一致的使用体验。

七、社区与资源

7.1 Material Dialogs 社区与支持

Material Dialogs 不仅仅是一款强大的对话框库,它背后还有一个充满活力的社区。这个社区由来自世界各地的开发者组成,他们共同致力于推动 Material Dialogs 的发展和完善。无论是遇到技术难题,还是寻求最佳实践,Material Dialogs 的社区都是一个宝贵的资源库。在这里,你可以找到详尽的文档、活跃的讨论区以及热心的贡献者们。张晓深知,一个活跃且支持性强的社区对于任何开源项目来说都是至关重要的。她经常参与社区活动,与其他开发者交流心得,分享经验,同时也从中汲取灵感和动力。

据统计,Material Dialogs 的 GitHub 仓库已经积累了超过 5000 个 star 和 1000 个 fork,这足以证明其受欢迎程度。不仅如此,社区成员们还积极贡献代码,修复 bug,并提出改进建议,使得 Material Dialogs 能够持续进化,满足更多开发者的需求。对于新手而言,加入 Material Dialogs 的官方 Discord 服务器是一个不错的选择,这里不仅有关于最新版本的公告,还有定期举办的线上研讨会和技术分享会,帮助大家更快地掌握 Material Dialogs 的使用技巧。

此外,Material Dialogs 的官方博客也是一个不容错过的信息来源。这里定期发布关于库的新功能介绍、使用教程以及开发者访谈等内容,为用户提供了丰富的学习资源。张晓建议,对于那些希望深入了解 Material Dialogs 内部机制的开发者来说,阅读官方博客中的技术文章是非常有益的。这些文章不仅详细解释了库的工作原理,还提供了大量的代码示例,帮助开发者更好地理解和应用 Material Dialogs。

7.2 相关资源与学习材料推荐

为了帮助开发者更好地掌握 Material Dialogs 的使用方法,张晓精心整理了一份学习资源清单。这份清单涵盖了从入门到进阶所需的各种材料,无论是初学者还是有一定经验的开发者,都能从中受益匪浅。

首先,官方文档是学习 Material Dialogs 的最佳起点。文档详细介绍了库的所有功能模块及其使用方法,并提供了丰富的代码示例。通过阅读文档,开发者可以快速上手,并学会如何利用 Material Dialogs 创建各种类型的对话框。张晓特别推荐新手从文档中的“快速入门”部分开始学习,这部分内容简洁明了,非常适合初学者。

其次,YouTube 上有许多关于 Material Dialogs 的视频教程。这些教程通常由经验丰富的开发者制作,不仅讲解了库的基本用法,还分享了许多实用的小技巧。通过观看这些视频,开发者可以更直观地理解 Material Dialogs 的工作原理,并学到一些书本上学不到的知识。张晓自己也曾录制过多期关于 Material Dialogs 的教学视频,深受观众喜爱。

此外,GitHub 上也有不少优秀的示例项目,这些项目展示了 Material Dialogs 在实际应用中的使用场景。通过研究这些示例代码,开发者可以更好地理解如何将 Material Dialogs 应用于自己的项目中。张晓建议,对于那些希望深入学习 Material Dialogs 的开发者来说,动手实践是最好的学习方式。可以选择一个感兴趣的示例项目,尝试自己动手实现其中的功能,这样不仅能加深对库的理解,还能锻炼编程技能。

最后,参加线下的开发者大会和技术沙龙也是提升技能的好方法。这些活动不仅能让开发者有机会与同行面对面交流,还能了解到最新的技术和趋势。张晓曾多次参加过类似的活动,并从中收获颇丰。她认为,通过与他人交流,可以开阔视野,激发灵感,对于提升个人技术水平大有裨益。

八、总结

通过本文的详细介绍,我们不仅了解了Material Dialogs这一高度可定制且易于使用的对话框库,还掌握了其在Android开发中的广泛应用。从Material Design的设计理念出发,Material Dialogs凭借其丰富的功能和高度的灵活性,成功地帮助开发者在兼容Android API 8及以上版本的基础上,实现了既美观又实用的对话框设计。通过一系列详实的代码示例,本文展示了如何创建从简单的信息提示到复杂的表单输入等多种类型的对话框,并探讨了自定义布局、底部sheets以及全屏对话框等高级功能。此外,本文还强调了对话框的内存管理和性能优化的重要性,以及如何在不同版本的Android系统上确保一致的用户体验。Material Dialogs背后的活跃社区和支持资源也为开发者提供了强有力的支持,帮助他们不断提升技术水平。总而言之,Material Dialogs不仅是Android开发者手中的有力工具,更是提升应用质量和用户体验的关键所在。