安卓开发(5)一些小功能

安卓开发(0)前言&目录
主要是右上角的menu里面的按钮的功能实现。

写完一个页面别忘了去AndroidManifest.xml注册一下。

快捷记录功能实现

功能概述

快捷记录功能帮助用户在就诊过程中快速记录重要信息,自动保存并支持历史记录查看,即使软件卸载了笔记都还在。

核心文件

  • NoteEditActivity.java

  • activity_note_edit.xml

  • top_menu.xml

代码实现

布局文件(activity_note_edit.xml)

很简单的界面,一整个文字编辑界面。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:background="@android:color/white">

    <EditText
        android:id="@+id/note"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="top"
        android:hint="在此输入内容..."
        android:inputType="textMultiLine"
        android:scrollbars="vertical"
        android:scrollHorizontally="false"
        android:padding="16dp"
        android:textSize="16sp"
        android:lineSpacingExtra="4dp">
        <requestFocus />
    </EditText>

</ScrollView>
菜单配置(top_menu.xml)
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <!-- 其他菜单项省略 -->
    <item
        android:id="@+id/note"
        android:title="快捷记录"
        app:showAsAction="never" />
</menu>
主要功能实现(NoteEditActivity.java)

但是这个功能还有一个问题没有解决,就是键盘弹出的时候对界面的遮挡没有解决。一直以为这是一个任何笔记编辑界面都已经不需要考虑的问题,但是我始终没能解决😭😭。

  1. 文件读写:使用 openFileInputopenFileOutput 持久化存储笔记到私有目录。即使软件卸载了这个笔记也还在

  2. 时间戳:仅首次打开时在末尾添加 --- yyyy-MM-dd HH:mm:ss --- 分隔标记。

  3. 自动聚焦:启动后延迟请求焦点并弹出软键盘,确保光标在末尾、内容滚动到底部。

  4. 自动保存:在 onPauseonDestroy 时自动保存,防止内容丢失。

public class NoteEditActivity extends AppCompatActivity {
    
    private static final String TAG = "NoteEditActivity";
    private static final String FILE_NAME = "hospital_notes.txt";
    private EditText noteEditText;
    private ScrollView scrollView;
    private boolean timestampAdded = false;
    private boolean isKeyboardShowing = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_note_edit);

        // 初始化控件
        noteEditText = findViewById(R.id.note);
        scrollView = findViewById(R.id.scroll_view);

        // 设置系统边距(状态栏、导航栏)
        ViewCompat.setOnApplyWindowInsetsListener(scrollView, (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        // 设置键盘弹出监听
        setupKeyboardListener();

        // 加载已有内容
        loadNoteContent();

        // 添加时间戳(仅一次)
        addTimestamp();

        // 关键:延迟聚焦并弹出输入法
        focusOnEditText();
    }

    @Override
    protected void onPause() {
        super.onPause();
        saveNoteContent();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        saveNoteContent();
    }

    /**
     * 加载已有笔记内容,若文件不存在则创建
     */
    private void loadNoteContent() {
        try {
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(openFileInput(FILE_NAME), StandardCharsets.UTF_8)
            );
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            reader.close();

            noteEditText.setText(sb.toString());
            noteEditText.setSelection(sb.length()); // 光标在末尾

        } catch (IOException e) {
            Log.d(TAG, "文件不存在,将创建新文件: " + FILE_NAME);
            try {
                new File(getFilesDir(), FILE_NAME).createNewFile();
            } catch (IOException ex) {
                Log.e(TAG, "创建文件失败", ex);
            }
        }
    }

    /**
     * 保存当前笔记内容
     */
    private void saveNoteContent() {
        String content = noteEditText.getText().toString().trim();
        if (content.isEmpty()) return;

        try {
            FileOutputStream fos = openFileOutput(FILE_NAME, MODE_PRIVATE);
            fos.write(content.getBytes(StandardCharsets.UTF_8));
            fos.close();
            Log.d(TAG, "笔记内容已保存");
        } catch (IOException e) {
            Log.e(TAG, "保存失败", e);
        }
    }

    /**
     * 添加时间戳(仅首次进入时)
     */
    private void addTimestamp() {
        if (timestampAdded) return;

        String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
        String currentText = noteEditText.getText().toString();
        String separator = currentText.isEmpty() ? "" : "\n";
        String newText = currentText + separator + "--- " + timestamp + " ---\n";

        noteEditText.setText(newText);
        noteEditText.setSelection(newText.length()); // 光标移到末尾
        timestampAdded = true;
    }

    /**
     * 聚焦到输入框并弹出键盘,确保滚动到底部
     */
    private void focusOnEditText() {
        noteEditText.post(() -> {
            noteEditText.requestFocus();
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            if (imm != null) {
                imm.showSoftInput(noteEditText, 0); // 弹出键盘
            }
            // 延迟滚动,确保键盘弹出后再滚动
            scrollView.postDelayed(() -> {
                scrollToCursorPosition();
            }, 300);
        });
    }

}

