Skip to content

第3章 移动应用开发基础

本章旨在为初学者提供一个系统、全面的学习路径,从基础到进阶,逐步深入Android应用开发的核心技术。读者将从零开始,逐步掌握Android应用开发的核心技术和最佳实践。 每一节都是一个独立的学习单元,但它们之间又紧密相连,共同构成了一个完整的Android应用开发知识体系。 希望本教程能够帮助读者在移动应用开发上打下基础。

本章包括以下几个部分:

3.1 安卓开发环境搭建

基础:搭建开发环境是学习Android开发的第一步,为后续章节提供开发工具和平台支持。

3.2 Activity

核心组件:Activity是Android应用的核心组件,负责显示用户界面。理解Activity的生命周期和管理是开发Android应用的基础。

3.3 Intent

组件通信:Intent是Activity、Service和BroadcastReceiver之间通信的桥梁,通过Intent可以实现组件之间的跳转和数据传递。

3.4 Layout

用户界面设计:布局是用户界面的基础,通过学习不同的布局类型,可以设计出美观且功能强大的用户界面。

3.5 Listener

事件处理:Listener用于处理用户交互事件,如点击、触摸等。通过Listener可以实现动态的用户交互,增强应用的交互性。

3.6 Fragment

模块化开发:Fragment是Activity的扩展,用于构建模块化的用户界面。通过Fragment可以实现更加灵活和动态的界面布局。

3.7 Tab和Navigation

导航设计:Tab和Navigation是现代Android应用中常见的导航方式。通过学习Tab和Navigation,可以优化应用的结构和用户体验。

3.8 RecyclerView

数据展示:RecyclerView是高效展示大量数据的组件,通过学习RecyclerView可以实现高性能的数据展示和动态更新。

3.9 网络相关功能

网络交互:网络功能是现代应用的重要组成部分。通过学习Socket编程和okHttp,可以实现应用与外部世界的网络通信。

3.10 保存和恢复数据

数据保存与恢复:保存和恢复数据是应用开发中的关键环节。通过学习数据持久化技术,如文件存储、SharedPreferences和数据库操作,可以实现用户数据的稳定保存和高效恢复,确保应用在不同场景下能够持续提供一致的用户体验。

除非特别声明,本书中的移动应用开发主要是在Android(安卓)操作系统上的移动应用,开发过程主要基于Android Studio软件,使用Java语言进行,这也算是一种最原始的移动应用开发的方式。以此为基础,可以讲清楚很多移动应用开发中的基本概念,其他的平台可以以此类推。

3.1 安卓开发环境搭建

在开始Android应用开发之前,搭建一个稳定且高效的开发环境至关重要。本章将详细介绍如何安装和配置Android Studio、设置Java开发环境,以及如何使用模拟器和真机进行调试。这是整个学习过程的基础,为后续章节的学习打下坚实的基础。

3.1.1 Android Studio的下载安装

Android Studio官方下载地址为https://developer.android.com/studio?hl=zh-cn,读者们可以自行下载安装。值得注意的是在用Android Studio创建项目后可能遇到gradle sync failed错误。解决这个问题的方法包括Gradle换源、配置代理等方式,读者可以自行搜索相关的教程。

3.1.2 快速上手

打开Android Studio软件,首先新建一个空项目,选择Empty Activity(Kotlin)或Empty Views Activity(Java),就可以创建一个空白的应用了,如下图所示。如果你有相关的编程软件使用经验,例如visual studio等,这一步应该就非常熟悉了。本书也是假设读者有相关的编程基础。因此如何使用Java语言,如何使用基本的软件,在本书中就不过多介绍,感兴趣的读者可以去阅读相关材料。

图3-1 Android Studio创建新项目

3.2 Activity

Activity是Android应用的核心组件之一,它代表了用户界面的一个屏幕。理解Activity是掌握Android应用架构的关键。 本章将深入讲解Activity的生命周期、创建和管理Activity的方法,以及如何通过Activity实现用户交互。在基于Android的移动应用开发中,Activity是一个核心组件,这个单词还不好直接翻译,通过英文单词理解它的意思还更加直接,从形式上看,它表示应用程序的一个单独屏幕内容。例如下图所示,笔记、计算器和地图等都是一个Activity的表现形式。每个Activity通常包含一个用户界面,用户可以在其中与应用程序进行交互,这也是为什么被称为Activity的原因。Activity之间可以跳转,就跟我们平时使用应用一样,例如,一个邮件应用程序可能有一个显示收件箱的Activity和另一个撰写新邮件的Activity,两个Activity之间还可以通过点击某些按钮跳转。

图3-2 Activity的表现形式

Activity的主要职责包括:(1)显示用户界面,每个Activity都负责绘制用户界面,它通常由XML布局文件定义;(2)响应用户操作,Activity处理用户的输入并与其他组件(如Service、BroadcastReceiver)进行交互;(3)管理应用程序的生命周期:Activity管理其生命周期状态,从创建到销毁,这一点对于资源管理尤为重要。 从粗粒度的角度来说,一个应用就是一系列Activity的组合,APP=Activity+Activity+...,因此理解了Activity对于理解移动应用开发非常重要。

3.2.1 Activity的生命周期

Activity的生命周期由一系列方法管理,这些方法在Activity的状态发生变化时调用。理解Activity的生命周期对于编写高效、稳定的应用程序至关重要。下面是Activity生命周期的主要阶段:

(1)创建(onCreate),onCreate()方法是Activity的入口点。当Activity第一次创建时调用,通常在这里进行初始化工作,如设置用户界面(通过setContentView方法)和绑定数据。

(2)启动(onStart),onStart()方法在Activity变得对用户可见时调用。此时Activity已经创建,但还没有进入前台。

(3)恢复(onResume),onResume()方法在Activity开始与用户交互之前调用。此时,Activity位于栈顶,并且是前台Activity。

(4)暂停(onPause),onPause()方法在系统即将启动另一个Activity之前调用。这个方法用于保存数据和释放资源,因为Activity可能很快会被销毁。

(5)停止(onStop),onStop()方法在Activity不再对用户可见时调用。此时,Activity可能会被销毁或重新启动。

(6)销毁(onDestroy),onDestroy()方法在Activity被销毁之前调用。这个方法用于执行清理工作,比如释放资源。

(7)重新启动(onRestart),onRestart()方法在Activity从停止状态重新启动时调用。此方法用于重新初始化在onStop()中释放的资源。

图3-3 Activity的什么周期

上图表达了Activity生命周期之间的关系。当用户启动一个Activity时,方法的调用顺序如下:onCreate() -> onStart() -> onResume()。当用户离开一个Activity时,方法的调用顺序如下:onPause() -> onStop() -> onDestroy()。了解这些生命周期方法以及它们的调用顺序,有助于开发者在适当的时间点执行适当的操作,从而提高应用的性能和用户体验。

3.2.2 Activity的跳转

在Android开发中,Activity之间的跳转是实现不同界面之间交互的关键机制。Activity之间通过Intent进行跳转,Intent不仅可以指定目标Activity,还可以携带额外的数据,从而实现Activity之间的信息传递。

基本跳转

当需要从一个Activity跳转到另一个Activity时,可以使用以下代码:

Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);

在这里,this表示当前的ActivityTargetActivity.class表示目标Activity的类名。Intent对象用于描述跳转的意图,startActivity(intent)方法则触发跳转动作。

带参数跳转

Activity之间不仅可以进行跳转,还可以通过Intent传递数据。例如,如果需要将一些数据从当前Activity传递到目标Activity,可以使用putExtra方法:

Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("key", value); // 将数据以键值对的形式添加到Intent中
startActivity(intent);

在目标Activity中,可以通过getIntent()方法获取Intent对象,并通过getStringExtragetIntExtra等方法获取传递的数据:

Intent intent = getIntent();
String value = intent.getStringExtra("key"); // 根据键名获取传递的值

跳转关系的可视化

上图展示了多个Activity之间的跳转关系。在图中,每个节点代表一个Activity,箭头表示跳转的方向。通过这种方式,可以清晰地看到不同Activity之间的交互流程。例如,从MainActivity跳转到DetailActivity,再从DetailActivity跳转到SettingActivity,这种跳转关系可以通过Intent来实现,并且可以在跳转过程中传递必要的数据。

跳转的高级用法

除了基本的跳转和带参数跳转,Intent还支持更多高级功能。例如,可以使用startActivityForResult方法启动一个Activity,并等待其返回结果。这在需要从目标Activity获取数据时非常有用,例如在拍照或选择图片后返回结果。

Intent intent = new Intent(this, TargetActivity.class);
startActivityForResult(intent, REQUEST_CODE); // REQUEST_CODE用于标识请求

在目标Activity中,可以通过调用setResult方法返回数据:

Intent returnIntent = new Intent();
returnIntent.putExtra("result", resultValue);
setResult(Activity.RESULT_OK, returnIntent);
finish();

在原Activity中,可以通过onActivityResult方法接收返回的数据:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        String resultValue = data.getStringExtra("result");
        // 处理返回的数据
    }
}

通过这些方式,Activity之间的跳转和数据传递变得更加灵活和强大,为Android应用的开发提供了丰富的交互方式。

图3-4

可以看出来,你平时在使用App的时候看到界面一个个的跳来跳去,大概率就是使用了Intent进行了Activity跳转。因此,设计出来一个一个的Activity之后,我们就能够组合成一个我们想要的应用了,核心问题就是怎么将应用分解为一个个的Activity,以及如何实现每一个Activity。

3.2.3 大模型生成Activity

目标

假设作为一个初学者,我们理解了基本概念后,要写一个简单的应用,实现以下功能:

  • 生成两个Activity,分别对应动态页面和详情页面
  • 在动态页面中显示任意两条动态,内容为纯文字
  • 在用户点击一条动态时,会启动动态详情页面的Activity,在动态详情页面中显示该动态的文字
  • 保存及恢复数据

实现过程

我们先在Android中创建一个空项目,选择Empty Views Activity,项目名称任意,其他保持默认。我们再创建添加一个新的Activity,名为DetailActivity,然后我们根据需求询问大模型。

对于复杂的APP,我们需要将过程进行解构: - 实现两个Activity之间的跳转 - 实现两个Activity之间的信息传递 - 实现第二个Activity到第一个Activity的返回 - 实现数据的保存和恢复

交互过程1:Activity之间的跳转

用户:

请在第一个MainActivity中添加显示两个纯文本动态,并且点击动态的时候跳转到DetailActivity

大模型:

在第一个MainActivity中添加显示两个纯文本动态,并在点击动态时跳转到DetailActivity,可以按照以下步骤进行:

  1. 在activity_main.xml布局文件中添加两个TextView控件,用于显示动态内容。例如:
<LinearLayout  
android:layout_width="match_parent"  
android:layout_height="match_parent"  
android:orientation="vertical">  

<TextView  
android:id="@+id/textView_dynamic1"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content" />  

<TextView  
android:id="@+id/textView_dynamic2"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content" />  
</LinearLayout>  
  1. 在MainActivity的Java代码中,为两个TextView控件分别设置动态内容。例如:
public class MainActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 设置第一个动态内容  
        TextView textViewDynamic1 = findViewById(R.id.textView_dynamic1);  
        textViewDynamic1.setText("这是第一条动态内容");  

        // 设置第二个动态内容  
        TextView textViewDynamic2 = findViewById(R.id.textView_dynamic2);  
        textViewDynamic2.setText("这是第二条动态内容");  
    }  
}  
  1. 在MainActivity中添加一个方法,用于跳转到DetailActivity。例如:
public void startDetailActivity(View view) {  
   Intentintent = new Intent(this, DetailActivity.class);  
    startActivity(intent);  
}  
  1. 在MainActivity的布局文件中,为每个TextView控件添加一个点击事件监听器,并在其中调用startDetailActivity()方法。例如:
<LinearLayout  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical">  

    <TextView  
        android:id="@+id/textView_dynamic1"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:clickable="true"  
        android:onClick="startDetailActivity"/>  

    <TextView  
        android:id="@+id/textView_dynamic2"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:clickable="true"  
        android:onClick="startDetailActivity"/>  
</LinearLayout>  

操作:

根据大模型的回答,我们知道了如何在MainActivity中添加两条动态并在点击时跳转到DetailActivity,我们将生成的XML代码复制到activity_detail.xml文件中,并把Java代码复制到MainActivity.java中。

我们首先保留原来XML文件中的第一行,这个是XML文件的类型声明。我们将生成的代码覆盖下面代码,注意这里无法直接使用生成的代码,需要根据IDE的报错进行修复,对于这段代码,我们需要添加xmlns:android值。

