65.9K
CodeProject 正在变化。 阅读更多。
Home

第 6 篇文章:Android 中的菜单、多意图、资源管理和数据绑定,附带一个简单的 ImageList 应用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (9投票s)

2014 年 8 月 24 日

CPOL

33分钟阅读

viewsIcon

41195

downloadIcon

1878

学习从头开始构建一个应用程序, 以便通过真实案例研究来理解 Android 资源。

1. 背景

本教程旨在通过一个图像列表控件的案例研究,为 Android 中的资源管理提供一个非常基础的初学者教程,该控件是一个已发布的实时应用程序的一部分。简单来说,我们想开发一个 Android 表单,其中包含一个列表框,该列表框显示一个可滚动的项目列表,包括图像。但通常,除非您了解正在学习的新事物的强大功能,否则教程不会太有趣。您也不知道它到底可以用在哪里,以及如何自定义它。因此,我决定不只是发布一个随机教程,而是通过一系列教程指导您完成一个名为 DreamCurtains 的已发布应用程序的开发过程。

DreamCurtains 本质上是一个免费的室内设计应用程序,它可以帮助您为窗户拍摄快照,然后从供应商提供的窗帘布列表中进行选择,以可视化如果您选择特定的面料,您的窗户会是什么样子。请看下图。这正是我们想要创建的列表。

因此,在本教程中,我们有兴趣创建一个以以下形式呈现信息的控件。我们将涵盖:

  1. 用于创建此类列表的 XML 布局
  2. 管理列表数据
  3. 在列表中选择
  4. 列表样式
  5. 创建菜单
  6. 使用多个表单并从一个表单访问另一个表单的数据。

我们将使用 DreamCurtains 的特定数据,但我们也将了解与本教程相关的其他可能情况和用法。

在本教程结束时,您将能够创建自己的列表并对其进行自定义。此外,本教程的最终目标是指导您了解 Android 设计的最佳实践,这些实践实际上有助于您最终在 Google Play 上发布应用程序。

那么让我们开始,一步一步地学习创建 ImageList 的过程。

2. 开始应用程序开发

在我们开始讨论本文的具体内容之前,我想为您的 Android 项目提供一个重要提示。当您创建 Android 项目时,默认命名空间以 com.example. 开头。但请记住,com.example 是一个保留的包名。Google Play 不允许发布任何使用 com.example 包名的应用程序。因此,从一开始就做出明智的决定并提供一致的命名空间是有意义的。如果您有 Google Play 开发者帐户,请创建类似的命名空间,否则您可以随时创建帐户(这不是免费的,但可以以低廉的价格获得)并使用该名称。或者,如果您现在没有 Play 帐户,您可以使用以后可能想要使用的命名空间。请注意,包名可以使用 Eclipse 强大的重构工具随时更改,但正如我们所说,为什么要把基础工作留到最后呢?

现在,对于此示例,我也建议您在将代码导入 Eclipse 后,适当地更改包名,以便您可以跟踪自己的包。您可以通过右键单击 src 文件夹中的包名并选择“重构”选项,然后选择“重命名”来完成此操作。

但是,我假设您对从头开始构建项目更感兴趣,并且一开始不需要查看代码。

因此,在 Eclipse 中单击“文件”->“新建”->“其他”,然后选择 Android 应用程序项目,如下图所示

在下一个屏幕中,输入应用程序名称为 MyImageList,并使用适当的包名和其他选项,如下图所示。

我想讨论一些关于选择正确的最小 SDK 和目标 SDK 的问题。很多时候,您可能一开始很少关注这两个问题,结果在发布应用程序后,您会看到许多设备甚至可能是您测试应用程序的设备显示“不兼容”。请记住,2.2 确实是您可能想要关注的最低版本。但是,如果您专门为智能手机开发应用程序,那么没有多少好的智能手机使用那么老的 2.2。Android 4.0 与其以前的版本相比发生了巨大的变化,并提供了许多旧版 Android 中没有的酷炫功能。Android 的次要修订版,如 4.1 和 4.2,也提供了一些新功能,但与 Android 4.0 相比,并没有什么根本性或戏剧性的新功能。因此,Android 4.0 确实是一个很好的选择,可以同时设置您的最小和目标 SDK。保持较高会删除许多设备,保持较低会删除功能。所以这确实是一个权衡的问题。

现在,只需在所有后续表单中选择“下一步”即可。确保您在“创建活动”表单中选择了“空白活动”。

如果一切顺利,您将看到一个类似于下图的屏幕。

现在我们想通过菜单单击打开图像列表表单。图像列表将在表单中显示窗帘列表(图像缩略图、窗帘名称、颜色和其他信息)。当我们选择其中任何一个时,完整图像必须显示在主页的图像中。

所以我们应该在主表单中有一个图像视图和菜单。为此,我们将在主表单中拖放一个 Image View 控件,如下图所示。

3. 布局、字符串和菜单

如您所见,默认情况下它将默认图标作为图像源,并按比例缩放图像以适应图标的大小。但是,我们希望图像在整个表单上对齐。我们还想重命名图像视图的资源名称,以便在使用 findViewById 方法时在“代码”中轻松跟踪。因此,我们将单击 fragment_main.xml 并更新 xml 文件。请注意,宽度和高度属性已更改为 match_parent,而不是默认的 wrap_content。另请注意,id 已从默认的 imageView1 更改为 imMainForm

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.grasshoppernetwork.myimagelist.MainActivity$PlaceholderFragment" >

    <ImageView
        android:id="@+id/imMainForm"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="70dp"
        android:layout_marginTop="43dp"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>

一旦您仔细地进行了更改,您可以返回设计模式,并看到表单更改,如下图所示。您可以清楚地看到图像大小的变化。

您可以尝试使用 layout_marginLeft layout_marginTop 参数,看看图像位置有何不同。

此时,您可以在模拟器中运行代码并查看结果。但是,作为个人选择,我总是更喜欢 USB 调试模式下的真实 Android 设备,而不是 AVD。首先,它为您提供了您想要探索的所有功能,其次,您可以知道您的表单在真实设备上的外观。您还可以在真实的 RAM 和显示设置中运行代码,这提供了更好的测试环境。

设置好主页表单后,我们现在要设置菜单选项。

要设置菜单,请进入 res->menu 并双击 main.xml。它将在布局模式下打开菜单 xml。您可以从 xml 创建菜单选项作为项目,甚至可以通过 GUI 创建项目,如下图所示。首先,您需要单击添加并选择菜单项。更改 ID 和标题。此时不要担心其他更改。使用 ctrl+s 保存您的更改。

现在当您查看 xml 部分时,您将清楚地看到以下行

 <item android:id="@+id/menuOpenList" android:title="Open Curtain List"></item>

您还会注意到 Android 中此行旁边有一个黄色警告标记。它会警告您不要使用没有资源的静态文本。在 Android 中,对菜单文本或标签等静态文本使用字符串资源始终是一个好习惯。

我们将通过创建一个字符串资源来消除此警告。您可以通过双击 res/values/strings.xml,然后添加一个新的字符串来创建字符串资源。您可以提供一个适当的名称,并将其值设置为“Open Curtain List”,如下图所示。

现在我们所要做的就是返回 res/menu 下的 main.xml,并将项目的静态文本更改为新创建的资源名称。

