Bootstrap

Android Studio使用ArcGIS Runtime SDK for Android做地图软件

Android Studio使用ArcGIS Runtime SDK for Android做地图软件

基础版,完整版在完成
需要准备的内容(本项目不提供数据,以及运行截图)

地图文件:.shx .shp .dbf .prj
文本文件 .dbf 转.txt 本项目使用名字是data

1、下载ArcGIS Runtime SDK for Android

链接如下:下载v100.15.5
https://developers.arcgis.com/android/downloads/
下载ArcGIS Runtime SDK for Android
选择Download(107 MB);

2、创建项目

打开Android studio
在这里插入图片描述
选择New Project

在这里插入图片描述

选择No Activity

在这里插入图片描述
写项目名字,路径,包名,语言,SDK,build configuration language
本项目的SDK:API 24(“Nougat”; Android 7.0)
本项目语言:Java
本项目build configuration language:Groovy DSL (build.gradle)
本项目名称为:testApplication
本项目包名为:com.test.testApplication

提示:下面的MyApplication是演示作用
点击finish,等待加载完成,这个是默认目录
在这里插入图片描述
在app目录下,也就是和src同级创建libs
在这里插入图片描述

3、导入到libs

下载完成后打开压缩包里面应该有一下文件
在这里插入图片描述
打开libs/aar 把里面的内容解压出来
在这里插入图片描述
解压后,把这两个文件粘贴到项目文件的libs中

在这里插入图片描述

4、配置文件(加载ArcGIS Runtime SDK for Android)

打开app下的build.gradle修改以下内容(不要打开项目级别的)