我们再看Java代码,我们同样保留源代码的前面几行,这几行的作用是声明类所在包以及导入相关的包。我们将生成的代码覆盖下面,这是Android Studio会提示需要导入一些相关的包,这是我们可以按照IDE的提示导入所需的包,现在的Android Studio也很智能了,它能够帮忙识别出来很多修改的地方,并提出正确的修改建议了,很多时候,我们使用Android Studio提出的建议就能解决很多问题。最终修复的代码如下所示:
```Java
package com.example.xxx;  

import androidx.appcompat.app.AppCompatActivity;  
import android.content.Intent;  
import android.os.Bundle;  
import android.view.View;  
import android.widget.TextView;  

public class MainActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 设置第一个动态内容  
        TextView textViewDynamic1 = findViewById(R.id.textView_dynamic1);  
        textViewDynamic1.setText("这是第一条动态内容");  

        // 设置第二个动态内容  
        TextView textViewDynamic2 = findViewById(R.id.textView_dynamic2);  
        textViewDynamic2.setText("这是第二条动态内容");  
    }  

    public void startDetailActivity(View view) {  
       intent = new Intent(this, DetailActivity.class);  
        startActivity(intent);  
    }  
}  

现在,我们可以尝试编译运行这个APP。可以看到,现在已经实现了点击一条动态跳转到第二个Activity的功能。

我们现在需要实现第二个Activity显示动态内容的功能,我们继续询问大模型。

交互过程2:跳转显示

用户:

很好,现在进一步添加以下功能:跳转到DetailActivity的时候显示点击到的MainActivity中的动态文字。


大模型:

要在跳转到DetailActivity时显示点击到的MainActivity中的动态文字,你需要做的是在跳转的时候传递这个信息。这通常通过Intent实现。以下是如何实现这一功能的步骤:

1.在MainActivity中处理点击事件

首先,你需要在MainActivity的布局文件中为每个可点击的元素(例如TextView)设置一个android:onClick属性,并为其指定一个方法(例如onTextViewClick)。

<TextView  
    android:id="@+id/textView_dynamic1"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:text="动态内容1"  
    android:clickable="true"  
    android:onClick="onTextViewClick"/>  

然后,在MainActivity的Java代码中实现这个方法,并创建一个新的Intent来启动DetailActivity,同时将动态文字作为Extra数据传递给这个Intent。

public void onTextViewClick(View view) {  
    // 获取要传递的动态文字  
    String dynamicText = ((TextView) view).getText().toString();  

    // 创建Intent并添加Extra数据  
   intent = new Intent(this, DetailActivity.class);  
    intent.putExtra("dynamic_text", dynamicText);  

    // 启动DetailActivity  
    startActivity(intent);  
}  

2.在DetailActivity中接收并显示动态文字

在DetailActivity的onCreate方法中,你可以获取传递过来的动态文字,并显示在相应的UI元素上。

public class DetailActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_detail);  

        // 获取传递过来的动态文字  
        String dynamicText = getIntent().getStringExtra("dynamic_text");  
        if (dynamicText != null) { // 确保动态文字有效  
            // 显示动态文字(这里只是一个示例,实际显示方式可能会有所不同)  
            TextView textViewContent = findViewById(R.id.textView_content);  
            textViewContent.setText(dynamicText);  
        } else { // 如果没有传递动态文字,则显示默认内容或提示错误信息(这里只是一个示例)  
            TextView textViewContent = findViewById(R.id.textView_content);  
            textViewContent.setText("无法找到动态内容"); // 这里只是一个示例,实际内容可能会有所不同  
        }  
    }  
}  

通过以上步骤,你可以在跳转到DetailActivity时显示点击到的MainActivity中的动态文字。记得根据你的具体需求调整代码。


操作:

大模型告诉我们需要通过Intent传递数据,但是它创建了新的方法,这是没有必要的,我们直接将相应代码复制到startDetailActivity方法内,修改得到的Java代码如下所示:

package com.example.demo3;  

import androidx.appcompat.app.AppCompatActivity;  

import android.content.Intent;  
import android.os.Bundle;  
import android.view.View;  
import android.widget.TextView;  

public class MainActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 设置第一个动态内容  
        TextView textViewDynamic1 = findViewById(R.id.textView_dynamic1);  
        textViewDynamic1.setText("这是第一条动态内容");  

        // 设置第二个动态内容  
        TextView textViewDynamic2 = findViewById(R.id.textView_dynamic2);  
        textViewDynamic2.setText("这是第二条动态内容");  
    }  

    public void startDetailActivity(View view) {  
        // 获取要传递的动态文字  
        String dynamicText = ((TextView) view).getText().toString();  

        // 创建Intent并添加Extra数据  
        intent = new Intent(this, DetailActivity.class);  
        intent.putExtra("dynamic_text", dynamicText);  

        // 启动DetailActivity  
        startActivity(intent);  
    }  
}  

然后修改DetailActivity的代码,如下所示:

package com.example.demo3;  

import androidx.appcompat.app.AppCompatActivity;  

import android.os.Bundle;  
import android.widget.TextView;  

public class DetailActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_detail);  

        // 获取传递过来的动态文字    
        String dynamicText = getIntent().getStringExtra("dynamic_text");  
        if (dynamicText != null) { // 确保动态文字有效    
            // 显示动态文字(这里只是一个示例,实际显示方式可能会有所不同)    
            TextView textViewContent = findViewById(R.id.textView_content);  
            textViewContent.setText(dynamicText);  
        } else { // 如果没有传递动态文字,则显示默认内容或提示错误信息(这里只是一个示例)    
            TextView textViewContent = findViewById(R.id.textView_content);  
            textViewContent.setText("无法找到动态内容"); // 这里只是一个示例,实际内容可能会有所不同    
        }  
    }  
} 

我们发现这个程序会报错,大模型没有提供布局文件activity_detail.xml,我们继续询问大模型。


交互过程3:补充缺失文件

用户:

此时DetailActivity的布局文件应该是怎么样的?


大模型:

要实现这个功能,你需要创建一个DetailActivity的布局文件,这个布局文件应该包含一个TextView来显示动态文字。

以下是一个简单的DetailActivity布局文件示例:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical">  

    <TextView  
        android:id="@+id/textView_content"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:textSize="20sp" />  
</LinearLayout> 

在这个布局文件中,我们定义了一个TextView,它的id为textView_content,用于显示动态文字。你可以根据需要调整布局和样式。


操作:

我们将生成的代码复制到相应文件中,这里还是得注意链接Java类,即xmlns:tools和tools:context,这是文件格式的要求,修改后的代码如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<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"  
    tools:context=".DetailActivity">  

    <TextView  
        android:id="@+id/textView_content"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:textSize="20sp" />  
</LinearLayout> 

再次编译运行程序,可以看见已经实现点击动态后在打开的第二个界面中显示动态内容。

现在我们想在第二个Activity中添加返回上一级功能,我们继续询问大模型。


交互过程4:返回功能

用户:

如何在打开的Activity左上角显示返回按钮?


大模型:

要在打开的Activity左上角显示返回按钮,你可以在布局文件中添加一个返回按钮,或者在代码中动态添加。以下是在布局文件中添加返回按钮的示例:

1.在你的布局文件中,添加一个Button元素作为返回按钮:

<Button  
    android:id="@+id/button_back"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:text="返回" />  

2.在你的布局文件中,添加一个Button元素作为返回按钮:

Button backButton = findViewById(R.id.button_back);  
backButton.setOnClickListener(new View.OnClickListener() {  
    @Override  
    public void onClick(View v) {  
        onBackPressed(); // 调用onBackPressed()方法实现返回功能  
    }  
}); 

这样,当用户点击返回按钮时,就会调用onBackPressed()方法,实现返回上一个Activity的功能。你可以根据需要自定义按钮的样式和文本。


操作:

我们分别在布局文件中添加按钮以及在Java文件中添加代码,修改后的XML文件如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<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"  
    tools:context=".DetailActivity">  

    <Button  
        android:id="@+id/button_back"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="返回" />  

    <TextView  
        android:id="@+id/textView_content"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:textSize="20sp" />  
</LinearLayout>  

修改后的Java文件如下所示:

package com.example.demo3;  

import androidx.appcompat.app.AppCompatActivity;  

import android.os.Bundle;  
import android.view.View;  
import android.widget.Button;  
import android.widget.TextView;  

public class DetailActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_detail);  

        Button backButton = findViewById(R.id.button_back);  
        backButton.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                onBackPressed(); // 调用onBackPressed()方法实现返回功能  
            }  
        });  

        // 获取传递过来的动态文字  
        String dynamicText = getIntent().getStringExtra("dynamic_text");  
        if (dynamicText != null) { // 确保动态文字有效  
            // 显示动态文字(这里只是一个示例,实际显示方式可能会有所不同)  
            TextView textViewContent = findViewById(R.id.textView_content);  
            textViewContent.setText(dynamicText);  
        } else { // 如果没有传递动态文字,则显示默认内容或提示错误信息(这里只是一个示例)  
            TextView textViewContent = findViewById(R.id.textView_content);  
            textViewContent.setText("无法找到动态内容"); // 这里只是一个示例,实际内容可能会有所不同  
        }  
    }  
}  

再次编译运行程序,可以看到第二个界面中出现了“返回”按钮,可以返回第一个界面。 在此,我们实现了两个Activity的跳转与数据传递。当然,读者还可以思考,能否实现其他的返回形式,比如可以在整个Activity的左上角添加一个返回按钮,就像很多应用里面展示的一样。


3.3 Intent

回过头来,我们再看看Intent。Intent是Android应用中用于组件之间通信的机制,是实现Android应用功能的关键纽带。通过Intent,Activity、Service和BroadcastReceiver之间可以相互协作。Activity、Service和BroadcastReceiver是Android系统提供的几个重要组件,换句话说就是Android系统提供的几个实现应用的基础功能。本章将介绍Intent的基本概念、显式Intent与隐式Intent的使用,以及如何通过Intent传递数据。

3.3.1 Intent的使用场景

前面我们已经看到了Intent的使用基本方法,Intent是Android系统中一个用于传递消息的对象,事实上Intent在操作系统中的作用就跟它的名字一下,是操作系统通信的机制,告诉操作系统相关“意图”,然后操作系统执行对应的操作。它可以在应用内部或应用之间传递数据和触发动作。Intent可以用于启动活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)等组件。

Intent可以分为两类:(1)显式Intent(Explicit Intent),显式指定要启动的组件(如某个具体的Activity或Service)。一般用于应用内部的组件之间进行通信;(2)隐式Intent(Implicit Intent),没有指定具体的组件,而是通过指定动作、数据等信息,让系统选择符合条件的组件来处理。常用于调用系统的功能或与其他应用交互。

构建Intent可以包含以下内容:(1)组件名称,由名称定义的应用组件,没有即隐式,例如:com.example.ExampleActivity;(2)操作,指定要执行的通用操作的字符串,如ACTION_VIEW:可向用户显示信息(使用图库应用查看的照片;或者要使用地图应用查看的地址),ACTION_SEND:可发送或者共享数据(电子邮件应用或社交共享应用);(3)数据,引用待操作数据和/或该数据URI,提供的数据类型通常由intent的操作决定;(4)类别,应处理Intent的组件类型;(5)Extra:完成请求操作所需的附加信息的键值对,如EXTRA_EMAIL:指定目标收件人;(6)标志:元数据,标志系统如何启动和处理Activity

隐式Intent与系统的交互包括以下过程:(1)使用隐式Intent时,Android 系统通过将Intent的内容与在设备上其他应用的清单文件中声明的Intent过滤器进行比较,从而找到要启动的相应组件;(2)如果Intent与Intent过滤器匹配,则系统将启动该组件,并向其传递Intent对象;(3)如果多个Intent过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。

Intent有以下的使用场景:

(1)启动新Activity:当用户点击按钮时,从一个Activity跳转到另一个Activity。

// 在MainActivity中  

IntentIntent= new Intent(MainActivity.this, DetailActivity.class);  
intent.putExtra("username", "JohnDoe");  
startActivity(intent);  

// 在DetailActivity中接收数据  
String username = getIntent().getStringExtra("username"); 

(2)启动服务(Service):当需要在后台执行一些任务时,比如下载时,可以通过Intent启动一个Service形式执行。

// 在MainActivity中  
IntentIntent= new Intent(MainActivity.this, MyService.class);  
startService(intent);  

// 在MyService类中  
public class MyService extends Service {  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        // 执行后台任务  
        return START_STICKY;  
    }  
}  

(3)发送广播(Broadcast):通过Intent还可以让操作系统来发送广播,当需要通知其他组件或应用某些事件时,可以发送广播。Broadcast也是Android操作系统提供的一个组件,用于操作系统内部进行广播通信。每一个应用中都可以使用Broadcast组件接收系统发送过来的消息。

// 发送广播  
IntentIntent= new Intent("com.example.CUSTOM_BROADCAST");  
sendBroadcast(intent);  

// 接收广播  
public class MyBroadcastReceiver extends BroadcastReceiver {  
    @Override  
    public void onReceive(Context context,Intentintent) {  
        // 处理广播事件  
    }  
}  

例如,在应用中,我们常见的分享文字,当用户希望将某段文字通过社交媒体、邮件等方式分享出去时,可以使用隐式Intent。

// 要分享的文字内容  
String textToShare = "这是我要分享的文字内容";  

// 创建一个隐式Intent  
Intent shareIntent = new Intent();  
shareIntent.setAction(Intent.ACTION_SEND);  
shareIntent.putExtra(Intent.EXTRA_TEXT, textToShare);  
shareIntent.setType("text/plain");  

// 启动分享的Activity  
startActivity(Intent.createChooser(shareIntent, "选择分享方式")); 

分享图片的过程与分享文字类似,只需将Intent的类型设置为图片类型,并附加图片的URI。注意在这里面,我们传递消息给其他Activity的时候,也附加上了对应的文字或者图片信息,这样在新启动的Activity中就能够获取到要分享的文字或者图片。

// 图片的URI  
Uri imageUri = Uri.parse("android.resource://com.example.myapp/" + R.drawable.image_to_share);  

// 创建一个隐式Intent  
Intent shareIntent = new Intent();  
shareIntent.setAction(Intent.ACTION_SEND);  
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);  
shareIntent.setType("image/*");  

// 启动分享的Activity  
startActivity(Intent.createChooser(shareIntent, "选择分享方式")); 

3.3.2 大模型生成Intent代码的示例

目标:我们将要写一个简单的应用,用户输入一个URL,然后点击按钮使用浏览器打开这个链接。分析一下,如果我们理解了Intent和Android开发的理念,我们就知道这个过程其实就是在用户Activity和浏览器对应的这个Activity之间的跳转,同时跳转的过程还需要带上用户的URL。注意,浏览器的URL如何访问目前我们还不知道,读者可以先想想怎么确定这个Activity名字。

交互过程1:目标输入

用户:

请用Android studio和java帮我生成一个app,我可以输入一个URL,点击按钮跳转使用浏览器打开这个URL。

大模型:

当然,我可以帮你生成一个简单的Android应用,该应用允许用户输入一个URL,并点击按钮以在浏览器中打开该URL。以下是实现这个功能的步骤和代码示例。

1.设置新的Android Studio项目

首先,打开Android Studio并创建一个新的项目。选择“Empty Activity”作为初始活动。

2.设计界面

在activity_main.xml中,你需要添加一个EditText用于输入URL和一个Button用于触发跳转。XML布局可能看起来像这样:

<?xml version="1.0" encoding="utf-8"?>    
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    xmlns:app="http://schemas.android.com/apk/res-auto"    
    xmlns:tools="http://schemas.android.com/tools"    
    android:layout_width="match_parent"    
    android:layout_height="match_parent"    
    android:padding="16dp"    
    tools:context=".MainActivity">    

    <EditText    
        android:id="@+id/urlInput"    
        android:layout_width="match_parent"    
        android:layout_height="wrap_content"    
        android:hint="Enter URL"    
        android:inputType="textUri" />    

    <Button    
        android:id="@+id/openButton"    
        android:layout_width="wrap_content"    
        android:layout_height="wrap_content"    
        android:layout_below="@id/urlInput"    
        android:layout_centerHorizontal="true"    
        android:layout_marginTop="16dp"    
        android:text="Open URL" />    

</RelativeLayout>

3.编写Java代码

在MainActivity.java中,你需要编写代码来处理按钮点击事件,并启动浏览器打开输入的URL。

package com.example.yourappname;    

import android.content.Intent;    
import android.net.Uri;    
import android.os.Bundle;    
import android.view.View;    
import android.widget.Button;    
import android.widget.EditText;    

import androidx.appcompat.app.AppCompatActivity;    

public class MainActivity extends AppCompatActivity {    

    private EditText urlInput;    
    private Button openButton;    

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

        urlInput = findViewById(R.id.urlInput);    
        openButton = findViewById(R.id.openButton);    

        openButton.setOnClickListener(new View.OnClickListener() {    
            @Override    
            public void onClick(View v) {    
                String url = urlInput.getText().toString();    
                if (!url.startsWith("http://") && !url.startsWith("https://")) {    
                    url = "http://" + url;    
                }    
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));    
                startActivity(intent);    
            }    
        });    
    }    
}  

这段代码中,我们为按钮设置了一个点击监听器,当点击按钮时,它会获取EditText中的文本,创建一个意图(Intent)来打开URL,并使用startActivity()方法启动浏览器。

4.测试应用

最后,构建并运行你的应用。你应该能够在模拟器或真实设备上输入一个URL,并点击按钮在浏览器中打开它。请确保你有适当的网络权限,尽管对于使用浏览器打开URL来说,这通常不是必需的。如果你打算在应用内部处理网络请求,你可能需要在AndroidManifest.xml中添加网络权限。

<uses-permission android:name="android.permission.INTERNET" />  

这就是一个基本的示例,展示如何在Android Studio中使用Java创建一个简单的应用来打开浏览器并导航到用户输入的URL。


操作:

根据大模型的回答,我们了解了实现页面跳转的主要步骤:

1.我们新建Empty Activity,并将大模型提供的xml代码复制粘贴进activity_main.xml中,java代码复制粘贴进MainActivity.java,其中java代码

package com.example.yourappname;  

需要将"youappname"修改为你的文件名。也可以选择不复制粘贴这一行,直接保留Android studio生成的程序mainActivity中原有的这一行。

编译运行,可以得到一个页面中包含一个button以及一个可以输入URL的EditText,如下图所示。在EditText中输入URL,点击按钮,可以观察到页面跳转至浏览器并打开了输入的URL。

3-6

3.4 Layout

3.4.1 布局方式简介

在Android开发中,Layout类型用于定义界面布局的结构和样式。常见的Android Layout类型包括:(1)LinearLayout(线性布局),是一种简单的布局,它沿着水平或垂直方向排列子视图,可以通过orientation属性设置布局方向为水平或垂直。(2)RelativeLayout(相对布局),允许通过相对于其他视图的位置来放置子视图,子视图可以相对于父视图或其他子视图的位置进行定位;(3)ConstraintLayout(约束布局),是一种灵活的布局,允许通过约束将子视图相对于其他视图进行定位,可以通过拖动边界、设置边距和创建水平/垂直约束来定义布局;(4)FrameLayout(帧布局),允许子视图堆叠在一起,并且每个子视图都覆盖在前一个视图的上面,常用于在同一区域显示单个子视图,如Fragment或者加载动画;(5)GridLayout(网格布局),GridLayout将子视图放置在网格中,可以通过行和列的方式来组织视图,可以灵活地定义网格的行数、列数以及每个单元格的大小;(6)TableLayout(表格布局),允许以表格的形式排列子视图,可以定义多行和多列,并且每个单元格可以放置一个视图;(7)CoordinatorLayout(协调布局),CoordinatorLayout是一个增强的FrameLayout,用于实现复杂的用户界面交互效果,通过CoordinatorLayout可以实现诸如滚动效果、悬浮按钮动画等复杂的UI行为。

3.4.2 Constrain Layout(约束布局)

ConstraintLayout(约束布局)是 Android 开发中最灵活和功能最强大的布局之一。它允许我们通过添加约束条件来定义视图之间的相对位置,而不是依赖于视图的层次结构。这种相对定位的方法使得设计复杂的界面变得更加简单和高效。这也是一般应用开发中最常用的布局模式,也是我最喜欢用的布局模式。 顾名思义,ConstraintLayout中有一些关键特性和用法:(1)相对定位,ConstraintLayout 允许我们将视图相对于父布局或其他视图进行定位。我们可以通过水平和垂直方向上的约束条件,指定视图与其它视图之间的位置关系。(2)约束条件:每个视图都可以与 ConstraintLayout 中的其他视图或者父布局的边界之间建立约束条件。这些约束条件可以是水平方向的对齐、垂直方向的对齐、边距、最小宽度、最小高度等。这个是在实际应用编写过程中非常常用的一个模式。(3)比例约束,ConstraintLayout 允许我们定义视图的尺寸为另一个视图尺寸的一个比例。这在确保界面在不同屏幕尺寸上保持一致性时非常有用。(4)可视性约束,我们可以根据条件动态更改视图的可见性,并在 ConstraintLayout 中定义这些视图的约束条件。这使得在用户交互或者其他条件下,动态地调整布局变得更加容易。(5)布局优化,ConstraintLayout 提供了优化布局层次结构的功能,可以减少布局层次的嵌套。这有助于提高应用的性能并简化布局的管理。

Android Studio中提供了一个可视化的布局编辑器,使得在 ConstraintLayout 中设计界面变得更加直观和方便。我们可以通过拖拽和调整视图来创建和编辑约束条件。不过我的建议就是大家在写应用的时候内心一定要有一个视图编辑器,写好约束条件后就可以在内心中知道布局会变成什么样子,这样对于可视化编辑是非常有用的。换句话说,大家心中现有一个要布局的样子,也要能够想到通过什么样的约束能够实现这样的布局效果,即使没有试图编辑器,也能实现约束布局的效果。

通过合理使用 ConstraintLayout,可以创建各种复杂的界面布局,约束布局还有一个好处就是由于是通过相对约束进行布局而不是绝对位置,能够使得这些布局在不同的屏幕尺寸和设备上都能够正常显示和运行。

3.4.3 编辑器设计布局示例

(1)目标

我们将要使用Constraint Layout设计一个简单的布局,如下左图:

  • 参考微信界面的案例,实现相应的布局
  • 第一个部分有两个图标及位于其下的文字,分别是收付款和钱包
  • 第二个部分是九宫格,每一个同样有图标及位于其下的文字,内容不限

(2)实现过程

看到这个界面,我们先思考可以用什么样的布局方法实现,首先我们想到的就是可以用Linearlayout来实现,因为里面的元素到多数是比较规则的水平或者垂直的方式排列的。那如何开始这个布局呢,我们可以试着让大模型来帮助我们。我们可以将这个图片给大模型,看看大模型会给我们什么样的答案。注意,现在很多大模型都支持多模态,不仅是能够支持文字输入,还支持图片等其他形式的文件输入。

我们首先创建一个空项目,选择Empty Views Activity,项目名称任意,其他保持默认。然后,我们在Android Studio里面双击activity_main.xml文件,使用自带的编辑器来进行编辑。我们通过尝试发现,使用大模型来设计好布局还是很难的,可能在大模型中还没有完全理解布局这一理念,希望未来大模型能够更加好的适应这一块。对于布局文件来说,让大模型按照需求绘制界面远没有使用可视化的编辑器来的简单。

对于这个目标我们首先确定总体的界面结构:这个界面有两个部分,第一个部分有两个图标及位于其下的文字,分别是收付款和钱包,第二个部分是九宫格,每一个同样有图标及位于其下的文字。因此我们可以用两个LinearLayout来分别表示第一个和第二个部分。以此类推,第一部分又可以分为左右两个图标,而第二部分又可以分为9个图标,每个图标可以分别使用一个TextView来实现,或者拆分为一个图标和一个标签。9个图标同样可以使用3个LinearLayout或者一个GridLayout来组织,依据实际情况而定。

默认的基础布局是ConstrainLayout,因此我们从左边的组件中拖入LinearLayout,并设置与父组件(即ConstrainLayout)的约束,我们可以通过可视化工具来设置,如下图所示:

3-7

可以通过拖动界面中的组件四边蓝点来添加和其他组件之间的约束,并在右边的工具栏中设置相关的约束值。这里我们定义了该LinearLayout的上边和父组件上边的约束为间距8dp、该LinearLayout的左边和父组件左边的约束为间距8dp,以及该LinearLayout的右边和父组件右边的约束为间距8dp。在Android中,上下左右分别被定义为Top、Bottom、Start和End。

同时,我们也可以在右边的工具栏中修改任意选中组件的属性,如文字内容、图标等等。在这个例子中,下面的每个图标组件都是用一个TextView实现,并定义了android:text和app:drawableTopCompat,从而绘制出一个图标按钮。

同样这些约束和属性也可以在xml代码中进行修改,例如:

<TextView  
    android:id="@+id/textView5"  
    android:layout_width="0dp"  
    android:layout_height="wrap_content"  
    android:layout_weight="1"  
    android:paddingTop="30dp"  
    android:paddingBottom="30dp"  
    android:text="信用卡还款"  
    android:textAlignment="center"  
    app:drawableTopCompat="@drawable/ic_credit" />  

依据要求依次通过可视化编辑器或者代码定义组件及其约束。

(3)运行示例

如下图所示,实机结果与编辑器中预览结果一致。

3-8

3.4.4 大模型设计布局示例

目标:我们将使用大模型实现上述的布局样例。通常情况下,程序员会从设计人员那边拿到界面的原型设计,如果能借助大模型的能力直接从界面图片中生成相关的代码,想必会极大地提高效率。

我们首先创建一个空项目,选择Empty Views Activity,项目名称任意,其他保持默认。对于该项任务,我们需要充分利用大模型的多模态能力。

交互过程1:目标输入

用户:

可以帮我在移动应用开发过程中,实现这样的一个布局效果吗?

3-7


注意,我们将图片直接输入给了大模型,就在我们最开始准备这个材料的时候,大部分的大模型还不支持图片作为输入,而到现在,大量的大模型都支持图片输入了,都可以来分析理解图片,我们就展示一个案例,看看大模型怎么学习一个设计好的界面的效果,然后帮助来设计相关的布局。

大模型:

当然可以!下面是一个简单的示例,展示如何使用Android的XML布局文件来实现类似的界面布局。这个示例使用了ConstraintLayout来实现复杂的布局需求。

1.创建一个新的XML布局文件

在你的Android项目的res/layout目录下创建一个新的XML文件,例如activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- Header Section -->
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="#00C853"
        android:padding="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_payment" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="收付款"
            android:textColor="#FFFFFF"
            android:layout_marginStart="16dp"
            android:textSize="18sp" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="end"
            android:orientation="vertical">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_wallet" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="钱包"
                android:textColor="#FFFFFF"
                android:layout_marginTop="4dp"
                android:textSize="14sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="¥9.25"
                android:textColor="#FFFFFF"
                android:textSize="16sp" />
        </LinearLayout>
    </LinearLayout>

    <!-- Service Section -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="腾讯服务"
        android:textSize="16sp"
        android:padding="16dp"
        app:layout_constraintTop_toBottomOf="@id/header"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <GridLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:columnCount="3"
        android:rowCount="2"
        android:layout_margin="16dp"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_credit_card" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="信用卡还款"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_phone" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="手机充值"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_finance" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="理财通"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_life_pay" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="生活缴费"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_q_coin" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Q币充值"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_city_service" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="城市服务"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>
    </GridLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        android:padding="16dp"
        app:layout_constraintTop_toBottomOf="@id/gridLayout"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_charity" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="腾讯公益"
            android:textSize="14sp"
            android:layout_marginTop="8dp" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

2.添加图标资源

确保在你的res/drawable目录下添加相应的图标资源文件,例如ic_paymentic_walletic_credit_card等。

3.在MainActivity中设置布局

在你的MainActivity.java中设置布局:

package com.example.yourapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

4.运行应用

现在你可以运行你的应用,应该能看到类似于你提供的界面布局效果。

这个示例只是一个基本的实现,你可以根据需要进一步调整样式和功能。例如,你可以添加点击事件、动态数据加载等。希望这对你有帮助!


操作:

我们根据对话内容依次进行以下操作:

  • 将大模型提供的XML代码复制到activity_main.xml文件中

添加代码后,发现IDE报错:The id "header" is not defined anywhere.,如图X-X所示:

这个问题如果是熟悉布局使用的开发者,应该能够比较快的发现主要原因,其实就是前面的相对参考的layout没有设置id,我们补充上id就可以。不过我们也可以基于大模型,在大模型的帮助下来帮我们完成相关事情,因此我们再次询问大模型。


交互过程2:解决报错

用户:

出现了报错:The id "header" is not defined anywhere.


大模型:

抱歉,我在示例代码中使用了一个未定义的ID header。让我修正这个问题并提供一个更新的示例。

更新的XML布局文件

在你的res/layout目录下创建或更新activity_main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- Header Section -->
    <LinearLayout
        android:id="@+id/header"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="#00C853"
        android:padding="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_payment" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="收付款"
            android:textColor="#FFFFFF"
            android:layout_marginStart="16dp"
            android:textSize="18sp" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="end"
            android:orientation="vertical">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_wallet" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="钱包"
                android:textColor="#FFFFFF"
                android:layout_marginTop="4dp"
                android:textSize="14sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="¥9.25"
                android:textColor="#FFFFFF"
                android:textSize="16sp" />
        </LinearLayout>
    </LinearLayout>

    <!-- Service Section -->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="腾讯服务"
        android:textSize="16sp"
        android:padding="16dp"
        app:layout_constraintTop_toBottomOf="@id/header"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <GridLayout
        android:id="@+id/gridLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:columnCount="3"
        android:rowCount="2"
        android:layout_margin="16dp"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_credit_card" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="信用卡还款"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_phone" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="手机充值"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_finance" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="理财通"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_life_pay" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="生活缴费"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_q_coin" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Q币充值"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_city_service" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="城市服务"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>
    </GridLayout>

    <LinearLayout
        android:id="@+id/charityLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        android:padding="16dp"
        app:layout_constraintTop_toBottomOf="@id/gridLayout"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_charity" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="腾讯公益"
            android:textSize="14sp"
            android:layout_marginTop="8dp" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

3.在MainActivity中设置布局

在你的MainActivity.java中设置布局:

package com.example.yourapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

4.添加图标资源

确保在你的res/drawable目录下添加相应的图标资源文件,例如ic_paymentic_walletic_credit_card等。

5.运行应用

现在你可以运行你的应用,应该能看到类似于你提供的界面布局效果。

这个示例只是一个基本的实现,你可以根据需要进一步调整样式和功能。例如,你可以添加点击事件、动态数据加载等。希望这对你有帮助!


操作:

我们根据对话内容依次将大模型提供的XML代码复制到activity_main.xml文件中,此时可以看到界面渲染如下所示:

在这个基本框架上,进一步地把图标资源导入,可以看到如下图的效果

这时候我们可以看到,跟我们最后要的效果还差得很远,这时候不要着急,不要慌,我们继续来调整一步一步来解决问题。如果有一些布局设置经验的开发者,其实到这一步可以自己来慢慢调整了,例如这里面很多图标重叠,肯定是相对位置设置得不太对。不过我们也可以继续询问大模型出了什么问题。


交互过程3:解决布局紊乱

用户:

现在的界面结构长这个样子,要怎么进一步修改。


大模型:

根据您提供的新的界面结构,我们需要对布局进行一些调整。以下是更新后的XML布局文件,适用于Android应用开发。

更新的XML布局文件

在您的res/layout目录下创建或更新activity_main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- Header Section -->
    <LinearLayout
        android:id="@+id/header"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="#00C853"
        android:padding="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_android" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="收付款"
            android:textColor="#FFFFFF"
            android:gravity="center"
            android:textSize="18sp" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:gravity="center">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_wallet" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="钱包"
                android:textColor="#FFFFFF"
                android:layout_marginTop="4dp"
                android:textSize="14sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="¥9.25"
                android:textColor="#FFFFFF"
                android:textSize="16sp" />
        </LinearLayout>
    </LinearLayout>

    <!-- Service Section -->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="腾讯服务"
        android:textSize="16sp"
        android:padding="16dp"
        app:layout_constraintTop_toBottomOf="@id/header"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <GridLayout
        android:id="@+id/gridLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:columnCount="3"
        android:rowCount="3"
        android:layout_margin="16dp"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_credit_card" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="信用卡还款"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_phone" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="手机充值"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_finance" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="理财通"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_life_pay" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="生活缴费"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_q_coin" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Q币充值"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_city_service" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="城市服务"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_charity" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="腾讯公益"
                android:textSize="14sp"
                android:layout_marginTop="8dp" />
        </LinearLayout>
    </GridLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

3.在MainActivity中设置布局

在您的MainActivity.java中设置布局:

package com.example.yourapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

4.添加图标资源

确保在您的res/drawable目录下添加相应的图标资源文件,例如ic_androidic_walletic_credit_card等。

5.运行应用

现在您可以运行您的应用,应该能看到类似于您提供的界面布局效果。

这个示例只是一个基本的实现,您可以根据需要进一步调整样式和功能。例如,您可以添加点击事件、动态数据加载等。希望这对您有帮助!


操作:

我们根据对话内容依次进行以下操作:

  • 再次将大模型提供的XML代码复制到activity_main.xml文件中

此时可以看到界面渲染如下图所示:

可以看到,现在界面已经基本符合需求,手动修改以下最前面的图标命名错误并给定大小,再修复一些头部的Bug,修改后的xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<!-- 这边头部需要加上ID -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"                                               
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- Header Section -->
    <LinearLayout
        android:id="@+id/header"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="#00C853"
        android:orientation="horizontal"
        android:padding="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <!-- 这边模仿下面的代码加上LinearLayout -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="42sp"
                android:src="@drawable/ic_payment" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:text="收付款"
                android:textColor="#FFFFFF"
                android:textSize="18sp" />
        </LinearLayout>

        <!-- 这边把宽度改成正确的宽度 -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="42sp"
                android:src="@drawable/ic_wallet" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:text="钱包"
                android:textColor="#FFFFFF"
                android:textSize="14sp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="¥9.25"
                android:textColor="#FFFFFF"
                android:textSize="16sp" />
        </LinearLayout>
    </LinearLayout>

    <!-- Service Section -->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="腾讯服务"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/header" />

    <GridLayout
        android:id="@+id/gridLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:columnCount="3"
        android:rowCount="3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_credit_card" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="信用卡还款"
                android:textSize="14sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_phone" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="手机充值"
                android:textSize="14sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_finance" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="理财通"
                android:textSize="14sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_life_pay" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="生活缴费"
                android:textSize="14sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_q_coin" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="Q币充值"
                android:textSize="14sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_city_service" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="城市服务"
                android:textSize="14sp" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="16dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_charity" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="腾讯公益"
                android:textSize="14sp" />
        </LinearLayout>
    </GridLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

(3)运行示例

编译运行程序后看到的界面如下图所示,这时候我们就得到了一个相对比较清晰的布局设计了,在此基础上,我们可以在进一步调整,这样就可以得到更好的效果。对于其他的布局也是一样。

从总体上来看,布局设计的时候与大模型直接进行多模态图片交互,通常很难一下子得到想要的效果,对于复杂的效果可能大模型给出的方法跟实际我们想要的效果差别还很大,这就很需要开发者对布局有相应的了解,基于了解将任务进行分解,或者对大模型给出的答案进行修改优化。

3.5 Listener

Listener是Android中用于处理用户交互和事件响应的机制,是事件处理机制的核心组件之一,是让应用“活”起来的关键。 它允许开发者监听并响应用户与界面交互时产生的各种事件,如用户的交互(点击、触摸、按键等)、数据变化或系统状态的变更。通过使用Listener类,开发者可以为不同的控件(如按钮、文本框等)设置相应的事件处理逻辑,从而实现丰富的交互功能。理解和使用监听器是开发响应式和互动性强的应用程序的关键。

3.5.1 监听器的概念

监听器是一种接口(Interface),用于定义特定事件发生时的回调方法。当用户与界面交互(如点击按钮、触摸屏幕、输入文本等)时,或是数据和系统状态发生变化时,Android系统会触发相应的事件,并调用监听器接口中定义的方法。开发者可以在这些回调方法中编写具体的处理逻辑,从而实现对事件的响应。

3.5.2 监听器的作用

监听器的主要作用是将用户操作(事件)与具体的处理逻辑解耦。通过监听器,开发者可以灵活地为不同的控件设置不同的事件处理逻辑,而无需在控件的代码中直接编写事件处理代码。这种设计模式使得代码更加清晰、模块化,便于维护和扩展。

3.5.3 监听器的工作原理

监听器的工作原理基于事件驱动编程模型,具体流程如下:

  1. 事件源(Event Source):事件源是指能够产生事件的对象,通常是界面中的控件(如按钮、文本框等)。
  2. 事件(Event):事件是指用户与界面交互时产生的动作,如点击、触摸、按键等。
  3. 监听器(Listener):监听器是一个接口,定义了事件发生时需要执行的回调方法。
  4. 事件分发(Event Dispatch):当事件发生时,Android系统会将事件分发到对应的事件源。
  5. 事件处理(Event Handling):事件源会调用监听器接口中定义的回调方法,并将事件的相关信息传递给该方法。开发者可以在回调方法中编写具体的处理逻辑。

3.5.4 常见的监听器类型

在移动应用开发中,有几种常见的监听器类型:(1)用户交互监听器,如点击监听器、触摸监听器、滚动监听器等;(2)数据监听器,如数据变化监听器、数据库监听器等;(3)系统事件监听器,如电池状态监听器、网络状态监听器等。

(1)用户交互监听器

用户交互监听器用于监听用户与界面控件的交互事件,如点击、触摸、长按等。这些监听器通常绑定到UI控件上,用于响应用户的操作。

以下是一些常见的用户交互监听器及其用途:

  • View.OnClickListener:用于监听控件的点击事件。
  • View.OnLongClickListener:用于监听控件的长按事件。
  • View.OnTouchListener:监听控件的触摸事件,包括按下、移动、抬起等。
  • AdapterView.OnItemClickListener:用于监听列表项的点击事件。

(2)数据监听器

数据监听器用于监听数据的变化,通常用于实现数据绑定和响应式编程。

以下是一些常见的数据监听器及其用途:

  • TextWatcher:监听文本框(EditText)内容的变化。

  • CompoundButton.OnCheckedChangeListener :监听复选框(CheckBox)或单选按钮(RadioButton)的选中状态变化。

  • SeekBar.OnSeekBarChangeListener:监听进度条(SeekBar)的变化。

(3)系统事件监听器

系统事件监听器用于监听系统层面的事件,如生命周期事件、配置变化、电池状态等。

以下是一些常见的系统事件监听器及其用途:

  • ActivityLifecycleCallbacks:监听Activity的生命周期事件。
  • BroadcastReceiver:监听系统广播事件,如电池状态变化、网络状态变化等。
  • OnSharedPreferenceChangeListener:监听SharedPreferences中数据的变化。

合理使用这些监听器可以提升应用程序的交互性和用户体验,同时也可以更好地管理应用的全局状态和系统资源。

3.5.5. 监听器的注册方式

要使用监听器,必须先为控件注册监听器。Android提供了多种注册监听器的方式,常见的有以下几种:

(1)匿名内部类

通过匿名内部类实现监听器接口,并直接为控件设置监听器。这种方式简洁明了,适合处理逻辑较简单的场景。

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(context, "按钮被点击了", Toast.LENGTH_SHORT).show();
    }
});

(2)内部类

通过定义一个内部类实现监听器接口,然后将内部类的实例设置为控件的监听器。这种方式可以使代码更加清晰,适合处理逻辑较复杂的场景。

private class MyClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(context, "按钮被点击了", Toast.LENGTH_SHORT).show();
    }
}

button.setOnClickListener(new MyClickListener());

(3)外部类

将监听器实现为一个独立的外部类,然后在需要的地方创建其实例并注册到控件上。这种方式可以使代码更加模块化,便于复用。

public class MyClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(v.getContext(), "按钮被点击了", Toast.LENGTH_SHORT).show();
    }
}

button.setOnClickListener(new MyClickListener());

(4)Activity或Fragment自身实现接口

让Activity或Fragment自身实现监听器接口,并在需要的地方将自身设置为控件的监听器。这种方式可以使代码更加简洁,但需要注意避免混淆。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(this, "按钮被点击了", Toast.LENGTH_SHORT).show();
    }
}

(5)通过XML设置

在布局文件中通过android:onClick属性指定一个方法名,该方法将在点击事件发生时被调用。这种方式可以使代码更加简洁,但灵活性相对较低。

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="点击我"
    android:onClick="onButtonClick" />
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onButtonClick(View v) {
        // 处理点击事件
        Toast.makeText(this, "按钮被点击了", Toast.LENGTH_SHORT).show();
    }
}

3.5.6 监听器的回调方法

监听器接口通常定义了一个或多个回调方法,这些方法会在特定事件发生时被调用。例如:

  • View.OnClickListeneronClick(View v)方法会在控件被点击时被调用。
  • View.OnLongClickListeneronLongClick(View v)方法会在控件被长按时被调用。
  • View.OnKeyListeneronKey(View v, int keyCode, KeyEvent event)方法会在按键事件发生时被调用。

开发者需要在这些回调方法中编写具体的事件处理逻辑。例如:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 处理点击事件
        Toast.makeText(context, "按钮被点击了", Toast.LENGTH_SHORT).show();
    }
});

3.5.8 大模型使用Listener案例

目标:我们将实现拖拽方块的功能,来体现Listener的基本使用方式。

我们首先创建一个空项目,选择Empty Views Activity,项目名称任意,其他保持默认。

交互过程1:目标输入

用户:

请给我一个安卓Java使用Listener拖动方块的示例。

大模型:

以下是一个简单的安卓Java示例代码,用于实现通过监听器(Listener)拖动方块的功能。这个示例使用了一个自定义的View来绘制方块,并通过触摸事件来实现方块的拖动。

示例代码

1.创建一个自定义的View

这个类负责绘制方块,并处理触摸事件以实现拖动功能。

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class DraggableSquareView extends View {
    private float squareX = 100; // 方块的初始X坐标
    private float squareY = 100; // 方块的初始Y坐标
    private float squareSize = 100; // 方块的大小
    private Paint paint; // 用于绘制方块的画笔
    private boolean isDragging = false; // 是否正在拖动
    private float touchOffsetX; // 触摸点与方块左上角的X偏移量
    private float touchOffsetY; // 触摸点与方块左上角的Y偏移量

    public DraggableSquareView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(Color.BLUE); // 设置方块颜色为
蓝色    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制方块
        canvas.drawRect(squareX, squareY, squareX + squareSize, squareY + squareSize, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 检测触摸点是否在方块内
                if (event.getX() >= squareX && event.getX() <= squareX + squareSize &&
                    event.getY() >= squareY && event.getY() <= squareY + squareSize) {
                    isDragging = true;
                    touchOffsetX = event.getX() - squareX; // 计算触摸点与方块左上角的X偏移量
                    touchOffsetY = event.getY() - squareY; // 计算触摸点与方块左上角的Y偏移量
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isDragging) {
                    // 更新方块的位置
                    squareX = event.getX() - touchOffsetX;
                    squareY = event.getY() - touchOffsetY;
                    invalidate(); // 重绘视图
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isDragging) {
                    isDragging = false;
                }
                break;
        }
        return true;
    }
}

2.在布局文件中使用自定义的View

res/layout/activity_main.xml中添加自定义的DraggableSquareView

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <com.example.draggable.DraggableSquareView
        android:id="@+id/draggable_square"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

3.在MainActivity中初始化

MainActivity中不需要额外的代码,因为自定义的View已经处理了所有的逻辑。

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

运行效果

运行这个应用后,屏幕上会显示一个蓝色的方块。你可以通过触摸并拖动方块来改变它的位置。这个示例通过onTouchEvent方法监听触摸事件,并根据触摸点的位置动态更新方块的位置。

注意事项

  • 如果方块的大小或位置需要动态调整,可以在DraggableSquareView类中添加相应的逻辑。
  • 如果需要更复杂的交互(例如限制方块的移动范围),可以在ACTION_MOVE事件中添加额外的逻辑来限制方块的位置。

操作:

根据大模型的输出,我们进行以下操作:

  • 创建一个新的类DraggableSquareView,并把代码复制进相应文件中
  • 将XML代码复制到activity_main.xml文件中

记得保持package包名。

记得修改奇怪的但是很小的Bug。

(3)运行示例

拖拽前:

3.5.9 总结

监听器是Android开发中实现事件处理的核心机制。通过监听器,开发者可以灵活地为控件设置事件处理逻辑,实现丰富的交互功能。合理使用监听器可以提高代码的可读性和可维护性,同时需要注意避免内存泄漏、线程安全等问题。

3.6 Fragment

Fragment是Android开发中用于构建动态和模块化用户界面的重要组件,是实现复杂界面布局和功能模块化的强大工具。 本章将讲解Fragment的生命周期、如何在Activity中使用Fragment,以及Fragment与Activity之间的通信方式。

3.6.1 Fragment介绍

Fragment 是Android开发中用于构建动态和模块化用户界面的重要组件。它可以嵌入到 Activity 中,从而实现更灵活的界面布局和功能模块化。Fragment 有自己的生命周期,类似于 Activity,但更加灵活,可以独立于 Activity 被添加、移除或替换。

3.6.1 Fragment的特性

(1)模块化与复用性

  • 模块化Fragment 可以作为独立的模块,封装特定的功能和布局。这使得代码更加清晰,便于管理和维护。
  • 复用性:同一个 Fragment 可以在不同的 Activity 中复用,从而减少重复代码,提高开发效率。

(2)动态性与灵活性

  • 动态加载Fragment 可以在运行时动态添加、移除或替换,这使得界面更加灵活,可以根据用户的操作或应用的状态动态调整界面布局。
  • 生命周期管理Fragment 有自己的生命周期,类似于 Activity,但更加灵活。开发者可以在 Fragment 的生命周期方法中进行资源的初始化和释放。

(3)多屏适配

  • 多屏布局Fragment 特别适合在平板电脑等大屏幕上实现多屏布局。通过在同一个 Activity 中使用多个 Fragment,可以创建复杂的界面布局,提升用户体验。
  • 响应式设计Fragment 可以根据屏幕尺寸和方向动态调整布局,实现响应式设计。

(4)与Activity的协作

  • 通信机制Fragment 可以通过接口回调的方式与 Activity 通信,实现数据共享和事件传递。
  • 生命周期关联Fragment 的生命周期与 Activity 的生命周期密切相关,但 Fragment 可以独立于 Activity 进行操作。

(5)状态保存与恢复

  • 保存状态Fragment 提供了 onSaveInstanceState 方法,用于在配置变化(如屏幕旋转)时保存状态。
  • 恢复状态:在 onCreateonViewCreated 方法中,可以通过 savedInstanceState 恢复保存的状态。

(6)嵌套Fragment

  • 嵌套支持:从 Android 4.2 开始,支持嵌套 Fragment,即一个 Fragment 可以包含另一个 Fragment。这使得界面布局更加灵活,但也需要谨慎使用以避免复杂性和潜在问题。
  • 管理嵌套:嵌套 Fragment 的生命周期管理更加复杂,需要确保正确管理每个 Fragment 的生命周期。

3.6.3 Fragment的使用

(1)创建Fragment

创建一个 Fragment 需要继承 androidx.fragment.app.Fragment 类,并实现必要的生命周期方法。例如:

public class MyFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // 加载布局文件
        return inflater.inflate(R.layout.fragment_my, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 初始化视图组件
        TextView textView = view.findViewById(R.id.tv_fragment);
        textView.setText("这是一个Fragment");
    }
}

(2)在Activity中添加Fragment

可以通过静态方式(在布局文件中定义)或动态方式(通过代码添加)将 Fragment 添加到 Activity 中。

静态方式: 在布局文件中使用 <fragment> 标签:

<fragment
    android:id="@+id/my_fragment"
    android:name="com.example.MyFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

动态方式: 通过 FragmentManagerFragmentTransaction 动态添加 Fragment

Fragment myFragment = new MyFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_container, myFragment);
fragmentTransaction.commit();

(3)与Activity通信

FragmentActivity 之间可以通过接口进行通信。例如:

public class MyFragment extends Fragment {
    private OnFragmentInteractionListener mListener;

    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(String data);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public void someMethod() {
        if (mListener != null) {
            mListener.onFragmentInteraction("Hello from Fragment");
        }
    }
}

Activity 中实现接口:

public class MyActivity extends AppCompatActivity implements MyFragment.OnFragmentInteractionListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
    }

    @Override
    public void onFragmentInteraction(String data) {
        // 处理来自Fragment的数据
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show();
    }
}

3.7 Tab和Navigation

Tab和Navigation是现代Android应用中常见的导航方式。掌握Tab和Navigation是提升应用交互性的关键

本章将介绍如何使用TabLayout、ViewPager和Navigation组件实现流畅的导航体验,以及如何通过这些组件优化应用的结构和用户体验。同时Tab通常是与Fragment结合,我们这里也通过Tab的方式,展示Fragment的用法。

3.7.1 Tab的介绍与使用

Tab(标签)界面如下图所示:

3-9

TabLayout 是安卓提供的一个组件,用于创建标签导航。通过与 ViewPager 配合使用,TabLayout 可以轻松实现可滑动的标签视图。TabLayout中的每个Tab都是一个Fragment,通过滑动Tab动态切换Fragment。

使用 TabLayout 一般包含以下几个步骤:(1)添加依赖:在项目的 build.gradle 文件中添加对 Material Components 的依赖,如下所示:

implementation 'com.google.android.material:material:1.4.0'

(2)布局文件:在布局文件中添加TabLayout和ViewPager:

<com.google.android.material.tabs.TabLayout  
    android:id="@+id/tab_layout"  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content" />  

<androidx.viewpager.widget.ViewPager  
    android:id="@+id/view_pager"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" />  

(3)Activity文件:在Activity中设置TabLayout和ViewPager:

public class MainActivity extends AppCompatActivity {  

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

        TabLayout tabLayout = findViewById(R.id.tab_layout);  
        ViewPager viewPager = findViewById(R.id.view_pager);  

        // 设置ViewPager的适配器  
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));  

        // 将TabLayout与ViewPager关联  
        tabLayout.setupWithViewPager(viewPager);  
    }  

    // ViewPager适配器  
    private class ViewPagerAdapter extends FragmentPagerAdapter {  

        public ViewPagerAdapter(@NonNull FragmentManager fm) {  
            super(fm);  
        }  

        @NonNull  
        @Override  
        public Fragment getItem(int position) {  
            switch (position) {  
                case 0:  
                    return new FragmentOne();  
                case 1:  
                    return new FragmentTwo();  
                case 2:  
                    return new FragmentThree();  
                default:  
                    return null;  
            }  
        }  

        @Override  
        public int getCount() {  
            return 3; // 返回Tab的数量  
        }  

        @Nullable  
        @Override  
        public CharSequence getPageTitle(int position) {  
            switch (position) {  
                case 0:  
                    return "Tab One";  
                case 1:  
                    return "Tab Two";  
                case 2:  
                    return "Tab Three";  
                default:  
                    return null;  
            }  
        }  
    }  
}  

TabLayout 提供了多种属性来定制其外观和行为,例如: - app:tabMode:设置标签的显示模式。常见值有 fixed 和 scrollable。 - app:tabGravity:设置标签的对齐方式。常见值有 fill 和 center。 - app:tabIndicatorColor:设置标签指示器的颜色。 - app:tabSelectedTextColor:设置选中标签的文字颜色。 - app:tabTextColor:设置未选中标签的文字颜色。

这些属性可以在XML布局文件中进行设置:

<com.google.android.material.tabs.TabLayout  
    android:id="@+id/tab_layout"  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content"  
    app:tabMode="fixed"  
    app:tabGravity="fill"  
    app:tabIndicatorColor="@color/colorAccent"  
    app:tabSelectedTextColor="@color/colorPrimary"  
    app:tabTextColor="@android:color/darker_gray" />  

3.7.2 Navigation的介绍和使用

在Android应用开发中,底部导航栏(Bottom Navigation Bar)是一种常用的导航模式。BottomNavigationView 是 Android 提供的一个组件,用于在屏幕底部创建一个导航栏,以方便用户在不同的视图之间进行切换,如下图所示

navigation

BottomNavigationView 是 Material Design 组件库中的一个视图组件,用于实现底部导航栏。它通常包含三个到五个导航项,每个项对应一个不同的视图或Fragment。通过与 Fragment 的结合,可以轻松创建用户友好的导航体验。

使用 BottomNavigationView 一般包含以下几个步骤:

(1)添加依赖:在项目的 build.gradle 文件中添加对 Material Components 的依赖,如下所示

implementation 'com.google.android.material:material:1.4.0'

(2)布局文件:在布局文件中添加TabLayout和ViewPager:

<com.google.android.material.bottomnavigation.BottomNavigationView  
    android:id="@+id/bottom_navigation"  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content"  
    android:layout_alignParentBottom="true"  
    app:menu="@menu/bottom_nav_menu" />  

(3)资源文件:在 res/menu 目录下创建 bottom_nav_menu.xml 文件,定义导航项:

<menu xmlns:android="http://schemas.android.com/apk/res/android">  
    <item  
        android:id="@+id/nav_home"  
        android:icon="@drawable/ic_home"  
        android:title="Home" />  
    <item  
        android:id="@+id/nav_search"  
        android:icon="@drawable/ic_search"  
        android:title="Search" />  
    <item  
        android:id="@+id/nav_profile"  
        android:icon="@drawable/ic_profile"  
        android:title="Profile" />  
</menu>  

(4)Activity文件:Java在Activity中设置BottomNavigationView:

public class MainActivity extends AppCompatActivity {  

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

        BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);  

        // 设置导航项选择监听器  
        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {  
            @Override  
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {  
                Fragment selectedFragment = null;  
                switch (item.getItemId()) {  
                    case R.id.nav_home:  
                        selectedFragment = new HomeFragment();  
                        break;  
                    case R.id.nav_search:  
                        selectedFragment = new SearchFragment();  
                        break;  
                    case R.id.nav_profile:  
                        selectedFragment = new ProfileFragment();  
                        break;  
                }  
                // 切换Fragment  
                getSupportFragmentManager().beginTransaction().replace(R.id.Fragment_container, selectedFragment).commit();  
                return true;  
            }  
        });  

        // 默认选择的导航项  
        bottomNavigationView.setSelectedItemId(R.id.nav_home);  
    }  
} 

BottomNavigationView 提供了多种属性来定制其外观和行为:

  • app:menu:设置导航栏的菜单资源文件。
  • app:itemIconTint:设置导航项图标的颜色。
  • app:itemTextColor:设置导航项文字的颜色。
  • app:labelVisibilityMode:设置标签的显示模式,常见值有 labeled(始终显示标签)和 unlabeled(仅显示图标)。

这些属性可以在XML布局文件中进行设置:

<com.google.android.material.bottomnavigation.BottomNavigationView  
android:id="@+id/bottom_navigation"  
android:layout_width="match_parent"  
android:layout_height="wrap_content"  
android:layout_alignParentBottom="true"  
app:menu="@menu/bottom_nav_menu"  
app:itemIconTint="@color/nav_item_color"  
app:itemTextColor="@color/nav_item_color"  
app:labelVisibilityMode="labeled" /> 

通过综合使用TabView和BottomNavigation,可以设计出复杂的应用导航方式,同时兼顾用户体验。现在大量的应用几乎都是采用这种方式来组织界面。

3.7.3 大模型实现Tab的示例

下面我们也再来看看如何使用大模型来帮助我们实现Tab相关的布局效果。

交互过程1:目标输入

用户:

请帮我写一个示例,如何在安卓中使用Java编写Tab页面?

大模型:

在Android开发中,可以使用TabLayoutViewPager来实现带有标签页(Tab)的界面。以下是一个完整的示例,展示如何使用Java在Android中实现一个带有多个Tab页的界面。

示例:实现一个带有三个Tab页的Android应用

1.添加依赖

build.gradle文件中添加TabLayoutViewPager的依赖(如果使用的是Material Design组件):

dependencies {
    implementation 'androidx.viewpager:viewpager:1.0.0'
    implementation 'com.google.android.material:material:1.9.0'
}

2.布局文件

创建一个布局文件activity_main.xml,用于定义TabLayoutViewPager

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMode="fixed" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

3.创建Fragment

为每个Tab创建一个Fragment。以下是一个简单的Fragment示例:

// TabFragment.java
package com.example.tabexample;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TabFragment extends Fragment {
    private String tabTitle;

    public static TabFragment newInstance(String title) {
        TabFragment fragment = new TabFragment();
        Bundle args = new Bundle();
        args.putString("title", title);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            tabTitle = getArguments().getString("title");
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_tab, container, false);
        TextView textView = view.findViewById(R.id.tabText);
        textView.setText(tabTitle);
        return view;
    }
}

创建fragment_tab.xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/tabText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:textStyle="bold" />
</LinearLayout>

4.创建适配器

创建一个FragmentPagerAdapter来管理ViewPager中的Fragment

// ViewPagerAdapter.java
package com.example.tabexample;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

public class ViewPagerAdapter extends FragmentPagerAdapter {
    private String[] tabTitles;

    public ViewPagerAdapter(@NonNull FragmentManager fm, String[] tabTitles) {
        super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.tabTitles = tabTitles;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        return TabFragment.newInstance(tabTitles[position]);
    }

    @Override
    public int getCount() {
        return tabTitles.length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return tabTitles[position];
    }
}

5.主Activity

MainActivity中设置TabLayoutViewPager

// MainActivity.java
package com.example.tabexample;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;

public class MainActivity extends AppCompatActivity {
    private TabLayout tabLayout;
    private ViewPager viewPager;

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

        tabLayout = findViewById(R.id.tabLayout);
        viewPager = findViewById(R.id.viewPager);

        String[] tabTitles = {"Tab 1", "Tab 2", "Tab 3"};
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), tabTitles);
        viewPager.setAdapter(adapter);

        tabLayout.setupWithViewPager(viewPager);
    }
}

6.运行效果

运行应用后,你将看到一个带有三个Tab页的界面,每个Tab页显示对应的标题。

说明

  • TabLayout用于显示Tab标题。
  • ViewPager用于显示内容页面。
  • ViewPagerAdapter用于管理ViewPager中的Fragment
  • 每个Fragment显示一个简单的TextView,显示Tab的标题。

你可以根据需要扩展Fragment的内容,添加更复杂的布局和逻辑。


操作:

根据大模型的输出,我们将依次进行以下操作:

  • 添加依赖到build.gradle
  • 将XML代码复制到activity_main.xml里面,定义TabLayoutViewPager
  • 创建类TabFragment,将代码复制到相应文件里,这里面Fragment是每一个Tab里面展示的内容,用户可以根据自己的布局进行修改。
  • 创建布局文件fragment_tab.xml,将代码复制到相应文件里,这里我们3个Tab都使用的是同一个Fragment布局,也可以换成不同的Fragment布局。
  • 创建适配器类ViewPagerAdapter,将代码复制到相应文件里,这个Adapter是重要的部分,就是用来管理Tab里面的不同页面,读者可以仔细去看一下Adapter里面的写法,很多不同的应用都需要用到Adapter。
  • 修改MainActivity,设置TabLayoutViewPager

(3)运行示例

打开应用,首先看到Tab1

点击后可以滑动到其他Tab,例如Tab3,每一个Tab里面展示的是非常简单的文字,这个在fragment_tab.xml中进行了定义:

3.7.4 大模型实现底部导航栏的示例

同时,我们也展示一下,如何通过与大模型进行交互,实现底部导航栏的效果。

交互过程1:目标输入

用户:

实现bottomNavigation使用Android studio和Java


大模型:

在Android Studio中使用Java实现底部导航栏(Bottom Navigation)的功能,你可以按照以下步骤进行:

创建新的Activity:

在Android Studio中,右键点击你的包名 -> New -> Activity -> Bottom Navigation Activity。 这将为你创建一个新的Activity,其中已经包含了底部导航栏的基本代码。 理解代码结构:

底部导航栏通常由3-5个图标组成,每个图标代表一个Fragment。 在activity_main.xml中,你会看到一个BottomNavigationView控件。 在MainActivity.java中,你会看到与每个底部导航项关联的Fragments以及它们的切换逻辑。 自定义Fragment:

如果默认生成的Fragments不符合你的需求,你可以自定义它们。 在layout文件夹中为每个Fragment创建一个新的XML布局文件。 在java文件夹中为每个Fragment创建一个新的Java类,并继承Fragment。 更新MainActivity.java:

在MainActivity.java中,你会看到onNavigationItemSelected方法,这是当用户点击底部导航项时调用的方法。 在这个方法中,你可以添加或修改逻辑来切换Fragments。 例如,你可以使用getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new YourFragment()).commit();来替换当前的Fragment。 更新activity_main.xml:

你可以根据需要修改activity_main.xml中的布局。 例如,你可以更改BottomNavigationView的ID或添加其他视图控件。 运行和测试:

点击运行按钮,在模拟器或连接的设备上测试你的应用。 确保底部导航栏正常工作,并且每个导航项都能正确显示相应的Fragment。 添加更多功能:

你可以为每个Fragment添加更多的功能,如列表、按钮、图片等。 你也可以添加更多的事件监听器来处理用户交互。 这就是使用Android Studio和Java实现底部导航栏的基本步骤。你可以根据自己的需求进行自定义和扩展。


操作 1. 使用Android studio:右键点击你的包名 -> New -> Activity -> Bottom Navigation Activity即可生成具有bottom navigation的新建项目。 2. 查看该项目中相关代码文件,即可了解bottom navigation生成原理。


但是大模型给出的都是原理性的话,并没有给出具体代码,我们可以将问题问得更加具体。

交互过程2:案例

用户: 你能给我写个例子吗


大模型: 当然可以。下面是一个简单的例子,展示如何使用Android Studio和Java创建一个带有底部导航栏(BottomNavigationView)的应用。

第一步:创建新的BottomNavigationActivity 在Android Studio中,创建一个新的BottomNavigationActivity。这通常会自动生成一个带有三个Fragment的底部导航栏示例。

第二步:更新activity_main.xml 在activity_main.xml中,你会看到一个FrameLayout作为Fragment的容器和一个BottomNavigationView。确保BottomNavigationView的menu属性指向了一个菜单资源文件,比如navigation。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:app="http://schemas.android.com/apk/res-auto"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    tools:context=".MainActivity">  

    <FrameLayout  
        android:id="@+id/fragment_container"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:layout_above="@id/nav_view" />  

    <com.google.android.material.bottomnavigation.BottomNavigationView  
        android:id="@+id/nav_view"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_alignParentBottom="true"  
        app:menu="@menu/bottom_nav_menu" />  

</RelativeLayout>

注意:在这个例子中,我使用了RelativeLayout,但ConstraintLayout可能是一个更好的选择,因为它更灵活且性能更佳。

第三步:创建菜单资源文件 在res/menu目录下创建一个名为bottom_nav_menu.xml的文件,并添加你的导航项。

<?xml version="1.0" encoding="utf-8"?>  
<menu xmlns:android="http://schemas.android.com/apk/res/android">  
    <item  
        android:id="@+id/navigation_home"  
        android:icon="@drawable/ic_home"  
        android:title="@string/title_home" />  
    <item  
        android:id="@+id/navigation_dashboard"  
        android:icon="@drawable/ic_dashboard"  
        android:title="@string/title_dashboard" />  
    <item  
        android:id="@+id/navigation_notifications"  
        android:icon="@drawable/ic_notifications"  
        android:title="@string/title_notifications" />  
</menu>

确保你有相应的图标资源放在res/drawable目录下。

第四步:更新MainActivity.java 在MainActivity.java中,你需要处理底部导航栏的点击事件,并切换相应的Fragment。

public class MainActivity extends AppCompatActivity {  

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener  
            = new BottomNavigationView.OnNavigationItemSelectedListener() {  

        @Override  
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {  
            Fragment fragment = null;  
            switch (item.getItemId()) {  
                case R.id.navigation_home:  
                    fragment = new HomeFragment();  
                    loadFragment(fragment);  
                    return true;  
                case R.id.navigation_dashboard:  
                    fragment = new DashboardFragment();  
                    loadFragment(fragment);  
                    return true;  
                case R.id.navigation_notifications:  
                    fragment = new NotificationsFragment();  
                    loadFragment(fragment);  
                    return true;  
            }  
            return false;  
        }  
    };  

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

        BottomNavigationView navView = findViewById(R.id.nav_view);  
        navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);  

        // 默认加载第一个Fragment  
        loadFragment(new HomeFragment());  
    }  

    private void loadFragment(Fragment fragment) {  
        // 替换FrameLayout中的Fragment  
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();  
        transaction.replace(R.id.fragment_container, fragment);  
        transaction.commit();  
    }  
}

确保你已经创建了HomeFragment、DashboardFragment和NotificationsFragment这三个Fragment类,以及它们的对应布局文件。

第五步:运行应用 现在你可以运行应用了。点击Android Studio的运行按钮,选择一个模拟器或连接的真实设备,然后观察底部导航栏是否正常工作,以及点击不同的导航项时是否能够切换到对应的Fragment。


操作: 我们按照大模型推荐的步骤操作就行,不过这里面很有意思的一点是,在第二步中,大模型表示使用ConstraintLayout更好,由于Android studio自动生成的activity即为ConstraintLayout, 在第三步中,大模型内容与自动生成内容一致,不做修改,值得注意的是: “android:title="@string/title_notifications”,需要保证在res/values/strings.xml文件中有

    <string name="title_home">Home</string>
    <string name="title_dashboard">Dashboard</string>
    <string name="title_notifications">Notifications</string>

第四步内容与自动生成内容不一致,将大模型内容复制粘贴至mainActivity.java。结果:可以实现导航栏切换,但是HomeFragment的内容一直处于页面中,会产生页面混叠的现象,如下图所示

home

dashboard

notifications

但是使用Android studio自己生成的代码并不会出现以下问题,考虑是否可能是因为第二步采用的是constraintLayout,而不是大模型提供的RelativeLayout,故对activity_main.xml进行修改,修改后可以正常运行,不会出现混叠现象,即相应的导航栏只会出现其中一条对应的"This is xxx fragment"字样。 因此需要注意不同的Layout具有不同的性能,要进行合适的搭配;以及要相信大模型给出的搭配。此时可以向大模型询问为什么不同的layout会出现不一样的结果。


交互过程3:异样查询

用户:

在上述内容中,为什么将relativeLayout改成Constraintlayout会出现,多个fragment混叠的现象

大模型:

在上述内容中,将 RelativeLayout 改为 ConstraintLayout 后出现多个 Fragment 混叠的现象,主要是因为 ConstraintLayout 的布局约束方式与 RelativeLayout 不同,如果没有正确设置约束,可能会导致布局问题。

1.RelativeLayout 和 ConstraintLayout 的区别

RelativeLayout:

子视图的位置是通过与其他视图或父视图的相对位置来定义的。

在你的代码中,FrameLayout 被设置为在 BottomNavigationView 的上方(android:layout_above="@id/nav_view"),因此它会自动占据除底部导航栏之外的区域。

ConstraintLayout:

子视图的位置是通过约束(Constraints)来定义的,需要明确指定每个视图的起点和终点。

如果没有正确设置约束,子视图可能会重叠或布局不正确。

2.为什么会出现 Fragment 混叠

在 ConstraintLayout 中,如果没有正确设置 FrameLayout 和 BottomNavigationView 的约束,可能会导致以下问题:

FrameLayout 的高度没有被正确约束:在 RelativeLayout 中,FrameLayout 的高度是通过 android:layout_above 属性自动调整的。但在 ConstraintLayout 中,如果没有设置顶部和底部约束,FrameLayout 的高度可能会被设置为 match_parent,从而覆盖 BottomNavigationView。

BottomNavigationView 的位置没有被正确约束:如果没有设置 BottomNavigationView 的底部约束(如 app:layout_constraintBottom_toBottomOf="parent"),它可能会被放置在错误的位置,导致布局混乱。

3.如何解决

要解决这个问题,需要正确设置 ConstraintLayout 中的约束。以下是修改后的布局代码示例:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

4.关键点说明

FrameLayout 的约束:

app:layout_constraintTop_toTopOf="parent":顶部与父视图对齐。

app:layout_constraintBottom_toTopOf="@id/nav_view":底部与 BottomNavigationView 的顶部对齐。

app:layout_constraintLeft_toLeftOf="parent" 和 app:layout_constraintRight_toRightOf="parent":左右两侧与父视图对齐。

宽度和高度设置为 0dp,表示宽度和高度由约束决定。

BottomNavigationView 的约束:

app:layout_constraintBottom_toBottomOf="parent":底部与父视图对齐。

app:layout_constraintLeft_toLeftOf="parent" 和 app:layout_constraintRight_toRightOf="parent":左右两侧与父视图对齐。

通过正确设置这些约束,可以确保 FrameLayout 和 BottomNavigationView 的布局正确,避免 Fragment 混叠的问题。


操作: 根据大模型提供的代码修改activity_main.xml,导航栏可以正常运行,不存在混叠现象。


3.8 RecyclerView

RecyclerView是Android中用于高效显示大量数据的组件,是实现高性能数据展示的核心。 本章将详细介绍RecyclerView的基本用法、如何自定义适配器和布局管理器,以及如何实现数据的动态加载和更新。

3.8.1 RecyclerView的组件

RecyclerView的实现主要包括以下几个组件:

(1)Adapter: 是的,又是Adapter,相信大家已经不陌生了。Adapter是连接数据源和RecyclerView的桥梁。它负责从数据源获取数据并创建用于显示这些数据的ViewHolder。每个ViewHolder对应 一个单独的列表项视图。

常见的方法有: - onCreateViewHolder(ViewGroup parent, int viewType):创建一个新的ViewHolder对象,这里面每一个ViewHolder可以认为就是列表里面的一行。 - onBindViewHolder(VH holder, int position):将数据绑定到指定位置的ViewHolder上 - getItemCount():返回数据集的总数,也就是要显示的数据总的行数。

(2)ViewHolder: ViewHolder是一个用于缓存列表项视图的静态内部类。它减少了findViewById调用的次数。ViewHolder中通常包含了列表项视图中的子视图,可以直接操作子视图的组件。

(3)LayoutManager: LayoutManager负责管理RecyclerView的所有项。它决定了RecyclerView的布局方式。 常见的LayoutManager: - LinearLayoutManager:用于创建垂直或水平滚动列表。 - GridLayoutManager:用于创建网格布局。 - StaggeredGridLayoutManager:用于创建瀑布流布局。

他们之间的关系可以用下图表示:

3.8.2 RecycleView的使用方式

(1)添加依赖: 在项目的build.gradle文件中添加RecyclerView的依赖:

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
}

(2)创建布局文件: 创建一个包含RecyclerView的布局文件:

<androidx.recyclerview.widget.RecyclerView  
    android:id="@+id/recyclerView"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"/>

(3)创建适配器: 实现自定义的Adapter类:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {  
    private List<String> dataList;  

    public MyAdapter(List<String> dataList) {  
        this.dataList = dataList;  
    }  

    @Override  
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
        View view = LayoutInflater.from(parent.getContext())  
                .inflate(R.layout.list_item, parent, false);  
        return new MyViewHolder(view);  
    }  

    @Override  
    public void onBindViewHolder(MyViewHolder holder, int position) {  
        String data = dataList.get(position);  
        holder.textView.setText(data);  
    }  

    @Override  
    public int getItemCount() {  
        return dataList.size();  
    }  

    public static class MyViewHolder extends RecyclerView.ViewHolder {  
        TextView textView;  

        public MyViewHolder(View itemView) {  
            super(itemView);  
            textView = itemView.findViewById(R.id.textView);  
        }  
    }  
}  

(4)设置RecyclerView

在Activity或Fragment中设置RecyclerView和Adapter:

RecyclerView recyclerView = findViewById(R.id.recyclerView);  
MyAdapter adapter = new MyAdapter(dataList);  
recyclerView.setAdapter(adapter);  
recyclerView.setLayoutManager(new LinearLayoutManager(this));  

3.8.3 大模型在RecyclerView中的应用

我们将要写一个简单的应用,实现以下功能: - 上方有一个消息列表,显示所有收到的消息。 - 在界面的最下方有一个输入框,输入文字并点击“发送”按钮后,会在上面的消息列表中显示出来。 - 点击某条消息后边的删除按钮后,删除该条消息。 怎么界面布局模拟的就是很多消息发送界面的形式,使用的场景非常广泛。

我们首先创建一个空项目,选择Empty Views Activity,项目名称任意,其他保持默认。对于该APP,我们按照需要实现的功能点对过程进行解构。

交互过程1:目标输入

用户: 我现在要实现一个可发送和删除的消息列表,首先请用RecycleView实现一个消息列表。


大模型:

在Android中,可以使用RecyclerView来实现一个消息列表。以下是一个简单的实现示例,包括如何发送和删除消息。

步骤一:设置RecyclerView

1.添加依赖项:确保在你的build.gradle文件中添加了RecyclerView的依赖项。

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
}

2.创建消息数据类:

public class Message {  
    private String content;  

    public Message(String content) {  
        this.content = content;  
    }  

    public String getContent() {  
        return content;  
    }  

    public void setContent(String content) {  
        this.content = content;  
    }  
}  

3.创建RecyclerView的布局文件(res/layout/activity_main.xml):

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

    <androidx.recyclerview.widget.RecyclerView  
        android:id="@+id/recyclerView"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"/>  

    <EditText  
        android:id="@+id/messageInput"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_alignParentBottom="true"  
        android:hint="Type a message"/>  

    <Button  
        android:id="@+id/sendButton"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentBottom="true"  
        android:layout_alignParentRight="true"  
        android:text="Send"/>  

</RelativeLayout>  

4.创建RecyclerView的单个项布局文件(res/layout/item_message.xml):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content"  
    android:orientation="horizontal"  
    android:padding="10dp">  

    <TextView  
        android:id="@+id/messageContent"  
        android:layout_width="0dp"  
        android:layout_height="wrap_content"  
        android:layout_weight="1"/>  

    <Button  
        android:id="@+id/deleteButton"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Delete"/>  

</LinearLayout> 

5.创建RecyclerView的适配器:

public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MessageViewHolder> {  
    private List<Message> messageList;  
    private OnDeleteClickListener onDeleteClickListener;  

    public MessageAdapter(List<Message> messageList, OnDeleteClickListener onDeleteClickListener) {  
        this.messageList = messageList;  
        this.onDeleteClickListener = onDeleteClickListener;  
    }  

    @NonNull  
    @Override  
    public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {  
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_message, parent, false);  
        return new MessageViewHolder(view);  
    }  

    @Override  
    public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {  
        Message message = messageList.get(position);  
        holder.messageContent.setText(message.getContent());  
        holder.deleteButton.setOnClickListener(v -> onDeleteClickListener.onDeleteClick(position));  
    }  

    @Override  
    public int getItemCount() {  
        return messageList.size();  
    }  

    static class MessageViewHolder extends RecyclerView.ViewHolder {  
        TextView messageContent;  
        Button deleteButton;  

        public MessageViewHolder(@NonNull View itemView) {  
            super(itemView);  
            messageContent = itemView.findViewById(R.id.messageContent);  
            deleteButton = itemView.findViewById(R.id.deleteButton);  
        }  
    }  

    public interface OnDeleteClickListener {  
        void onDeleteClick(int position);  
    }  
} 

6.在活动中设置RecyclerView和适配器(MainActivity.java):

public class MainActivity extends AppCompatActivity {  
    private RecyclerView recyclerView;  
    private MessageAdapter messageAdapter;  
    private List<Message> messageList;  
    private EditText messageInput;  
    private Button sendButton;  

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

        recyclerView = findViewById(R.id.recyclerView);  
        messageInput = findViewById(R.id.messageInput);  
        sendButton = findViewById(R.id.sendButton);  

        messageList = new ArrayList<>();  
        messageAdapter = new MessageAdapter(messageList, position -> {  
            messageList.remove(position);  
            messageAdapter.notifyItemRemoved(position);  
        });  

        recyclerView.setLayoutManager(new LinearLayoutManager(this));  
        recyclerView.setAdapter(messageAdapter);  

        sendButton.setOnClickListener(v -> {  
            String content = messageInput.getText().toString();  
            if (!content.isEmpty()) {  
                messageList.add(new Message(content));  
                messageAdapter.notifyItemInserted(messageList.size() - 1);  
                messageInput.setText("");  
            }  
        });  
    }  
}  

通过以上步骤,你就可以实现一个可以发送和删除消息的RecyclerView列表了。


操作:

我们根据对话内容依次进行以下操作:

  • 在build.gradle文件中添加RecyclerView的依赖项
  • 新建一个Message类并填入相应内容(注意保留创建文件时自带的第一行包名)
  • 将RecycleView组件添加进activity_main.xml文件中
  • 新建一个item_message.xml文件来创建RecycleView中每一条消息的组件和布局
  • 新建一个MessageAdapter类并填入相应内容(注意保留创建文件时自带的第一行包名),以创建RecyclerView的适配器
  • 在MainActivity.java文件中填入相应内容来将适配器应用在RecycleView上,并设置相关按钮的监听器

编译运行,可以得到一个带有发送和删除功能的消息列表。虽然我们只想让大模型实现第一个功能点,但大模型聪明地直接给出了整个示例,最终结果如下图所示:

3-10

添加几个消息之后,列表效果如下图所示:

列表也可以上下滑动:

3.9 网络相关功能

网络功能是现代移动应用不可或缺的一部分。掌握网络功能是实现应用与外部世界连接的重要一步。 本章将介绍如何使用Socket编程实现网络通信,以及如何使用okHttp库进行HTTP请求和数据交互。

3.9.1 Socket编程

Socket连接是计算机网络中实现进程间通信的一种方式。它是一种在网络上进行数据传输的抽象概念,使得不同计算机上的进程可以通过网络进行通信。简言之,Socket是一种通信的方式,可以用于在不同计算机之间进行数据传输。它包含了连接所需的相关信息,如IP地址、端口号等。

Socket通信可以分为两种主要方式:TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议)。TCP提供可靠的、面向连接的数据传输,而UDP则是无连接的、不可靠的传输方式。TCP连接是一种可靠的、面向连接的通信方式。它通过三次握手建立连接,然后进行数据传输。TCP提供了数据可靠性和顺序性,适用于需要确保数据完整性的场景,如文件传输、网页浏览等。UDP连接是一种无连接的通信方式,不保证数据的可靠性和顺序性。它适用于实时性要求较高、对数据传输实时性要求较高的场景,如音视频传输、实时游戏等。 Socket编程是指使用Socket API进行网络编程的过程,它可以在各种编程语言中进行。通过Socket编程,可以实现客户端和服务器端之间的通信,实现数据的发送和接收。

在Android中Socket连接的基本步骤包括: - 创建Socket对象:指定服务器的IP地址和端口号。 - 获取输入输出流:用于接收和发送数据。 - 进行数据传输:通过输入输出流读写数据。 - 关闭连接:通信结束后,关闭Socket和相关流。

服务器端将在指定的端口上监听客户端的连接请求,并与客户端进行通信:

import java.io.BufferedReader;  
import java.io.InputStreamReader;  
import java.io.OutputStream;  
import java.io.PrintWriter;  
import java.net.ServerSocket;  
import java.net.Socket;  

public class SocketServer {  
    public static void main(String[] args) {  
        int port = 12345; // 服务器端口号  

        try (ServerSocket serverSocket = new ServerSocket(port)) {  
            System.out.println("服务器启动,等待客户端连接...");  

            // 等待客户端连接  
            Socket clientSocket = serverSocket.accept();  
            System.out.println("客户端连接成功:" + clientSocket.getInetAddress().getHostAddress());  

            // 获取输入输出流  
            BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));  
            PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);  

            // 读取客户端发送的数据  
            String message = reader.readLine();  
            System.out.println("收到客户端消息:" + message);  

            // 发送响应给客户端  
            writer.println("服务器已收到消息:" + message);  

            // 关闭连接  
            reader.close();  
            writer.close();  
            clientSocket.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  

客户端将连接到服务器,并发送一条消息,然后接收服务器的响应。

import java.io.BufferedReader;  
import java.io.InputStreamReader;  
import java.io.OutputStream;  
import java.io.PrintWriter;  
import java.net.Socket;  

public class SocketClient {  
    public static void main(String[] args) {  
        String serverAddress = "127.0.0.1"; // 服务器IP地址  
        int port = 12345; // 服务器端口号  

        try (Socket socket = new Socket(serverAddress, port)) {  
            System.out.println("连接到服务器:" + serverAddress);  

            // 获取输入输出流  
            PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);  
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  

            // 发送数据到服务器  
            String message = "Hello, Server!";  
            writer.println(message);  
            System.out.println("发送消息到服务器:" + message);  

            // 读取服务器的响应  
            String response = reader.readLine();  
            System.out.println("收到服务器响应:" + response);  

            // 关闭连接  
            reader.close();  
            writer.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  

3.9.2 HTTP/HTTPS基础

在Android应用开发中,网络操作是非常常见的需求,如访问Web服务、下载文件和上传数据等。我们需要理解网络基础概念从而开发高效的网络应用。

HTTP(Hypertext Transfer Protocol)是无状态的、应用层的协议,用于传输超文本数据(如HTML)。它是Web应用最常用的协议。HTTPS(Hypertext Transfer Protocol Secure)是HTTP的加密版本,通过SSL/TLS协议进行加密,确保数据传输的安全性。 常见的HTTP请求方法包括:(1)GET:从服务器请求数据。(2)POST:向服务器发送数据。(3)PUT:向服务器更新数据。(4)DELETE:从服务器删除数据。

在网络操作中,数据处理也是关键步骤,包括数据的解析、转换和存储。其中,JSON是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它是Android应用中最常用的数据格式,如:

{  
    "name": "John",  
    "age": 30,  
    "city": "New York"  
}  

在Android中,可以很方便地对其进行解析,如:

JSONObject jsonObject = new JSONObject(responseData);  
// 解析JSON数据  
String name = jsonObject.getString("name");  
int age = jsonObject.getInt("age");  
String city = jsonObject.getString("city");  

System.out.println("Name: " + name);  
System.out.println("Age: " + age);  
System.out.println("City: " + city);  

3.9.3 大模型在获取HTTPS内容中的应用

我们将要写一个简单的应用,实现获取百度网页的内容,并在界面中显示出来。

交互过程1:需求输入

用户:

我想获取百度网页的内容,并在界面中显示出来,应该怎么做?


大模型:

要在Android应用中获取百度网页的内容并显示在界面中,可以使用以下步骤: - 使用HttpURLConnection或OkHttp获取网页的HTML内容。 - 将获取到的HTML内容在WebView中显示出来。

下面是一个完整的示例,包括如何使用OkHttp获取网页内容并在WebView中显示。

步骤1:添加OkHttp依赖

在你的build.gradle文件中添加OkHttp的依赖:

dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.3' }

步骤2:在布局文件中添加WebView

在你的res/layout/activity_main.xml文件中添加一个WebView:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical">  

    <WebView  
        android:id="@+id/webview"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" />  
</LinearLayout>  

步骤3:在MainActivity中获取网页内容并显示

在你的MainActivity中编写代码来获取百度网页内容并显示在WebView中:

import android.os.AsyncTask;  
import android.os.Bundle;  
import android.webkit.WebView;  
import androidx.appcompat.app.AppCompatActivity;  
import okhttp3.OkHttpClient;  
import okhttp3.Request;  
import okhttp3.Response;  

public class MainActivity extends AppCompatActivity {  

    private WebView webView;  

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

        webView = findViewById(R.id.webview);  
        webView.getSettings().setJavaScriptEnabled(true);  

        new FetchWebPageTask().execute("https://www.baidu.com");  
    }  

    private class FetchWebPageTask extends AsyncTask<String, Void, String> {  

        @Override  
        protected String doInBackground(String... urls) {  
            String url = urls[0];  
            OkHttpClient client = new OkHttpClient();  
            Request request = new Request.Builder()  
                    .url(url)  
                    .build();  

            try (Response response = client.newCall(request).execute()) {  
                if (response.isSuccessful()) {  
                    return response.body().string();  
                }  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            return null;  
        }  

        @Override  
        protected void onPostExecute(String result) {  
            if (result != null) {  
                webView.loadDataWithBaseURL(null, result, "text/html", "UTF-8", null);  
            }  
        }  
    }  
} 

添加OkHttp依赖:在build.gradle文件中添加OkHttp依赖,以便我们可以使用OkHttp库来发送HTTP请求。

添加WebView:在布局文件中添加一个WebView,用于显示网页内容。 获取网页内容并显示:

  • 在MainActivity中,使用AsyncTask来执行网络请求。AsyncTask在后台线程中执行网络请求,并在主线程中更新UI。
  • 使用OkHttp发送HTTP请求,获取百度网页的HTML内容。
  • 在onPostExecute方法中,将获取到的HTML内容加载到WebView中显示。

这种方法简单有效,可以帮助你在Android应用中获取并显示百度网页的内容。如果需要处理更多复杂的HTML内容,可以考虑使用Jsoup等HTML解析库。


操作:

我们将内容复制到对应的文件中,并点击编译运行,发现并没有按照预期显示出百度页面的内容,我们查看LogCat发现错误为:

java.lang.SecurityException: Permission denied (missing INTERNET permission?)

我们继续询问大模型如何解决问题。


交互过程2:报错解决

用户:

报错为java.lang.SecurityException: Permission denied (missing INTERNET permission?),如何解决?


大模型:

这个错误表明应用程序缺少访问网络的权限。你需要在Android项目的AndroidManifest.xml文件中声明INTERNET权限。

步骤:在AndroidManifest.xml文件中添加INTERNET权限

打开你的AndroidManifest.xml文件,并在标签内添加以下权限声明: <uses-permission android:name="android.permission.INTERNET" />, 通过这行代码声明了应用程序需要访问互联网的权限。 这样就可以解决java.lang.SecurityException: Permission denied (missing INTERNET permission?)的错误,并正确加载和显示百度网页的内容了。如果还有其他问题或需要进一步的帮助,请告诉我。

操作:

我们在对应文件中声明网络权限,并再次编译运行程序,此时便可以看到百度的页面。 编译运行程序后看到的界面如下图所示:

3-11

可以看到,确实打开了百度的页面,但是图片没有成功加载。这是由于我们为了演示okhttp3的使用方法而手动渲染了字符串,于是图片的加载被忽略了。实际上直接使用WebView的内置功能即可正常显示页面:

webView = findViewById(R.id.webView);  

// 启用JavaScript  
WebSettings webSettings = webView.getSettings();  
webSettings.setJavaScriptEnabled(true);  

// 确保在WebView中打开链接而不是使用外部浏览器  
webView.setWebViewClient(new WebViewClient());  

// 加载百度首页  
webView.loadUrl("https://www.baidu.com"); 

3-12

3.10 保存和恢复数据

保存和恢复数据也是移动应用开发过程中很重要的功能,一般的开发过程中主要有如下几个手段来保存数据。

  1. SharedPreferences: 轻量级存储,基于XML文件存储键值对数据,适合存储少量的简单数据,如用户偏好设置、登录状态等。通过SharedPreferences对象获取Editor实例,使用putX()方法存储数据,并通过commit()apply()提交。通过SharedPreferencesgetX()方法直接读取数据。
  2. 文件存储:适用于存储大量数据或非结构化数据,如图片、音频、视频等。通过ContextopenFileOutput()openFileInput()方法读写文件。恢复时只需要直接读取存储的文件内容就可以。

  3. SQLite数据库:轻量级关系型数据库,适合存储结构化数据,支持复杂查询。通过SQLiteDatabase对象进行数据库操作,包括创建表、插入、查询、更新和删除数据。通过查询操作恢复数据,这里需要大家回顾一下数据库的一些基本操作了。

  4. ContentProvider:用于在多个应用之间共享数据,提供标准化的数据访问接口。通过实现ContentProvider类,定义数据结构和访问权限。其他应用通过ContentResolver访问和恢复数据。

  5. 网络存储:现代的应用开发还有一种比较常用的方式,就是将数据存储在远程服务器,适合需要备份或跨设备同步的场景,很多应用都支持这一模式了。通过网络请求库(如Retrofit、OkHttp)与服务器交互,发送和接收数据。需要使用的时候从服务器下载数据就可以。另外现在还有很多云服务或者云存储的提供商,注册之后就可以很方便的使用数据存储服务了,大家都可以去尝试一下,例如腾讯、七牛云等。

下面我们来看看在前面的Activity跳转案例中,怎么在跳转过程中实现数据的保存和恢复,怎们与大模型交互实现相关功能。

用户:

如何实现数据的保存和恢复?


是的,我们可以如此直截了当的问大模型,因为大模型前面已经有了我们应用的上下文信息了,它会基于相关信息进行回答。

大模型:

在Android中,实现数据的保存和恢复通常涉及几个关键步骤。这里我们将通过示例来展示如何在Activity之间实现数据的保存和恢复,即使在屏幕旋转、程序退出到后台再返回时,也能保持数据的完整性。

1.使用onSaveInstanceState()和onRestoreInstanceState() 这两个方法是Activity生命周期中的回调,用于在Activity即将被销毁时保存数据,并在Activity重新创建时恢复数据。

• 保存数据:

@Override  
protected void onSaveInstanceState(Bundle outState) {  
    super.onSaveInstanceState(outState);  
    // 保存需要恢复的数据  
    outState.putString("my_key", "my_value");  
}  
•   恢复数据:
// Java
@Override  
protected void onRestoreInstanceState(Bundle savedInstanceState) {  
    super.onRestoreInstanceState(savedInstanceState);  
    // 在创建视图之后恢复数据  
    String myValue = savedInstanceState.getString("my_key");  
    // 使用myValue更新UI或进行其他操作  
}  

2.使用SharedPreferences

SharedPreferences是一个轻量级的存储类,用于保存应用程序的常用配置。

• 保存数据:

SharedPreferences sharedPreferences = getSharedPreferences("my_preferences", MODE_PRIVATE);  
SharedPreferences.Editor editor = sharedPreferences.edit();  
editor.putString("my_key", "my_value");  
editor.apply();  

• 恢复数据:

SharedPreferences sharedPreferences = getSharedPreferences("my_preferences", MODE_PRIVATE);  
String myValue = sharedPreferences.getString("my_key", null);  
// 使用myValue更新UI或进行其他操作  

3.使用onPause()和onResume()

如果你需要在Activity暂停和恢复时保存和恢复数据,可以使用onPause()和onResume()方法。

• 保存数据:

@Override  
protected void onPause() {  
super.onPause();  
// 在这里保存数据到SharedPreferences或其他持久化存储  
}  

• 恢复数据:

@Override  
protected void onResume() {  
    super.onResume();  
    // 在这里从SharedPreferences或其他持久化存储恢复数据  
}  

注意事项:

• onSaveInstanceState()和onRestoreInstanceState()主要用于在Activity被系统销毁(如屏幕旋转)时保存和恢复临时数据,不应用于持久化存储。

• SharedPreferences适用于存储少量、简单的数据,如用户设置、得分等。

• 对于更复杂的数据结构或需要跨多个组件共享的数据,考虑使用数据库(如Room)或ViewModel与LiveData。

• 在保存和恢复数据时,始终考虑数据的完整性和安全性。

• 避免在onSaveInstanceState()中保存大量数据,因为它可能会导致性能问题或崩溃。

结合这些方法和注意事项,你可以根据应用程序的需求选择最适合的数据保存和恢复策略。


操作: 大模型为我们提供了三种保存和恢复数据的方法,还告诉了我们相应的注意事项,我们可以根据需求进行使用。对于这个简单的APP,我们在创建应用时就确定了数据,而对于复杂的、动态添加数据的APP,我们就必须注意数据的保存与恢复,防止APP进入后台或屏幕旋转等时候数据丢失。当然,对于更加复杂的数据保存,如使用sqlite数据库Room组件等,我们还可以继续在大模型的帮助下看看怎么能够完成,那部分就留给读者自己去探索了。

3-5