<item android:id="@+id/menuOpenList" android:title="@string/MenuOpenCurtainTest"/>

现在警告已经消失了。是时候构建和测试我们到目前为止所做的工作了。下图是到目前为止应用程序的屏幕截图,取自我的 Asus-Google Nexus 7 英寸的 DDMS。

但是打开 ImageList 的基本要求尚未完成。当您按下“打开窗帘表单”菜单选项时,您希望打开另一个表单。因此,我们仍然需要一些后台代码来触发菜单,并且需要用后台代码打开另一个表单。

4. 新布局和表单

我们首先创建一个名为 list_row.xml 的 RelativeLayout。

右键单击 res/layout,选择“新建”->“文件”,提供文件名 list_row.xml。如下图所示。

在此步骤之后,您将看到没有任何表单的设计器。只需单击 Graphics layout 旁边的 list_row.xml 选项卡并添加以下两行。

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

现在,当您回到图形布局时,您将看到白色表单。

您也可以以更直接的方式进行。右键单击布局后选择“新建”时,选择“其他”。在那里您可以选择一个 Android 活动。我更喜欢使用第一种方法,因为我可以根据我的需要很好地设置布局属性,而不会遇到太多麻烦。

好吧。我们有一个应该包含图像列表的表单,我们有一个应该从那里调用的菜单。但还有一件事缺失!如果您展开 src 及其后续的包文件夹,您将看到 MainActivity.Java 。同样地,我的 list_row.xml 也需要一个代码隐藏。

通过右键单击 src 文件夹中的包名并选择“新建类”,创建一个名为 CurtainListActivity.java 的新 Java 类。请参阅下图以更好地理解该过程。

但是请记住,我们的 Java 类不是普通的类。它是一个活动类。因此,让您的新类扩展 Activity。当您将类声明修改为

public class CurtainListActivity extends Activity 

您将在此行的左侧看到一个红色错误,如下图所示。只需单击错误标记,Eclipse 就会弹出所有可能的解决方案,选择导入 Android.Activity 选项。

因此,当您在 Android 开发中看到 Eclipse 中的错误消息时,您总是可以使用此技巧快速修复错误。

Activity 类必须覆盖 onCreate 方法,所有组件初始化都必须在此处进行。您可以通过简单地从 MainActivity 类复制 onCreate 方法并粘贴并更新方法定义来完成此操作。

如果您从 MainActivity 类复制了 onCreate 方法,那么您会看到一个用于 frame_layout 的 if。您绝对不想要它。因此,删除整个部分。您还会注意到自动导入 com.grasshoppernetwork.myimagelist.MainActivity.PlaceholderFragment 。也删除此行。您现在所要做的就是,在 findViewById 中,您必须将 R.layout.activity_main 更改为 R.layout.list_row。但是当您删除 activity_main 并使用 ctrl+space 查看选项时,很可能它不会向您显示 list_row 选项。如果它没有,那么只需构建项目一次(显然您会喜欢注释掉这个,因为 R.layout. 之后没有任何内容)。一旦项目成功构建,您将找到 list_row 选项。

因此,在您创建新布局后,构建项目一次以使 ID 反映在代码中始终是一个不错的选择。

所以,最终我们的 CurtainListActivity 类看起来像这样

package com.grasshoppernetwork.myimagelist;

import android.app.Activity;
import android.os.Bundle;

public class CurtainListActivity extends Activity 
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list_row);

        
    }
}

如果您的类看起来也完全相同,那么您可能没有按照教程开头建议的那样更改包名。是时候修复它并更改包名了。

每当您有一个活动类时,您都需要向 AndroidManifest 文件提供信息。如果您不这样做,那么当您尝试使用此表单时,您的应用程序将触发错误。

这里还要注意的一点是,对于这个特定的应用程序,我们希望以横向模式呈现每个表单。因此,您需要在清单中提及一些额外的信息。继续并将以下行粘贴到 AndroidManifest.xml 中,位于主活动部分的结束 </Activity> 之后。

<activity android:name="com.grasshoppernetwork.myimagelist.CurtainListActivity"
   android:screenOrientation="landscape"
   android:configChanges="orientation|keyboardHidden">
</activity>

您现在拥有了所有我们继续使用 ImageList 所需的一切。但是等等!我们是不是应该检查一下我们的第二个表单是否通过菜单选择正确调用了呢?当然应该。由于菜单是从 MainActivity 触发的,让我们进入 MainActivity 并进行一些编码。

如果您在 MainActivity 表单中向下滚动,您可以很容易地找到 onOptionsItemSelected(MenuItem item)。 当单击任何菜单项时,将调用此方法。您还会看到一个用于设置菜单的默认 if。但我们想要更简洁的代码。因此,我们希望用 switch 替换 if,并为 menuOpenList 放置一个 case。

可以使用 Intent 类对象从任何表单调用第二个表单。Intent 可以被视为一个类的实例(类似于我们在 C# 中使用的 Dialog 类)。Intent 可以接受参数并返回一些结果。因此,告诉 Intent 您想用它做什么总是一个不错的选择。由于我们想选择面料,我们将使用一个名为 FABRIC_SELECTION 的静态整数来告诉 CurtainActivityClass 的 Intent,我们希望它在第二个表单中返回用户选择的项目。onOptionItemSelected 方法采用以下形式。

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        switch(id)
        {
         case R.id.menuOpenList:
                try{
                Intent intent1 = new Intent();
                intent1.setClass(this, CurtainListActivity.class);
                Log.i("In Menu Browse","In Menu Browse");
                // parameters
                startActivityForResult(intent1, FABRIC_SELECTION);
                }catch(Exception ex)
                {
                    
                }
            break;
        }
        return super.onOptionsItemSelected(item);
    }

现在让我们构建解决方案并检查它是否执行了它应该执行的操作:即在菜单选择时打开新表单。

但是当你运行应用程序并选择菜单时,你的应用程序将会崩溃。并显示类似这样的错误消息

这是一个特别有用的错误,因为它让您了解如何处理错误。基本上,Android 运行时会针对任何崩溃发出大量错误消息。不要害怕巨大的文本,您必须仔细阅读消息。这里清楚地显示 CurtainListActivity 二进制 XML 文件 中的错误。由于 CurtainListActivity 是 list_row.xml 的代码隐藏,您需要了解问题出在那里。现在您看到了什么?绝对完美的观察。那里没有 android:layout_widthandroid:layout_height 参数。因此,修改 xml 如下。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">         
</RelativeLayout>

现在构建您的应用程序,您将获得您一直期待的笑容。

另外,我还想给您一个很好的调试技巧。当您有一个复杂的工作流程时(例如,用户应该通过登录表单,选择一些选项并打开表单,并且您在表单中遇到类似的错误),您可以暂时修改 AndroidManifest.xml。您所要做的就是将 <intent-filter> 标签从 MainActivity 部分剪切到您希望应用程序启动后打开的表单部分。这样您就可以快速测试问题区域。一旦问题解决,您可以将清单更改回其原始设置。

例如,在我们的案例中,当我们在第二个表单中遇到错误时,首先修复表单是有意义的。因此,我们可以使用以下设置首先打开 list_row 表单。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.grasshoppernetwork.myimagelist"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="14" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.grasshoppernetwork.myimagelist.MainActivity"
            android:label="@string/app_name" >
           
        </activity>
           <activity android:name="com.grasshoppernetwork.myimagelist.CurtainListActivity"
             
            android:screenOrientation="landscape"
   android:configChanges="orientation|keyboardHidden">
    <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