//修改后
plugins {
    alias(libs.plugins.android.application)
}

    android {
    namespace 'com.test.myapplication'
    compileSdk 34

        packagingOptions {
            exclude 'META-INF/DEPENDENCIES'
        }
    defaultConfig {
        applicationId "com.test.testApplication"
        minSdk 23
        targetSdk 34
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    android.applicationVariants.all {
        variant ->
            variant.outputs.all {
                outputFileName = "test.apk"
            }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets{
        main{
            jniLibs.srcDirs = ['libs']
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'com.esri.arcgisruntime:arcgis-android:100.15.5'
    implementation 'org.osmdroid:osmdroid-android:6.1.11'
    implementation 'androidx.annotation:annotation:1.8.0';
    implementation libs.appcompat
    implementation libs.material
    implementation libs.play.services.maps
    implementation libs.annotation.jvm
    testImplementation libs.junit
    androidTestImplementation libs.ext.junit
    androidTestImplementation libs.espresso.core
}

打开项目级别的settings.gradle

//修改后的
pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven { url = uri("https://esri.jfrog.io/artifactory/arcgis") }
    }
}

rootProject.name = "Test Application"
include ':app'

5、创建asserts文件夹

本文件夹应该与res同级

本文件夹放入的是地图文件

6、创建layout文件夹和raw文件

两个文件夹都应当在res文件夹下

raw 文件的内容

应该有一个data.txt的文件
在这里插入图片描述

layout文件夹下的内容

index.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">

    <com.esri.arcgisruntime.mapping.view.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"></com.esri.arcgisruntime.mapping.view.MapView>

    <Button
        android:id="@+id/shrink"
        android:layout_width="48dp"
        android:layout_height="58dp"
        android:text="-"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.931"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.435" />
    <Button
        android:id="@+id/magnify"
        android:layout_width="48dp"
        android:layout_height="58dp"
        android:text="+"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.931"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.325" />

    <EditText
        android:id="@+id/editTextText"
        android:layout_width="379dp"
        android:layout_height="55dp"
        android:background="#ffffff"
        android:ems="10"
        android:padding="15dp"
        android:hint="请输入地名"
        android:inputType="text"
        android:rotationX="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.023" />

    <Button
        android:id="@+id/Attribute_queries"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:onClick="Attribute_queries"
        android:text="属性查询"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.05"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.976" />

    <Button
        android:id="@+id/Load_layer"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:text="加载矢量图层"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.943"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.976" />

</androidx.constraintlayout.widget.ConstraintLayout>
input_select.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">

    <Button
        android:id="@+id/return_button1"
        android:layout_width="91dp"
        android:layout_height="49dp"
        android:text="首页"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.115"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.054" />

    <Button
        android:id="@+id/submit_button1"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:text="提交"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.879" />

    <Button
        android:id="@+id/select_button"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:text="选择输入"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.769" />

    <EditText
        android:id="@+id/editTextText2"
        android:layout_width="327dp"
        android:layout_height="44dp"
        android:ems="10"
        android:hint="请输入列名"
        android:inputType="text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.511"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.189" />

    <EditText
        android:id="@+id/editTextText3"
        android:layout_width="328dp"
        android:layout_height="49dp"
        android:ems="10"
        android:hint="请输入列值"
        android:inputType="text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.506"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.329" />
</androidx.constraintlayout.widget.ConstraintLayout>
attribute_select.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">

    <Spinner
        android:id="@+id/value_spinner"
        android:layout_width="337dp"
        android:layout_height="57dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.391" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="267dp"
        android:layout_height="37dp"
        android:text="请选择列名"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.256"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.188" />

    <Spinner
        android:id="@+id/line_spinner"
        android:layout_width="337dp"
        android:layout_height="57dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.232" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="267dp"
        android:layout_height="37dp"
        android:text="请选择值"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.256"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.342" />

    <Button
        android:id="@+id/return_button"
        android:layout_width="91dp"
        android:layout_height="49dp"
        android:text="返回"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.115"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.054" />

    <Button
        android:id="@+id/submit_button"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:text="提交"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.879" />

    <Button
        android:id="@+id/input_button"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:text="直接输入"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.769" />
</androidx.constraintlayout.widget.ConstraintLayout>

7、创建Java类

Main类
package com.test.testApplication;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.data.Feature;
import com.esri.arcgisruntime.data.FeatureQueryResult;
import com.esri.arcgisruntime.data.QueryParameters;
import com.esri.arcgisruntime.data.ShapefileFeatureTable;
import com.esri.arcgisruntime.geometry.Envelope;
import com.esri.arcgisruntime.geometry.GeometryEngine;
import com.esri.arcgisruntime.layers.FeatureLayer;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.view.BackgroundGrid;
import com.esri.arcgisruntime.mapping.view.MapView;
import com.esri.arcgisruntime.symbology.SimpleFillSymbol;
import com.esri.arcgisruntime.symbology.SimpleLineSymbol;
import com.esri.arcgisruntime.symbology.SimpleRenderer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

public class Main extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private MapView mapView; // 地图视图组件
    private FeatureLayer mFeatureLayer; // 要素图层
    // 需要检查的文件列表(实际只需要.shx .shp .dbf .prj)
    private final List<String> filesToCheck = Arrays.asList(
            "测试数据.shp",
            "测试数据.shp.xml",
            "测试数据.shx",
            "测试数据.dbf",
            "测试数据.prj",
            "测试数据.shp.DESKTOP-AVS74CA.17660.18312.sr.lock",
            "测试数据.cpg",
            "测试数据.sbn",
            "测试数据.sbx"
    );

    // onCreate 方法,当活动创建时调用
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.index); // 设置布局文件

        // 设置按钮点击事件
        setupButtonClickListeners();

        // 检查写入外部存储的权限
        handlePermission();

        // 初始化地图视图
        mapView = findViewById(R.id.mapView);
        mapView.setAttributionTextVisible(false);

        // 复制 Shapefile 文件到内部存储
        copyShapefilesToInternalStorage(this);

        // 获取 Shapefile 文件的路径
        String shapefilePath = getInternalStorageFilePath(this, "测试数据.shp");

        // 设置 Shapefile 特征表
        setupShapefileFeatureTable(shapefilePath);

        // 检查文件是否为空
        for (String fileName : filesToCheck) {
            checkNullFile(fileName);
        }

        // 设置地图视图的视点
        mapView.setViewpointGeometryAsync(mFeatureLayer.getFullExtent());

        // 设置地图背景
        setBackgroundGrid();
    }

    // 设置按钮点击事件
    private void setupButtonClickListeners() {
        // 放大按钮
        Button magnifyButton = findViewById(R.id.magnify);
        magnifyButton.setOnClickListener(v -> handleMagnifyButtonClick());

        // 缩小按钮
        Button shrinkButton = findViewById(R.id.shrink);
        shrinkButton.setOnClickListener(v -> handleShrinkButtonClick());

        // 提交按钮,用于启动另一个活动
        Button submitButton = findViewById(R.id.Attribute_queries);
        submitButton.setOnClickListener(v -> {
            Intent intent = new Intent(Main.this, Attribute_select.class);
            startActivityForResult(intent, 1);
        });

        // 加载图层按钮,可以添加加载图层的代码
        Button loadLayerButton = findViewById(R.id.Load_layer);
        loadLayerButton.setOnClickListener(v -> {
            // 可以添加加载图层的代码
        });
    }

    // onActivityResult 方法,处理从另一个活动返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == 1) {
            String fieldName = data.getStringExtra("fieldName");
            String fieldValue = data.getStringExtra("fieldValue");

            // 高亮显示符合条件的要素
            highlightFeatures(fieldName, fieldValue);
        }
    }

    // 高亮显示符合条件的要素
    private void highlightFeatures(String fieldName, String fieldValue) {
        // 确保图层已加载
        if (mFeatureLayer != null && mFeatureLayer.getFeatureTable() != null) {
            // 清除之前的选择
            mFeatureLayer.clearSelection();

            // 设置查询条件
            String queryExpression = fieldName + " = '" + fieldValue + "'";
            QueryParameters queryParameters = new QueryParameters();
            queryParameters.setWhereClause(queryExpression);

            // 设置选择模式为新选择
            FeatureLayer.SelectionMode selectionMode = FeatureLayer.SelectionMode.NEW;

            // 发起异步查询和选择操作
            final ListenableFuture<FeatureQueryResult> future = mFeatureLayer.selectFeaturesAsync(queryParameters, selectionMode);
            future.addDoneListener(() -> {
                try {
                    // 获取查询结果
                    FeatureQueryResult result = future.get();

                    // 检查查询结果是否为空
                    if (result == null || !result.iterator().hasNext()) {
                        // 如果没有查询结果,显示提示对话框
                        showAlertDialog("未找到要素");
                    } else {
                        // 处理查询结果并高亮显示要素
                        highlightFeaturesFromResult(result);
                    }

                } catch (InterruptedException | ExecutionException e) {
                    // 显示异常信息对话框
                    showAlertDialog("查询要素失败: " + e.getMessage());
                    Log.e("selected", "Select feature failed: " + e.getMessage());
                }
            });
        }
    }

    // 显示提示对话框
    private void showAlertDialog(String message) {
        // 在 UI 线程中执行
        runOnUiThread(() -> {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("提示");
            String pattern = "com.esri.arcgisruntime.ArcGISRuntimeException:";
            String pattern_error = "SQLite data type mismatch.: SQLite: 20\n" + "Data type mismatch";
            String message1 = message.replace(pattern, "");
            message1 = message1.replace(pattern_error, "数据类型不匹配");
            Log.d("showAlertDialog", "showAlertDialog: " + message1);
            builder.setMessage(message1);
            builder.setPositiveButton("确定", null);
            try {
                builder.show();
            } catch (WindowManager.BadTokenException e) {
                // 处理异常
                Log.e("AlertDialogError", "Unable to show AlertDialog", e);
            }
        });
    }

    // 处理查询结果并高亮显示要素
    private void highlightFeaturesFromResult(FeatureQueryResult result) {
        // 创建一个列表来存储查询到的要素
        List<Feature> features = new ArrayList<>();

        // 遍历查询结果,并将要素添加到列表中
        for (Feature feature : result) {
            features.add(feature);
        }

        // 在地图上选择和高亮显示这些要素
        if (!features.isEmpty()) {
//            mFeatureLayer.setSelectionColor(R.color.Green_200);
            mFeatureLayer.setSelectionWidth(1);
            mFeatureLayer.selectFeatures(features);
        }

        // 获取要素的范围,并调整地图视角以显示这些要素
        Envelope extent = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            extent = GeometryEngine.combineExtents(features.stream()
                    .map(Feature::getGeometry)
                    .filter(Objects::nonNull) // 过滤掉可能的null值
                    .collect(Collectors.toList()));
        }
        if (extent != null) {
            mapView.setViewpointGeometryAsync(extent, 100);
        }
    }

    // 处理放大按钮点击事件
    private void handleMagnifyButtonClick() {
        double currentScale = mapView.getMapScale();
        final double MAX_SCALE = currentScale * 5.0;
        final double MIN_SCALE = 1.0;

        double newScale = currentScale * 0.8; // 每次放大到原来的80%

        if (newScale > MAX_SCALE) {
            newScale = MAX_SCALE;
        } else if (newScale < MIN_SCALE) {
            newScale = MIN_SCALE;
        }

        mapView.setViewpointScaleAsync(newScale);
    }

    // 处理缩小按钮点击事件
    private void handleShrinkButtonClick() {
        double currentScale = mapView.getMapScale();
        final double MIN_SCALE = 1.0;
        final double MAX_SCALE = currentScale * 1.6;

        double newScale = currentScale * 1.2;

        if (newScale > MAX_SCALE) {
            newScale = MAX_SCALE;
        } else if (newScale < MIN_SCALE) {
            newScale = MIN_SCALE;
        }

        mapView.setViewpointScaleAsync(newScale);
    }

    // 处理权限请求
    private void handlePermission() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

    // 将资源文件复制到内部存储
    public boolean copyAssetToInternalStorage(Context context, String assetName, String destFileName) {
        if (destFileName == null) {
            destFileName = assetName;
        }

        File fileDir = context.getFilesDir();
        File destFile = new File(fileDir, destFileName);

        try (InputStream in = context.getAssets().open(assetName);
             FileOutputStream fos = new FileOutputStream(destFile)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = in.read(buffer)) > 0) {
                fos.write(buffer, 0, length);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    // 获取内部存储文件路径
    public String getInternalStorageFilePath(Context context, String fileName) {
        File fileDir = context.getFilesDir();
        File file = new File(fileDir, fileName);
        return file.getAbsolutePath();
    }

    // 检查文件是否为空
    public void checkNullFile(String filename) {
        try (RandomAccessFile reader = new RandomAccessFile(getInternalStorageFilePath(this, filename), "r")) {
            long fileLength = reader.length();
            if (fileLength > 0) {
                Log.d(TAG, filename + ":文件大小: " + fileLength + " 字节");
                byte[] bytes = new byte[10];
                reader.read(bytes);
            } else {
                Log.d(TAG, filename + "文件为空");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 设置 Shapefile 特征表
    private void setupShapefileFeatureTable(String shapefilePath) {
        ShapefileFeatureTable shapefileFeatureTable = new ShapefileFeatureTable(shapefilePath);
        shapefileFeatureTable.loadAsync();
        mFeatureLayer = new FeatureLayer(shapefileFeatureTable);
        ArcGISMap map = new ArcGISMap();

        // 设置符号和渲染器
        SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 1.0f);
        SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.GREEN, lineSymbol);
        SimpleRenderer renderer = new SimpleRenderer(fillSymbol);
        mFeatureLayer.setRenderer(renderer);

        map.getOperationalLayers().add(mFeatureLayer);
        mapView.setMap(map);

    }

    // 设置地图背景
    private void setBackgroundGrid() {
        BackgroundGrid mainBackgroundGrid = new BackgroundGrid();
        mainBackgroundGrid.setColor(Color.WHITE);
        mainBackgroundGrid.setGridLineColor(Color.WHITE);
        mainBackgroundGrid.setGridLineWidth(0);
        mapView.setBackgroundGrid(mainBackgroundGrid);
    }

    // 复制 Shapefile 文件到内部存储
    private void copyShapefilesToInternalStorage(Context context) {
        for (String file : filesToCheck) {
            boolean copied = copyAssetToInternalStorage(context, file, file);
            Log.d(TAG, file + "文件复制完毕");
            if (!copied) {
                Log.e(TAG, "Failed to copy: " + file);
            }
        }
    }
}

Attribute_select类
package com.test.testApplication;

import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;

import androidx.appcompat.app.AppCompatActivity;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;

public class Attribute_select extends AppCompatActivity {
    private List<String> field_value;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.attribute_select);

        Button return_button = findViewById(R.id.return_button);
        return_button.setOnClickListener(v -> finish());
        List<String> field_Name = read_firstName();
        Spinner line_spinner = findViewById(R.id.line_spinner);
        ArrayAdapter<String> fieldsAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, field_Name);
        fieldsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        line_spinner.setAdapter(fieldsAdapter);
        Spinner value_spinner = findViewById(R.id.value_spinner);
        line_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                field_value = readAndSortValuesSkippingFirstLine(position);
                ArrayAdapter<String> valuesAdapter = new ArrayAdapter<>(Attribute_select.this, android.R.layout.simple_spinner_item, field_value);
                valuesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                value_spinner.setAdapter(valuesAdapter);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });
        Button sumbit_button=findViewById(R.id.submit_button);
        sumbit_button.setOnClickListener(v -> {
            String selectedFieldName = line_spinner.getSelectedItem().toString();
            String selectedFieldValue = value_spinner.getSelectedItem().toString();
            Intent intent = new Intent();
            intent.putExtra("fieldName", selectedFieldName);
            intent.putExtra("fieldValue", selectedFieldValue);
            setResult(RESULT_OK, intent);
            finish();
        });

        Button inputButton=findViewById(R.id.input_button);
        inputButton.setOnClickListener(v -> {
            Intent intent = new Intent(Attribute_select.this, Input_select.class);
            startActivityForResult(intent, 1);
        });
    }
    public List<String> read_firstName() {
        Resources res = getResources();
        InputStream is = res.openRawResource(R.raw.data);
        List<String> namesList = new ArrayList<>(); // 使用ArrayList以支持删除操作
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
            String firstLine = reader.readLine();
            if (firstLine != null) {
                String[] namesArray = firstLine.split(",");
                for (String name : namesArray) {
                    namesList.add(name.trim()); // 将分割后的字符串添加到列表中
                }
                // 删除列表中的"FID"元素,如果存在
                namesList.remove("FID");
            } else {
                throw new Exception("文件是空的");
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("读取文件时发生错误:" + e.getMessage());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return namesList;
    }
    public List<String> readAndSortValuesSkippingFirstLine(int position) {
        List<String> valuesList = new ArrayList<>();
        //在活动中
        Resources res = getResources();
        InputStream is = null;
        try {
            is = res.openRawResource(R.raw.data);
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            String line;
            // 标记是否正在读取第一行
            boolean isFirstLine = true;
            while ((line = reader.readLine()) != null) {
                if (isFirstLine) {
                    isFirstLine = false;
                    continue;
                }
                String[] values = line.split(",");
                if (position+1 < values.length) {
                    String value = values[position+1].trim();
                    if (!value.isEmpty()) {
                        valuesList.add(value);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // 使用LinkedHashSet来去除重复的值
        LinkedHashSet<String> uniqueValues = new LinkedHashSet<>(valuesList);
        // 将LinkedHashSet转换为List以便排序
        List<String> sortedValues = new ArrayList<>(uniqueValues);

        // 自定义比较器
        Collections.sort(sortedValues, (o1, o2) -> {
            // 检查是否为数字
            boolean isNumeric1 = o1.matches("\\d+");
            boolean isNumeric2 = o2.matches("\\d+");

            // 如果两个字符串都是数字
            if (isNumeric1 && isNumeric2) {
                return Integer.compare(Integer.parseInt(o1), Integer.parseInt(o2));
            }
            // 如果两个字符串都不是数字
            else if (!isNumeric1 && !isNumeric2) {
                return o1.compareTo(o2);
            }
            // 如果第一个字符串是数字,第二个不是
            else if (isNumeric1) {
                return -1; // 数字应该排在前面
            }
            // 如果第一个字符串不是数字,第二个是
            else {
                return 1; // 字符串应该排在后面
            }
        });

        return sortedValues;
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == 1) {
            String fieldName = data.getStringExtra("fieldName");
            String fieldValue = data.getStringExtra("fieldValue");
            Intent intent = new Intent();
            intent.putExtra("fieldName", fieldName);
            intent.putExtra("fieldValue", fieldValue);
            setResult(RESULT_OK, intent);
            finish();
        }
    }
}

Input_select类
package com.test.testApplication;

import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

public class Input_select extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.input_select);

        Button select_button= findViewById(R.id.select_button);
        Button ret_button= findViewById(R.id.return_button1);
        select_button.setOnClickListener(v -> finish());
        ret_button.setOnClickListener(v -> {
            // 创建一个Intent来启动首页Activity
            Intent intent = new Intent(Input_select.this, Main.class);
            // 可选:添加flags来清除任务栈,这样用户点击返回时不会回到当前Activity
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
            // 启动首页Activity
            startActivity(intent);
            // 结束当前Activity
            finish();
        });

        Button sumit_button = findViewById(R.id.submit_button1);
        sumit_button.setOnClickListener(v -> {
            EditText e1 = findViewById(R.id.editTextText2);
            EditText e2 = findViewById(R.id.editTextText3);
            String selectedFieldName = e1.getText().toString().trim().toUpperCase();
            String selectedFieldValue = e2.getText().toString().trim();
            if(selectedFieldName.isEmpty() | selectedFieldValue.isEmpty()){
                showAlertDialog();

            }else{
                Intent intent = new Intent();
                intent.putExtra("fieldName", selectedFieldName);
                intent.putExtra("fieldValue", selectedFieldValue);
                setResult(RESULT_OK, intent);
                finish();
            }
        });



    }
    private void showAlertDialog() {
        // 在 UI 线程中执行
        runOnUiThread(() -> {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("提示");
            builder.setMessage("列名或列值不能为空");
            builder.setPositiveButton("确定", null);
            try {
                builder.show();
            } catch (WindowManager.BadTokenException e) {
                Log.e("AlertDialogError", "Unable to show AlertDialog", e);
            }
        });
    }
}

8、修改AndroidManifest.xml文件

打开app下的AndroidManifest.xml

修改后的:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="Manifest.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestApplication"
        android:requestLegacyExternalStorage="true"
        tools:targetApi="31" >
        <activity android:name=".Attribute_select" />
        <activity android:name=".Input_select" />
        <activity android:name=".Main"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
</application>
</manifest>

9、修改暗夜和白天模式的样式

在本项目中,暗夜和白天模式的样式都应该一致
两个文件夹都在res下
在这里插入图片描述

打开values下的themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.TestApplication" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>

打开values-night下的themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!--  Base application theme.  -->
    <style name="Theme.TestApplication" parent="Theme.AppCompat.DayNight.NoActionBar">
        <!--  Primary brand color.  -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!--  Secondary brand color.  -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!--  Status bar color.  -->
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
        <item name="hintTextColor">@color/black</item>
        <!--  Customize your theme here.  -->
    </style>
</resources>

打开color文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="Green_200">#82FFDC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
</resources>

结语

当这些全部写完已经写完项目!
但是只有copyright的权利

搜索框和加载图层会再之后补充

©寥若晨星

;