功能调用流程

  1. 入口:在MainActivity的菜单中点击"快捷记录"选项

  2. 启动:通过Intent启动NoteEditActivity

  3. 加载:自动加载已有笔记内容,并添加当前时间戳

  4. 编辑:用户在EditText中输入内容

  5. 保存:在Activity暂停或销毁时自动保存内容到文件系统

保存全部地图功能

功能概述

"下载全部地图"功能将应用assets目录中的地图图片保存到设备相册,方便离线查看。

核心实现代码

1. 功能触发点

在MainActivity的菜单点击事件中触发:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    
    if (id == R.id.download_maps) {
        // 调用下载地图方法
        downloadMaps();
        return true;
    }
    // ...其他菜单项处理
}
2. 下载地图主方法
/**
 * 下载地图主方法
 * 1. 检查存储权限
 * 2. 有权限则执行复制操作
 * 3. 无权限则请求权限
 */
private void downloadMaps() {
    // 1. 检查权限
    if (hasStoragePermission()) {
        // 2. 执行下载
        copyAssetsToPictures();
    } else {
        // 3. 显示提示并请求权限
        Toast.makeText(this, "需要存储权限才能下载地图", Toast.LENGTH_SHORT).show();
        requestStoragePermission();
    }
}
3. 权限管理

注意这里不同的安卓版本需要写不同的权限。

/**
 * 检查是否有存储权限
 * 适配Android不同版本的权限模型
 */
boolean hasStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        // Android 13及以上版本
        return checkSelfPermission(Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED;
    } else {
        // Android 12及以下
        return checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }
}

/** 请求存储权限 */
void requestStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        requestPermissions(new String[]{Manifest.permission.READ_MEDIA_IMAGES}, 100);
    } else {
        requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
    }
}
4. 核心复制功能
/**
 * 从assets目录复制图片到Pictures目录
 */