注意 intent 过滤器部分现在是如何位于 CurtainListActivity 部分内部的。

让我们快速回顾一下我们迄今为止学到的知识

  1. 如何创建 Android 表单
  2. 如何使用菜单
  3. 为什么以及何时应该使用字符串资源
  4. 如何调试与布局相关的问题以及
  5. AndroidManifest 文件扮演的角色。

我们还学习了从一开始就应该遵循的应用程序最佳实践,以便应用程序可以在 Google Play 中提供给大多数设备。

为我们的应用程序创建了坚实的基础后,让我们

5.管理外部资源

游戏开发者比资源更熟悉“资产”这个词。然而,在 Android 中我们称之为资源。资源通常是应用程序使用的静态数据。静态数据到底是什么?例如,在单个“扎气球”游戏中,场景中会有几个气球移动。这些不过是应用程序随机选择的不同气球图像,以呈现在场景中。它们的大小和位置可能不同,但图像将保持不变。这些气球图像是静态资源。一旦您扎破一个气球,您需要应用程序更新分数。现在,该分数是数据库的一部分。它需要频繁更新。我们将该资源称为动态资源。此外,游戏可能还有多个配置存储在 xml 文件中,这些配置可能通过一些 GUI 选项呈现给用户,用户可以选择任何项目但无法更改。此类包含配置数据的资源也将属于静态资源。

为了简单起见,我们将所有静态资源称为“资源”。对于我们的应用程序,我们希望创建一个包含多个窗帘图像的列表项。该列表还应显示其名称、颜色和面料类型,并应允许用户轻松筛选信息。因此,很明显,我们首先需要一组图像,然后我们需要一个 xml 文件,其中包含图像的缩略图以及其他所需信息。现在让我们学习如何做到这一点

5.1 管理图片

首先,让我们看一个窗帘,了解这张图片的一些事实。这是一张背景透明的 PNG 图片。但显然,这样的 PNG 图片会很大。这张图片是 1.1Mb。现在想象一下,如果我们正在寻找至少 100 个窗帘数据库!您最终会捆绑 110Mb 的窗帘图片。有多少客户真正有兴趣下载一个 100Mb 的应用程序?不仅如此,许多设备的内存也有限。因此,大于 30Mb 的应用程序将对许多设备不可见,即使它们满足 SDK 要求。因此,如果您有大量的图像资源,始终建议使用 Jpg 版本而不是 PNG 图像。但是透明度呢?好吧,我们将看到 Android 提供了一些非常酷的代码,您可以使用它通过编程使背景透明。

另一方面,如果您只有少量资源,例如“捏气球”的情况,您真的不希望通过在每次刷新时调用 ImageTransparent 代码来耗尽设备电池。由于资源数量较少,PNG 图像更适合此类选项。但请注意,如果您在商店中为您的应用程序使用自定义菜单(如果您不希望任何人下载您的应用程序,则不会),那么您可能希望该图像是透明的、高质量的,并且毫无疑问是 PNG 图像。

当我刚开始进行 Android 开发时,我花了很长时间才弄清楚这些简单的问题。有很多教程会教你“如何”,但你很少会遇到能回答“为什么”并指导你“何时”的解决方案。以上两段对您作为 Android 开发者来说将是最有帮助的。

好的,我们的应用程序是一个资源密集型应用程序。因此,我们将使用 JPEG 图像。我们还会稍微缩放图像以稍微减小大小。对于许多其他 Android 图像资源利用率,您需要调整大小、重新缩放、重命名图像。这款软件 Fotosizer 将非常方便。安装该软件。它的免费版本足以满足您的日常需求。它的优点是批处理大小调整功能非常酷。

为了完成讨论,我还想在这里提出另一个问题。当您处理图像时,很多时候您会有一个 JPEG 图像,并且您想删除其背景或使其透明。您可以使用以下 C# 代码片段和 Windows 窗体,而不是使用任何外部软件。让我告诉您,这比大多数所谓的在线软件效果更好。

//using System.Drawing;

Bitmap bmp=(Bitmap)Bitmap.FromFile("my.jpg");

bmp.MakeTransparent();

bmp.Save("tmp.png", System.Drawing.Imaging.ImageFormat.Png);

您可以为自己构建一个漂亮的小应用程序,它会要求选择图像并保存。另一方面,您总是可以自由使用其他应用程序。CodeProject 的 C# 社区非常强大。我们发现 C# 比其他语言更舒适。所以上面的代码只是我想提供给社区的一小部分贿赂 :)

很快回到我们的应用程序。我们已经学习了使用图像类型、控制应用程序大小以及如何在图像之间进行转换和调整大小的技巧。最大的问题是把它们放在哪里?

您需要将图像放在 res/drawable- 目录中。但是等等!它们有 5 个版本:hdpi、ldpi、mdpi、xhdpi 和 xxhdpi。我们的图像应该放在哪里,以及它们有什么意义。

经典的 Android 理论会告诉您,您应该根据设备像素 (dp) 中的分辨率放置图像。hdpi=> 640 X 480,ldpi =>240 X 320,mdpi=>320 X 480,xhdpi=> 640 X 960,以及 xxhdpi 用于高于 640x960 的非常高分辨率图像。您需要了解并非所有手机都是智能手机。有些手机功能较弱,屏幕较小。对于这些手机,您需要呈现 240x320 的图像。在像三星 Galaxy S3 这样的智能手机中,如果您呈现分辨率为 240x320 的图像,它必须拉伸图像,这无疑会破坏应用程序的美观性。因此,经典的 Android 理论认为,提供不同分辨率版本的图像始终是一个不错的选择,这样每部手机都可以呈现与其兼容的图像。

但 Android 4 更强大,可以自动为低分辨率手机缩小图像。所以你真的不需要太担心分辨率。放置在 xhdpi 文件夹中的平均分辨率图像是应用程序开发中一个良好而明智的通用选择。

如果某些图像尺寸小于 xhdpi 规范怎么办?这真的无关紧要。您也可以将 50x50 的图像放在 xhdpi 中,它也能完美地渲染您的图像。那么我们学到了什么?

任何图像资源都应放置在 res/drawable-xhdpi 下,无论其大小和分辨率如何,手机都会处理其余部分。(您可能会遇到此规则失效的情况,如果失效,请回到经典理论。但对我来说,我的理论在大多数手机上一直都运行良好)。

但是等等……不要急着把图片放到目录里……

为什么?因为在这个阶段还有一件重要的事情要知道。每个外部资源(我希望您现在非常清楚什么是外部资源)都被 Android 编译为二进制文件。所有内容都捆绑为单个 apk 文件用于分发。因此,Android 不允许通过其绝对路径使用资源。相反,它们被编译为二进制资源,就像菜单和布局等其他资源一样。

如果您查看 gen 文件夹中的 R.java,您会看到为 ic_launcher.png 创建了一个 ID,它的不同版本存在于不同的 res/drawable- 文件夹中。

另请务必注意,Android 支持资源名称以小写字母开头。

当您将 ic_launcher.png 重命名为 Ic_launcher.png 时,它会开始给出二进制 xml 错误。因此,我遵循一个经验法则:“所有外部资源都应以小写字母命名”

下载 drawable-xhdpi.zip,解压并复制图像粘贴到您应用程序的 drawable-xhdpi 文件夹中。如果您在该文件夹内使用拖放,它可能会询问您是想使用“复制”还是“链接”到文件,无需多言,您必须选择复制文件。一旦您完成,您的 res/drawable-xhdpi 将看起来像这样。

在添加新资源(无论是静态外部资源、外部图像、数据资源还是 GUI 资源)之后,构建项目是有意义的。

如果一切按计划进行并且您的应用程序成功构建,您将看到 R.java 已更新,并且现在应该包含所有新资源的 ID。

这证实您做得非常出色。再多一点有趣的东西,您就可以完成本教程,并了解 Android 中资源的工作原理。

5.2 配置数据库

现在我们应该创建我们的 xml 文件,其中包含我们放置的所有照片信息,然后将此 sml 提供给一个类,用于将信息与我们的 list_row 类绑定。用作应用程序数据的 XML 文档,其中包含有关不同静态资源及其附加信息的信息,也称为资产。因此,此类文件必须妥善存储在 Android 的 Asset 文件夹中。

我们先看看我们的数据。每个窗帘都存储在根标签 <Curtains> 内的 <Curtain> 标签中

<Curtain>
    <ItemNo>Ami-Liner-Quill-4</ItemNo>
    <ItemName>Ami-Liner-Quill-4</ItemName>
    <Color>Grey</Color>
    <Genere>Contemporary</Genere>
    <Composition>Polyester</Composition>
    <Utility>Main Curtain</Utility>
    <ImagePath>photo0.jpg</ImagePath>
  </Curtain>

ItemNo、ItemName、Color、Composition、Utility 是我们在这里考虑的窗帘属性。您的数据可能具有其他配置。您可以相应地更改配置。有趣的是 Imagepath。您会发现没有指定绝对路径,因为我们无论如何都不会使用它。在代码的绑定部分,我们将适当地将图像与其特定路径绑定。

下载 curtaindata.xml 并将其放入您的 Asset 文件夹。由于您添加了新资源,请构建您的应用程序一次。现在当您查看 R.java 时,您将找不到任何指向 curtaindata.xml 的链接。那是因为资产的 ID 未生成,您需要使用资产的相对路径通过代码将其与 GUI 组件绑定。

熟悉 .Net 中 MVVM 编码风格的人会觉得它非常相似且容易。其他人无需担心,绑定过程在这里并非火箭科学。

用爱因斯坦的头脑来表示你的 xml 模式作为类,对吧?所以我们首先可以编写一个带有 ItemNo、Color、ItemName、Genere、ImagePath 和 Composition 等字段的类。所有 xml 数据都可以配置为该类对象的列表。

所以,按照我们之前学到的方法,在您的 src 文件夹中创建一个名为 CurtainDbClass 的新类。

package com.grasshoppernetwork.myimagelist;

public class CurtainDbClass 
{
    public String ItemNo,ItemName,Genere,Composition,Utility,Color;
 public CurtainDbClass()
  {
    
  }
 public CurtainDbClass(String ItemNo,String ItemName,String Genere,String Composition,String Utility,String Color)
 {
    this.Composition=Composition;
    this.Genere=Genere;
    this.ItemName=ItemName;
    this.ItemNo=ItemNo;
    this.Utility=Utility;
    this.Color=Color;
 }
}

现在我们需要从 xml 中提取数据并创建所需的列表。但是,如果您还记得,我们的第二个表单,即 list_row 表单尚未准备好接收这些数据。为什么?因为该表单既不包含列表,也不包含可以容纳图像缩略图、其名称和其他属性的任何控件。因此,在我们将重点转移到 Java 编码和将 xml 数据与上述类的列表绑定之前,我们需要更新我们的布局。

5.3 自定义样式

自定义样式是 Android 的另一个重要方面。这与 WPF 设计非常相似,但有一些 Android 特定的调整。使用自定义样式,您可以提供诸如悬停时颜色变化、定义渐变颜色等行为。我们希望对 list_row 进行样式设置,使其更专业。样式资源也像资产一样,可以放置在任何目录中。我喜欢将它们放在 drawable 内部,因为我们总是通过它们定义渲染样式。 下载 list_style.zip,解压缩文件夹并将文件加载到 res/drawable-shdpi 文件夹中。虽然不是强制性为控件定义自定义样式,但我们在此处特意使用它,以便向读者介绍该概念。 gradient_bg_hover XML 文件如下所示。您可以看到我们在此处定义了一个渐变灰色,它以 270 度角径向变化。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
  <gradient
      android:startColor="#78DDFF"
      android:centerColor="#16cedb"
      android:endColor="#09adb9"
      android:angle="270" />
</shape>

同样,我们将 gradient_bg 文件定义为

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
  <gradient
      android:startColor="#D5DDE0"
      android:centerColor="#e7e7e8"
      android:endColor="#CFCFCF"
      android:angle="270" />
</shape>

您可以打开 Html Color Code 网站,获取您的自定义颜色。请参阅以下屏幕截图以了解其工作原理。屏幕截图显示了我们样式中使用的 D5DDE0 颜色。

我们已经定义了颜色样式。现在让我们使用定义的颜色样式为列表定义一个样式。list_selector.xml 正是这样做的。它定义了列表悬停时、未选择任何项目时和选择某个项目时的颜色。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
     android:state_selected="false"
        android:state_pressed="false"
        android:drawable="@drawable/gradient_bg" />
 
    <item android:state_pressed="true"
        android:drawable="@drawable/gradient_bg_hover" />
 
    <item android:state_selected="true"
     android:state_pressed="false"
        android:drawable="@drawable/gradient_bg_hover" />
</selector>

定义样式后,现在让我们更新 list_row.xml 布局文件,以便它不仅可以适应数据绑定,而且同时拥有所有新样式。

理想情况下,我们应该使用一个 ImageView 控件来显示窗帘图像,每个 ItemNumber、ItemName、Color、Composition、Genere 使用一个文本框。但为了简单起见,我们将使用两个文本框,并通过代码将颜色、成分和类型连接成一个字符串,并将其分配给其中一个文本框。另一个文本框将包含名称和编号。但是您可以为每个实体定义不同的文本框。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_selector"
    android:orientation="horizontal"
    android:padding="5dip" >

    <!-- ListRow Left sied Thumbnail image -->

    <LinearLayout
        android:id="@+id/thumbnail"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentLeft="true"
        android:layout_marginRight="5dip"
        android:padding="3dip" >

        <ImageView
            android:id="@+id/list_image"
            style="@dimen/activity_horizontal_margin"
            android:layout_width="0dp"
            android:layout_height="80dip"
            android:layout_gravity="top"
            android:layout_weight="9.63"
            android:contentDescription="@string/app_name"
            android:maxHeight="80dp"
            android:maxWidth="120dp" />
    </LinearLayout>

    <!-- Code -->

    <!-- Name -->

    <TextView
        android:id="@+id/tvCode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/thumbnail"
        android:layout_toRightOf="@+id/thumbnail"
        android:paddingBottom="9dip"
        android:text="@string/strCode"
        android:textColor="#040404"
        android:textSize="15sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/tvName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tvCode"
        android:layout_centerVertical="true"
        android:paddingTop="5dip"
        android:text="@string/strName"
        android:textColor="#343434"
        android:textSize="15sp" />