void copyAssetsToPictures() {
    try {
        // 获取assets目录下的所有文件名
        String[] files = getAssets().list("");
        if (files != null) {
            // 创建目标文件夹
            File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
            File hospitalNaviDir = new File(picturesDir, "HospitalNavi");
            if (!hospitalNaviDir.exists()) {
                hospitalNaviDir.mkdirs();
            }

            int copiedCount = 0;
            // 遍历assets中的文件
            for (String filename : files) {
                // 只处理jpg图片文件
                if (filename.endsWith(".jpg")) {
                    // 复制文件
                    copyFile(filename, new File(hospitalNaviDir, filename));
                    copiedCount++;
                }
            }
            
            // 显示复制成功的提示
            Toast.makeText(this, "成功保存" + copiedCount + "张地图到相册", Toast.LENGTH_SHORT).show();
        }
    } catch (IOException e) {
        Log.e("MainActivity", "保存文件失败", e);
        Toast.makeText(this, "保存地图失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

功能流程

  1. 用户点击菜单中的"下载全部地图"选项

  2. 系统检查存储权限

    • 有权限:直接执行复制操作

    • 无权限:请求存储权限

  3. 权限被授予后,将assets中的所有jpg图片复制到设备Pictures/HospitalNavi目录

  4. 显示复制结果提示

停车找车功能实现

功能概述

用户在医院停车场停车后,通过拍照和定位记录当前停车位置。当定位成功之后,按钮变成可点击的,照片选中的时候,会记录下照片并显示,同时在地图上做出标记。再次进入这个页面,依然存在标记和照片。当软件进程结束,自动清理掉地图标记和照片。

核心文件

  • FindCarActivity.java

  • activity_findcar.xml

布局文件(activity_findcar.xml)

  1. 上半部分:地图显示

    • 使用 MapView 控件显示百度地图;

    • 高度占父容器的50%(通过 layout_weight="1" 实现);

    • 宽度占满,用于可视化停车位置。

  2. 下半部分:功能区域

    • 使用嵌套 LinearLayout 垂直布局,高度同样占50%;

    • 包含三个子控件:

      • ImageView:显示拍照记录的图片,固定大小,背景灰色;

      • Button:点击拍照并记录位置;

      • TextView:显示当前选中位置的坐标信息,文字居中。

<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停车找车(activity_findcar.xml)"
        android:textSize="8sp"
        android:gravity="center"
        android:padding="16dp"/>

    <!-- 上半部分:地图(占50%) -->
    <!-- 百度地图显示控件 -->
    <com.baidu.mapapi.map.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!-- 下半部分:图片和按钮(占50%) -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical"
        android:padding="16dp"
        android:gravity="center">

        <!-- 图片显示区域 -->
        <ImageView
            android:id="@+id/photoImageView"
            android:layout_width="220dp"
            android:layout_height="160dp"
            android:background="@color/grey"
            android:scaleType="centerCrop"/>

        <!-- 拍照按钮 -->
        <Button
            android:id="@+id/takePhotoButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="拍照记录位置"
            android:textSize="16sp" />

        <!-- 显示坐标信息 -->
        <TextView
            android:id="@+id/locationInfoText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textSize="14sp"
            android:gravity="center" />

    </LinearLayout>

</LinearLayout>

主要功能实现(FindCarActivity.java)

类与成员变量定义

主要是相关的变量,和使用的控件。

/**
 * 找车功能Activity类
 * 用于实现停车找车功能,包括拍照记录停车位置和在地图上显示位置
 */
public class FindCarActivity extends AppCompatActivity {
    private static final String TAG = "FindCarActivity";
    private static final String PREFS_NAME = "FindCarPrefs";
    private static final String KEY_IS_PARKING_RECORDED = "is_parking_recorded";
    private static final String KEY_PARKING_LATITUDE = "parking_latitude";
    private static final String KEY_PARKING_LONGITUDE = "parking_longitude";
    private static final String KEY_PARKING_ADDRESS = "parking_address";
    
    // 百度地图控件
    private MapView mMapView;
    private BaiduMap mBaiduMap;
    
    // 定位相关
    private LocationClient mLocationClient;
    private boolean isLocationComplete = false; // 定位是否完成
    
    // UI组件
    private Button takePhotoButton;
    private ImageView photoImageView;
    private TextView locationInfoText;
    
    // 记录的停车位置
    private LatLng parkingLocation;
    
    // 拍照相关
    private static final int REQUEST_IMAGE_CAPTURE = 1;
    private static final int REQUEST_CAMERA_PERMISSION = 100;
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001;
    private String currentPhotoPath;
    
    // SharedPreferences用于保存停车位置
    private SharedPreferences sharedPreferences;
    private boolean isFirstTime = true;
    
    // 其他方法省略
}
初始化与核心方法

地图初始化,显示、定位、显示位置与前面首页是一样的,不再赘述。

这里主要是拍照功能的实现,调用顺序,和实现的逻辑。

如果是第一次记录,才会执行下面的操作。如果已经有过记录,直接显示之前的记录。

/**
 * Activity创建时调用的方法
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_findcar);
    
    // 初始化SharedPreferences
    sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
    
    // 检查是否已经记录过停车位置
    isFirstTime = !sharedPreferences.getBoolean(KEY_IS_PARKING_RECORDED, false);
    
    // 初始化UI组件
    initUI();
    
    // 初始化百度地图控件
    initBaiduMap();
    
    // 如果不是第一次进入,直接显示之前记录的位置
    if (!isFirstTime) {
        showSavedParkingLocation();
    }
}

/**
 * 拍照并记录当前位置
 */
private void takePhotoAndRecordLocation() {
    // 检查相机权限
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        // 请求相机权限
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.CAMERA},
                REQUEST_CAMERA_PERMISSION);
    } else {
        // 启动拍照
        dispatchTakePictureIntent();
    }
}
定位监听器与停车位置记录
  1. 监听定位结果

  2. 防止重复记录:通过 isLocationComplete 标志确保只记录一次停车位置。

  3. 显示当前定位:将定位结果通过 MyLocationData 更新到地图上,显示蓝点。

  4. 地图标记与聚焦:使用自定义图标(car_marker)在地图上打点,并将地图中心移至该位置,缩放至18级。

  5. 持久化存储:将停车位置和地址保存到 SharedPreferences,实现退出页面不丢失。

  6. 更新UI提示用户:在界面显示位置信息,并弹出 Toast 提示“已记录停车位置”。

  7. 停止定位节省资源:记录完成后立即停止定位服务。

/**
 * 定位监听器
 */