</RelativeLayout>

现在当你回到图形布局时,你应该看到一个漂亮的列表项行。

除了列表行,您还会看到一些与两个字符串资源相关的错误消息。好吧,我们现在知道诀窍了。修复这个问题不需要任何 Google 搜索。只需进入 /res/values/strings.xml 并在 XML 部分添加以下几行。

<string name="strName">Velvet</string>
    <string name="strCode">ABC1</string>

这两个变量的意义不大,除了它们有助于消除警告。如果您对警告没问题,您可以在 list_row.xml 中对 TextView1 和 TextView2 使用静态文本,而不是字符串资源。

6. 列表控件

我们创建了 list_row 表单并对其进行了更新。从名称本身就可以清楚地看出,此表单表示我们 ListView 的单行。那如何呈现多个这样的行呢?好吧,为此您需要一个 Android ListView 控件,并且为了容纳该控件,您还需要一个表单。让我们在 res/layout 中创建一个名为 main.xml 的布局。选择类型为 LinearLayout ,因为除了 ListView 之外没有其他控件。从工具箱的复合部分拖放一个 ListView 控件。

现在使用以下代码修改布局的 xml 代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <ListView
      android:id="@+id/list"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:divider="#b5b5b5"
      android:dividerHeight="1dp"
      android:listSelector="@drawable/list_selector">
    </ListView>
    
</LinearLayout>

您可以看到我们修改了 ListView 控件的属性,并使用了我们在上一节中创建的名为 list_selector 的样式作为 listSelector。但还有两个更重要的属性需要注意。

首先是 layout_width。它设置为 fill_parent,因为您希望行的宽度与表单的宽度相同。但是,列表视图的 layout_height 属性设置为 wrap_content,因为您确实希望行的高度与 ImageView 高度相同。您可以更改这些属性以查看最终结果中的效果。但现在,我们的 main.xml 应该类似于

添加新资源后,只需重新构建应用程序。我们还了解到新布局需要添加到清单文件中。但我们没有为此定义任何 Java 类。相反,我们将修改我们的 CurtainListActivity.java onCreate 方法,并使该类填充 main xml 而不是 list_row。我们只将 list_row 用作 main.xml 文件中 ListView 行的模板。

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
       PerformList(true);
       ad=new Builder(this);
    }

7. 数据绑定

我们有一个 CurtainListActivity 类,在该类中我们将打开 xml 文件并读取行。但我们需要一个类,它可以从一个 xml 节点创建单行,将其放入 list_row,然后将该行传递给主布局。因此,我们基本上正在寻找类似于辅助视图的东西。在 Android 中,多个独立视图可以分组在 ViewGroup 内部。每个视图都可以单独建模以具有独立数据。

让我们创建一个名为 BinderData 的类,它应该为我们执行此任务。使该类扩展 android.widget.BaseAdapter。现在该类将能够返回一个自定义视图。为了将 xml 节点数据与类数据绑定,您可以在类中定义字符串变量。

static final String KEY_TAG = "Curtain"; // parent node
    static final String KEY_CODE = "ItemNo";
    static final String KEY_NAME = "ItemName";
    static final String KEY_COMPOSITION = "Composition";
    static final String KEY_Utility = "Utility";
     static final String KEY_ICON = "ImagePath";
     static final String KEY_GENERE = "Genere";
     static final String KEY_COLOR = "Color";

现在我们还需要一个包含两对字符串的列表(还记得我们想把所有细节都放在两个文本框里吗?)。我们需要一个 ImageView 来拉取图像,一个 ViewHolder 来保存动态视图。

LayoutInflater inflater;
    ImageView thumb_image;
    List<HashMap<String,String>> curtainDataCollection;
    ViewHolder holder;

请记住,BinderData 将从 CurtainListActivity 中使用以获取 View(一行)。因此,BinderData 必须有一个构造函数,该构造函数接受 activity 实例,将文本数据与 curtainDataCollection 映射,并膨胀新视图。

public BinderData() {
        // TODO Auto-generated constructor stub
    }
    
    public BinderData(Activity act, List<HashMap<String,String>> map) {
        
        this.curtainDataCollection = map;
        
        inflater = (LayoutInflater) act
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    

 

其他重要方法是:getCount,它应返回元素(行)的数量;getItem,它必须返回给定行号的行项;以及 getItemId,它应返回视图位置的 itemId。

    public int getCount() {
        // TODO Auto-generated method stub
//        return idlist.size();
        return curtainDataCollection.size();
    }

    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return 0;
    }

BinderData 类最重要的一部分是 getView 方法。此方法负责填充准备好的视图。

public View getView(int position, View convertView, ViewGroup parent) 
    {

        View vi=convertView;
        if(convertView==null){
         Log.i("Tracking view", "-----Scrolling---------");
        vi = inflater.inflate(R.layout.list_row, null);
          holder = new ViewHolder();
         
          holder.tvName = (TextView)vi.findViewById(R.id.tvName); // city name
          holder.tvCode = (TextView)vi.findViewById(R.id.tvCode); // city weather overview
          holder.tvcurtainImage =(ImageView)vi.findViewById(R.id.list_image); // thumb image
     
          vi.setTag(holder);
        }
        else{
            
            holder = (ViewHolder)vi.getTag();
        }

          // Setting all values in listview
         try{ 
        String codeName=curtainDataCollection.get(position).get(KEY_CODE)+""+curtainDataCollection.get(position).get(KEY_NAME);
        String genereComUtility=curtainDataCollection.get(position).get(KEY_GENERE)+", "+curtainDataCollection.get(position).get(KEY_COMPOSITION)+", "+curtainDataCollection.get(position).get(KEY_Utility)+", "+curtainDataCollection.get(position).get(KEY_COLOR);
        holder.tvName.setText(codeName);
          holder.tvCode.setText(genereComUtility);
          //holder.tvTemperature.setText(curtainDataCollection.get(position).get(KEY_TEMP_C));
          Log.i("in Binder","before uri");
          //Setting an image
         
          String identifier=curtainDataCollection.get(position).get(KEY_ICON);
          identifier=identifier.split(".jpg")[0];
          String uri = "drawable/"+ identifier;
          int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(uri, null, vi.getContext().getApplicationContext().getPackageName());
          //int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(identifier, null, vi.getContext().getApplicationContext().getPackageName());
          Log.i("in Binder resource="," "+imageResource+"----"+identifier+"===="+vi.getContext().getApplicationContext().getPackageName());
         
          Drawable image = vi.getContext().getResources().getDrawable(imageResource);
         
         holder.tvcurtainImage.setImageDrawable(image);
          //holder.tvcurtainImage.setImageURI(Uri.parse(uri));
          Log.i("in Binder URI",uri); 
         }catch(Exception ex)
         {
             
         }
          return vi;

    }
    
    /*
     * 
     * */
    static class ViewHolder{
        
        TextView tvName;
        TextView tvCode;
        
        ImageView tvcurtainImage;
    }

在这一行

 vi = inflater.inflate(R.layout.list_row, null);

BinderData 定义它将呈现的视图不过是 list_row 布局的一个实例。因此,它必须以编程方式定义 list_row 布局的所有元素。为此,它定义了一个静态类 ViewHolder 并将其字段映射到相应的布局字段。

holder.tvName = (TextView)vi.findViewById(R.id.tvName); // city name
          holder.tvCode = (TextView)vi.findViewById(R.id.tvCode); // city weather overview
          holder.tvcurtainImage =(ImageView)vi.findViewById(R.id.list_image); // thumb image

它创建了两个字符串:一个名为 CodeName,其中包含窗帘的名称,另一个字符串包含所有其他信息。这些信息通过 KEY 代码从 curtainDataCollection 中获取。

  try{ 
        String codeName=curtainDataCollection.get(position).get(KEY_CODE)+""+curtainDataCollection.get(position).get(KEY_NAME);
        String genereComUtility=curtainDataCollection.get(position).get(KEY_GENERE)+", "+curtainDataCollection.get(position).get(KEY_COMPOSITION)+", "+curtainDataCollection.get(position).get(KEY_Utility)+", "+curtainDataCollection.get(position).get(KEY_COLOR);
        holder.tvName.setText(codeName);
          holder.tvCode.setText(genereComUtility);

设置 testData 后,是时候获取并创建图像了。回想一下,R.java 有一个名为 drawable 的 final static 类。资源名称作为变量存在。所以 photo0.jpg 只是 drawable 内部的 photo0 资源。因此,BinderData 必须提取路径,分割路径,分离 .jpg,并提取字符串的第一部分(即 photo0)。现在它应该在所有资源中搜索此资源,最后使用 ID 获取特定图像。

 String identifier=curtainDataCollection.get(position).get(KEY_ICON);
          identifier=identifier.split(".jpg")[0];
          String uri = "drawable/"+ identifier;
          int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(uri, null, vi.getContext().getApplicationContext().getPackageName());

一旦 ImageResource 号码可用,从资源创建一个 Drawable 对象,并将该对象分配给 ImageView 实例。Drawable 是一种 Android 中用于绘图的“脏”内存。在渲染之前,您可以将任何对象(如画布、位图)绘制到 drawable 中,然后将 drawable 渲染到主显示区域。

 Drawable image = vi.getContext().getResources().getDrawable(imageResource);
   holder.tvcurtainImage.setImageDrawable(image);

现在我们的 BinderData 类已完全准备好供 CurtainListActivity.java 使用。请看完整的类

BinderData.java

package com.grasshoppernetwork.myimagelist;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class BinderData extends BaseAdapter {

    // XML node keys
    static final String KEY_TAG = "Curtain"; // parent node
    static final String KEY_CODE = "ItemNo";
    static final String KEY_NAME = "ItemName";
    static final String KEY_COMPOSITION = "Composition";
    static final String KEY_Utility = "Utility";
     static final String KEY_ICON = "ImagePath";
     static final String KEY_GENERE = "Genere";
     static final String KEY_COLOR = "Color";
    
    LayoutInflater inflater;
    ImageView thumb_image;
    List<HashMap<String,String>> curtainDataCollection;
    ViewHolder holder;
    public BinderData() {
        // TODO Auto-generated constructor stub
    }
    
    public BinderData(Activity act, List<HashMap<String,String>> map) {
        
        this.curtainDataCollection = map;
        
        inflater = (LayoutInflater) act
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    

    public int getCount() {
        // TODO Auto-generated method stub
//        return idlist.size();
        return curtainDataCollection.size();
    }

    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return 0;
    }

    public View getView(int position, View convertView, ViewGroup parent) 
    {

        View vi=convertView;
        if(convertView==null){
         Log.i("Tracking view", "-----Scrolling---------");
        vi = inflater.inflate(R.layout.list_row, null);
          holder = new ViewHolder();
         
          holder.tvName = (TextView)vi.findViewById(R.id.tvName); // city name
          holder.tvCode = (TextView)vi.findViewById(R.id.tvCode); // city weather overview
          holder.tvcurtainImage =(ImageView)vi.findViewById(R.id.list_image); // thumb image
     
          vi.setTag(holder);
        }
        else{
            
            holder = (ViewHolder)vi.getTag();
        }

          // Setting all values in listview
         try{ 
        String codeName=curtainDataCollection.get(position).get(KEY_CODE)+""+curtainDataCollection.get(position).get(KEY_NAME);
        String genereComUtility=curtainDataCollection.get(position).get(KEY_GENERE)+", "+curtainDataCollection.get(position).get(KEY_COMPOSITION)+", "+curtainDataCollection.get(position).get(KEY_Utility)+", "+curtainDataCollection.get(position).get(KEY_COLOR);
        holder.tvName.setText(codeName);
          holder.tvCode.setText(genereComUtility);
          //holder.tvTemperature.setText(curtainDataCollection.get(position).get(KEY_TEMP_C));
          Log.i("in Binder","before uri");
          //Setting an image
         
          String identifier=curtainDataCollection.get(position).get(KEY_ICON);
          identifier=identifier.split(".jpg")[0];
          String uri = "drawable/"+ identifier;
          int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(uri, null, vi.getContext().getApplicationContext().getPackageName());
          //int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(identifier, null, vi.getContext().getApplicationContext().getPackageName());
          Log.i("in Binder resource="," "+imageResource+"----"+identifier+"===="+vi.getContext().getApplicationContext().getPackageName());
         
          Drawable image = vi.getContext().getResources().getDrawable(imageResource);
         
         holder.tvcurtainImage.setImageDrawable(image);
          //holder.tvcurtainImage.setImageURI(Uri.parse(uri));
          Log.i("in Binder URI",uri); 
         }catch(Exception ex)
         {
             
         }
          return vi;

    }
    
    /*
     * 
     * */
    static class ViewHolder{
        
        TextView tvName;
        TextView tvCode;
        
        ImageView tvcurtainImage;
    }
    
}

现在让我们继续更新我们的 CurtainListActivity.java。但在我们这样做之前,我们必须记住,用户可以在主布局中选择一行,并且该行的特定信息应该在我们的第一个表单中可用,我们将在其中显示图像。由于有多个参数供此表单写入,并且调用 MainActivity 将读取,我们将创建一个名为 GlobalPlaceHolderClass 的类。我们将创建与我们的数据对应的公共静态字段并存储特定数据。

public class GlobalPlaceholder 
{
public static ArrayList<CurtainDbClass> database;
public static String selectedGenere="";
public static String selectedComposition="";
public static String selectedUtility="";
public static String selectedColor="";

}


在第 6 节中,如果您仔细观察了 CurtainListActivity 类中 onCreate 方法的修改,您一定会注意到调用了一个名为 PerformList 的方法。

为了更好的代码维护和可用性,我们将从 xml 文件读取、将每个数据与 BinderData 绑定并最终在 PerformList 方法中呈现视图的整个逻辑

XML 数据将包含元素。我们将维护一个列表,用于维护每个节点(窗帘)及其关联的子节点(元素数据)。我们还将保留第一组元素和节点,以便我们可以在最初将它们放入选定项目中。不这样做有时会导致应用程序崩溃。

Element firstNameElement = null;
    NodeList textNameList = null;
    
    NodeList iconList = null;
    Element firstIconElement = null;
    NodeList textIconList = null;
    
    
    
    NodeList genereList = null;
    Element firstGenereElement = null;
    NodeList textGenereList = null;

   
    NodeList compositionList = null;
    Element firstCompositionElement = null;
    NodeList textCompositionList = null;

   
    
    NodeList utilityList = null;
    Element firstUtiliElement = null;
    NodeList textUtilityList = null;

   
    
    NodeList colorList = null;
    Element firstColorElement = null;
    NodeList textColorList = null;


现在类属性已声明,是时候定义 PerformList 并理解其背后的逻辑了。

void PerformList(boolean updateDatabase)
    {
        try {
            
            
            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            Document doc = docBuilder.parse (getAssets().open("curtaindata.xml"));
            Log.i("doc opened","Doc opened");
            curtainDataCollection = new ArrayList<HashMap<String,String>>();
            
            // normalize text representation
            doc.getDocumentElement ().normalize ();
                        
            NodeList curtainList = doc.getElementsByTagName("Curtain");
            Log.i("size", ".."+curtainList.getLength());
            HashMap<String,String> map = null;
            //////////////////////////Declarations///////////////
            Element firstCurtainElement = null;
            //-------
            if(updateDatabase)
                 GlobalPlaceholder.database=new ArrayList<CurtainDbClass>();
          
            ///////////////////////////////////////////////
            for (int i = 0; i < curtainList.getLength(); i++) {
                 
                   map = new HashMap<String,String>(); 
                   
                   Node firstCurtainNode = curtainList.item(i);
                   
                    if(firstCurtainNode.getNodeType() == Node.ELEMENT_NODE)
                    {
                        try{

                        firstCurtainElement = (Element)firstCurtainNode;
                        //-------
                        codeList = firstCurtainElement.getElementsByTagName(KEY_CODE);
                        firstCodeElement = (Element)codeList.item(0);
                        textCodeList = firstCodeElement.getChildNodes();
                        //--id
                        map.put(KEY_CODE, ((Node)textCodeList.item(0)).getNodeValue().trim());

                        
                        //2.-------
                        /*
                        NodeList nameList = firstCurtainElement.getElementsByTagName(KEY_NAME);
                        Element firstNameElement = (Element)nameList.item(0);
                        NodeList textNameList = firstNameElement.getChildNodes();
                        //--city
                        String nm= ((Node)textNameList.item(0)).getNodeValue().trim();
                        if(nm==null)
                            nm="";
                            */
                        map.put(KEY_NAME," ");
                        
                        //3.-------
                        
                        //6.-------
                        iconList = firstCurtainElement.getElementsByTagName(KEY_ICON);
                        firstIconElement = (Element)iconList.item(0);
                         textIconList = firstIconElement.getChildNodes();
                        //--city
                        map.put(KEY_ICON, ((Node)textIconList.item(0)).getNodeValue().trim());
                        
                        
                        genereList = firstCurtainElement.getElementsByTagName(KEY_GENERE);
                        firstGenereElement = (Element)genereList.item(0);
                         textGenereList = firstGenereElement.getChildNodes();
                   
                        map.put(KEY_GENERE, ((Node)textGenereList.item(0)).getNodeValue().trim());
                       // Log.d("list_row", ((Node)textGenereList.item(0)).getNodeValue().trim());
                        compositionList = firstCurtainElement.getElementsByTagName(KEY_COMPOSITION);
                        firstCompositionElement = (Element)compositionList.item(0);
                        textCompositionList = firstCompositionElement.getChildNodes();
                   
                        map.put(KEY_COMPOSITION, ((Node)textCompositionList.item(0)).getNodeValue().trim());
                        
                        utilityList = firstCurtainElement.getElementsByTagName(KEY_Utility);
                         firstUtiliElement = (Element)utilityList.item(0);
                         textUtilityList = firstUtiliElement.getChildNodes();
                   
                        map.put(KEY_Utility, ((Node)textUtilityList.item(0)).getNodeValue().trim());
                        
                         colorList = firstCurtainElement.getElementsByTagName(KEY_COLOR);
                         firstColorElement = (Element)colorList.item(0);
                         textColorList = firstColorElement.getChildNodes();
                   
                        map.put(KEY_COLOR, ((Node)textColorList.item(0)).getNodeValue().trim());
                        //Add to the Arraylist
                        boolean flagInsert=true;
                       
                        if(GlobalPlaceholder.selectedColor.length()>2 && !GlobalPlaceholder.selectedColor.trim().equals(((Node)textColorList.item(0)).getNodeValue().trim()))
                        {
                            flagInsert=false;
                        }
                        if(GlobalPlaceholder.selectedGenere.length()>2 && !GlobalPlaceholder.selectedGenere.trim().equals(((Node)textGenereList.item(0)).getNodeValue().trim()))
                        {
                            flagInsert=false;
                        }
                        if(GlobalPlaceholder.selectedComposition.length()>2 && !GlobalPlaceholder.selectedComposition.trim().equals(((Node)textCompositionList.item(0)).getNodeValue().trim()))
                        {
                            flagInsert=false;
                        }
                        if(GlobalPlaceholder.selectedUtility.length()>2 && !GlobalPlaceholder.selectedUtility.trim().equals(((Node)textUtilityList.item(0)).getNodeValue().trim()))
                        {
                            flagInsert=false;
                        }
                        if(flagInsert)
                        {
                             Log.i("In Curtain View After Selection",GlobalPlaceholder.selectedColor+"-"+GlobalPlaceholder.selectedGenere+"-"+GlobalPlaceholder.selectedComposition+">>"+map.get(KEY_GENERE));
                        curtainDataCollection.add(map);
                        if(updateDatabase)
                        GlobalPlaceholder.database.add(new CurtainDbClass(textCodeList.item(0).getNodeValue().trim(), " ", textGenereList.item(0).getNodeValue().trim(), textCompositionList.item(0).getNodeValue().trim(), textUtilityList.item(0).getNodeValue().trim(), textColorList.item(0).getNodeValue().trim()));
                        }
                        }
                        catch(Exception ex)
                        {
                            Log.e("In list_row", ex.getMessage());
                            
                        }
                    
                    }        
            }
            
    
            BinderData bindingData = new BinderData(this,curtainDataCollection);

                        
            list = (ListView) findViewById(R.id.list);

            Log.i("BEFORE", "<<------------- Before SetAdapter-------------->>");

    try{
        Log.i("Data binding",bindingData.curtainDataCollection.get(0).toString());
            list.setAdapter(bindingData);
    }catch(Exception ex)
    {
        
    }

            Log.i("AFTER", "<<------------- After SetAdapter-------------->>");

            // Click event for single list row
            list.setOnItemClickListener(new OnItemClickListener() {

                public void onItemClick(AdapterView<?> parent, View view,
                        int position, long id) {

                    Intent i = new Intent();
                    //i.setClass(CurtainListActivity.this, SampleActivity.class);

                    // parameters
                    //i.putExtra("icon", String.valueOf(position + 1));
                    
                   
                    i.putExtra("name", curtainDataCollection.get(position).get(KEY_NAME));
                    i.putExtra("code", curtainDataCollection.get(position).get(KEY_CODE));
                    
                    i.putExtra("icon", curtainDataCollection.get(position).get(KEY_ICON));
                    if (getParent() == null) 
                    {
                        setResult(Activity.RESULT_OK, i);
                    } else 
                    {
                        getParent().setResult(Activity.RESULT_OK, i);
                    }
                    finish();
                    // start the sample activity
                    //startActivity(i);
                }
            });

        
        }
        
        catch (IOException ex) {
            Log.e("Error", ex.getMessage());
        }
        catch (Exception ex) {
            Log.e("Error", ex.getMessage());
        }

    }

首先,我们将使用 DocumentBuilderFactory 的实例打开 asset 文件夹中的 curtaindata.xml 文件。通过 DocumentBuilderFactory 解析 curtaindata.xml 创建一个 Document 类对象 doc。

DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            Document doc = docBuilder.parse (getAssets().open("curtaindata.xml"));

现在获取所有带有标签 <Curtain> 的 xml 节点

 doc.getDocumentElement ().normalize ();
                        
 NodeList curtainList = doc.getElementsByTagName("Curtain");

下一部分非常简单。循环遍历 curtainList 的每个元素,读取元素,将子元素放入哈希表。哈希表的每一行都是一个键值对。键是:KEY_NAME、KEY_CODE、KEY_GENERE、KEY_COMPOSITION、KEY_UTILITY、KEY_ICON。它们对应的值是当前 sml 节点的关联值。

与组合相关的条目是

 compositionList = firstCurtainElement.getElementsByTagName(KEY_COMPOSITION);
                        firstCompositionElement = (Element)compositionList.item(0);
                        textCompositionList = firstCompositionElement.getChildNodes();
                   
                        map.put(KEY_COMPOSITION, ((Node)textCompositionList.item(0)).getNodeValue().trim());

其余条目相似。我们现在需要更新 GlobalPlaceHolder 中的一组值。因此,检查它是否已经包含值。如果没有,则添加元素。当它已经包含元素时,不要插入任何其他行。

  if(GlobalPlaceholder.selectedColor.length()>2 && !GlobalPlaceholder.selectedColor.trim().equals(((Node)textColorList.item(0)).getNodeValue().trim()))
                        {
                            flagInsert=false;
                        }

if(updateDatabase)
                        GlobalPlaceholder.database.add(new CurtainDbClass(textCodeList.item(0).getNodeValue().trim(), " ", textGenereList.item(0).getNodeValue().trim(), textCompositionList.item(0).getNodeValue().trim(), textUtilityList.item(0).getNodeValue().trim(), textColorList.item(0).getNodeValue().trim()));
                        }