public class MyLocationListener extends BDAbstractLocationListener {
    @Override
    public void onReceiveLocation(BDLocation location) {
        // 检查定位是否已完成
        if (isLocationComplete) {
            return;
        }
        
        if (location != null && mMapView != null && mBaiduMap != null) {
            int locType = location.getLocType();
            double latitude = location.getLatitude();
            double longitude = location.getLongitude();
            String addrStr = location.getAddrStr();
            
            // 检查定位类型是否有效
            if (locType == BDLocation.TypeGpsLocation || 
                locType == BDLocation.TypeNetWorkLocation || 
                locType == BDLocation.TypeOffLineLocation ||
                locType == BDLocation.TypeCacheLocation) {
                
                // 设置定位数据到地图上
                MyLocationData locData = new MyLocationData.Builder()
                        .accuracy(location.getRadius())
                        .direction(location.getDirection()).latitude(latitude)
                        .longitude(longitude).build();
                mBaiduMap.setMyLocationData(locData);
                
                // 保存停车位置
                parkingLocation = new LatLng(latitude, longitude);
                
                // 在地图上标记停车位置
                markParkingLocation(latitude, longitude);
                
                // 保存停车位置到SharedPreferences
                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putBoolean(KEY_IS_PARKING_RECORDED, true);
                editor.putFloat(KEY_PARKING_LATITUDE, (float) latitude);
                editor.putFloat(KEY_PARKING_LONGITUDE, (float) longitude);
                if (addrStr != null) {
                    editor.putString(KEY_PARKING_ADDRESS, addrStr);
                }
                editor.apply();
                
                // 更新UI显示位置信息
                String locationInfo = "停车位置:\n纬度: " + latitude + "\n经度: " + longitude;
                if (addrStr != null && !addrStr.isEmpty()) {
                    locationInfo += "\n地址: " + addrStr;
                }
                locationInfoText.setText(locationInfo);
                
                // 显示提示信息
                Toast.makeText(FindCarActivity.this, "已记录停车位置", Toast.LENGTH_SHORT).show();
                
                // 设置定位完成标志
                isLocationComplete = true;
                isFirstTime = false;
                
                // 停止定位以节省电量
                if (mLocationClient != null && mLocationClient.isStarted()) {
                    mLocationClient.stop();
                }
            } else {
                Log.e(TAG, "定位失败,错误码: " + locType);
                Toast.makeText(FindCarActivity.this, "定位失败,请检查GPS设置,错误码: " + locType, Toast.LENGTH_SHORT).show();
            }
        }
    }
}

/**
 * 在地图上标记停车位置
 */
private void markParkingLocation(double latitude, double longitude) {
    // 创建位置点
    LatLng point = new LatLng(latitude, longitude);
    
    // 构建Marker图标
    MarkerOptions markerOptions = new MarkerOptions()
            .position(point)
            .icon(BitmapDescriptorFactory.fromResource(R.drawable.car_marker)); // 使用自定义的车辆图标
    
    // 在地图上添加标记
    mBaiduMap.addOverlay(markerOptions);
    
    // 移动地图中心到标记位置
    MapStatus.Builder builder = new MapStatus.Builder();
    builder.target(point).zoom(18.0f);
    mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build()));
}
拍照相关方法
  1. 启动拍照dispatchTakePictureIntent() 创建拍照 Intent(Intent是一个消息传递对象,可用于请求操作),检查相机可用后,调用 createImageFile() 创建照片文件,并通过 FileProvider 安全传递文件路径,启动相机。

  2. 创建照片文件createImageFile() 在应用专属目录下生成带时间戳的临时 JPG 文件,并保存路径到 currentPhotoPath

  3. 显示照片缩略图setPic() 计算图片缩放比例,按 ImageView 尺寸加载合适大小的 Bitmap,并设置到 photoImageView 显示。

/**
 * 启动拍照Intent
 */
private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // 创建用于保存照片的文件
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            Toast.makeText(this, "创建照片文件失败", Toast.LENGTH_SHORT).show();
            return;
        }
        
        if (photoFile != null) {
            Uri photoURI = FileProvider.getUriForFile(this,
                    "com.example.hospitalnavi.fileprovider",
                    photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
}

/**
 * 创建用于保存照片的文件
 */
private File createImageFile() throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
            imageFileName,  /* 前缀 */
            ".jpg",         /* 后缀 */
            storageDir      /* 目录 */
    );
    
    currentPhotoPath = image.getAbsolutePath();
    return image;
}

/**
 * 显示照片缩略图
 */
private void setPic() {
    // 获取ImageView的尺寸
    int targetW = photoImageView.getWidth();
    int targetH = photoImageView.getHeight();
    
    // 获取照片的尺寸
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;
    
    // 计算缩放比例
    int scaleFactor = Math.min(photoW / targetW, photoH / targetH);
    
    // 解码照片文件
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    
    Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
    photoImageView.setImageBitmap(bitmap);
}
生命周期管理
/**
 * Activity恢复时调用
 */
@Override
protected void onResume() {
    super.onResume();
    if (mMapView != null) {
        mMapView.onResume();
    }
}

/**
 * Activity暂停时调用
 */
@Override
protected void onPause() {
    super.onPause();
    if (mMapView != null) {
        mMapView.onPause();
    }
}

/**
 * Activity销毁时调用
 */
@Override
protected void onDestroy() {
    super.onDestroy();
    // 停止并销毁定位客户端
    if (mLocationClient != null) {
        mLocationClient.stop();
        mLocationClient = null;
    }
    
    if (mMapView != null) {
        mBaiduMap.setMyLocationEnabled(false); // 关闭定位图层
        mMapView.onDestroy();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值