现在我们已经准备好数据行,创建并实例化 BinderData,它将为 XML 数据的一行创建一个 list_row。由于我们希望创建整个集合,只需将 curtainDataCollection 作为参数发送给 BinderData。

 BinderData bindingData = new BinderData(this,curtainDataCollection);

                        
            list = (ListView) findViewById(R.id.list);

            Log.i("BEFORE", "<<------------- Before SetAdapter-------------->>");

    try{
        Log.i("Data binding",bindingData.curtainDataCollection.get(0).toString());
            list.setAdapter(bindingData);
    }catch(Exception ex)
    {
         }

我们现在已经准备好我们的表单。CurtainListActivity 将返回与所选行关联的名称和代码。但是我们有

startActivity(intent1);

在 MainActivity 中,它启动 CurtainListActivity,我们必须将 intent 启动方法更改为可以从实例获取结果的方法。因此,MainActivity 类的 onOptionsItemSelected 方法的更新代码变为

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        switch(id)
        {
         case R.id.menuOpenList:
                try{
                Intent intent1 = new Intent();
                intent1.setClass(this, CurtainListActivity.class);
                Log.i("In Menu Browse","In Menu Browse");
                // parameters
                startActivityForResult(intent1, FABRIC_SELECTION);
            //    startActivity(intent1);
                }catch(Exception ex)
                {
                    
                }
            break;
        }
        return super.onOptionsItemSelected(item);
    }

如果您正确遵循了步骤并已准备好所有内容,您现在可以测试您的应用程序。当您从主表单中选择“打开窗帘表单”菜单时,您将看到您的窗帘列表 nicely presented。

您可以在列表中上下滚动。当您选择任何项目时,您将返回到主表单。但是,我们尚未实现第一个窗口显示所选图标的逻辑。

让我们继续完成我们的任务。为了在运行时更改 ImageView 图像,我们需要一个成员变量映射到资源 ImageView。为此声明一个成员变量

ImageView imgView1=null;

在 onCreate 方法中,在调用 super 之后使用 findViewById 初始化此变量。

imgView1=(ImageView)findViewById(R.id.imMainForm);

您想要做的是编写一个处理程序,当控制从另一个为结果而启动的实例返回时,应该调用该处理程序。为此,您需要更新 OnActivityResult。

由于在您的工作中,您可能会使用多个 Intent,并且您需要从哪个 Intent 返回控制权,因此使用 Switch 控制语句总是明智的。请记住,我们使用 ID FABRIC_SELECTION 启动了 CurtainListActivity Intent。因此,OnActivityResult 中的 case ID 也将相同。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) 
    { 
        super.onActivityResult(requestCode, resultCode, imageReturnedIntent); 

        switch(requestCode)
        { 
        case FABRIC_SELECTION:
            try{
            if(resultCode==RESULT_OK)
            {
            Bundle res = imageReturnedIntent.getExtras();
            //ad.setMessage(res.getString("icon"));
        //    ad.show();
            try{
                 String identifier=res.getString("icon");
                 identifier=identifier.split(".jpg")[0];
                 String uri = "drawable/"+ identifier;
                 int imageResource = getApplicationContext().getResources().getIdentifier(uri, null, getApplicationContext().getPackageName());
                 //int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(identifier, null, vi.getContext().getApplicationContext().getPackageName());
                 
                Log.i("While Displaying selected Image resource=", " "+imageResource );
                 Drawable image = getResources().getDrawable(imageResource);
                imgView1.setImageDrawable(image);
           
                
            imgView1.invalidate();
            
            
            }catch(Exception ex)
            {
                
            }
            
            }
            }catch(Exception ex)
            {}
            break;
        }
        
           }

请记住,我们在 CurtainListActivity 中将图像路径名放在“icon”参数中。因此,我们从 Bundle res 对象中获取通过 getExtra() 方法获得的“icon”资源中的选定图像路径。现在我们获取资源编号并更新 ImageVie 的 drawable。

现在,当您运行应用程序并从列表中选择图像时,您将在第一个表单中看到该图像,如下图所示。

这里注意:如果您正在使用 FrameLayout,图像可能不会更新。您可以通过将 fragment_.xml 文件中的整个布局代码复制到 main_activity.xml 文件中来更改 FrameLayout。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.grasshoppernetwork.myimagelist.MainActivity$PlaceholderFragment" >

    <ImageView
        android:id="@+id/imMainForm"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="70dp"
        android:layout_marginTop="43dp"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>

8. 结论

本教程旨在教授 Android 中资源处理的基础知识,特别是菜单、布局、字符串和其他资源。我发现,简单地罗列不同控件或概念的使用方法,很少能教会与工作流程相关的细微问题。在本教程中,我在一个已发布的应用程序的背景下涵盖了这些概念。我希望本教程能帮助您理解这些概念,并鼓励您将其开发成一个已发布的应用程序。

© . All rights reserved.