
倒计时应用开发实践:从零到完整功能实现
在移动应用开发中,倒计时功能是一个非常实用的场景,比如纪念日、考试、活动倒计时等。最近,我利用AI完成了一个基于 Android 的倒计时应用,它支持本地数据存储、动态计算剩余天数、支持添加、编辑和删除倒计时。
项目功能概述
这个倒计时应用的核心功能包括:
-
本地数据存储:使用 SQLite 数据库保存倒计时数据。
-
动态日期计算:实时计算目标日期与当前日期之间的剩余天数。
-
用户交互:支持添加、编辑和删除倒计时。
-
UI 更新:每秒更新当前时间和日期,并动态刷新倒计时天数。
核心技术点
1. SQLite 数据库的使用
在 Android 中,SQLite 是一个轻量级的嵌入式数据库,非常适合本地数据存储。我创建了一个 DatabaseHelper
类来管理数据库操作。
java
复制
java
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "countdown.db";
private static final int DATABASE_VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_TABLE = "CREATE TABLE countdowns (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
"title TEXT," +
"target_date INTEGER)";
db.execSQL(CREATE_TABLE);
}
public long addCountdown(CountdownItem item) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("title", item.getTitle());
values.put("target_date", item.getTargetDate().getTime());
long id = db.insert("countdowns", null, values);
db.close();
return id;
}
public List<CountdownItem> getAllCountdowns() {
List<CountdownItem> items = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT * FROM countdowns", null);
// 数据处理逻辑
return items;
}
}
-
表结构设计 :
countdowns
表包含id
(主键)、title
(标题)和target_date
(目标日期的时间戳)。 -
CRUD 操作 :通过
addCountdown
添加数据,getAllCountdowns
查询所有数据,updateCountdown
更新数据,deleteCountdown
删除数据。
2. 日期计算与格式化
日期计算是倒计时应用的核心功能之一。我使用了 Date
和 Calendar
类来处理日期,并通过 SimpleDateFormat
格式化日期和时间。
java
复制
typescript
public class DateUtils {
public static String formatDate(Date date) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault());
return dateFormat.format(date);
}
public static long getDaysBetween(Date startDate, Date endDate) {
long diff = endDate.getTime() - startDate.getTime();
return diff / (24 * 60 * 60 * 1000);
}
public static boolean isFutureDate(Date date) {
return date.after(new Date());
}
}
-
日期格式化 :将日期格式化为
yyyy/MM/dd
或HH:mm:ss
。 -
天数计算:通过时间戳差值计算剩余天数。
-
未来日期判断:确保用户选择的目标日期是未来的日期。
3. UI 动态更新
为了实现倒计时天数的动态更新,我使用了 Handler
和 Runnable
来每秒刷新 UI。
java
复制
ini
private void setupTimeUpdates() {
Handler timeHandler = new Handler();
Runnable timeRunnable = new Runnable() {
@Override
public void run() {
updateDateTime();
timeHandler.postDelayed(this, 1000);
}
};
timeHandler.postDelayed(timeRunnable, 1000);
}
private void updateDateTime() {
Date now = new Date();
timeTextView.setText(DateUtils.formatDateTime(now));
dateTextView.setText(DateUtils.formatDate(now) + " " + DateUtils.formatDayOfWeek(now));
for (int i = 0; i < countdownItems.size(); i++) {
CountdownItem item = countdownItems.get(i);
long daysLeft = DateUtils.getDaysBetween(now, item.getTargetDate());
adapter.notifyItemChanged(i);
}
}
-
Handler 和 Runnable :每秒触发一次
updateDateTime
方法。 -
UI 刷新:更新当前时间和日期,并重新计算所有倒计时的剩余天数。
4. RecyclerView 的使用
RecyclerView
是 Android 中用于展示列表数据的高效组件。我使用它来展示倒计时列表,并支持编辑和删除操作。
java
复制
less
public class CountdownAdapter extends RecyclerView.Adapter<CountdownAdapter.ViewHolder> {
private List<CountdownItem> items;
private MainActivity activity;
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
CountdownItem item = items.get(position);
Date now = new Date();
long daysLeft = DateUtils.getDaysBetween(now, item.getTargetDate());
holder.titleTextView.setText(item.getTitle());
holder.daysLeftTextView.setText(String.format("距离 %s %s 还有 %d天",
DateUtils.formatDate(item.getTargetDate()),
DateUtils.formatDayOfWeek(item.getTargetDate()),
daysLeft));
holder.editButton.setOnClickListener(v -> activity.editCountdown(position));
holder.deleteButton.setOnClickListener(v -> activity.deleteCountdown(position));
}
}
-
数据绑定:将倒计时数据绑定到列表项。
-
用户交互:通过按钮点击事件实现编辑和删除功能。
xml文件
添加图标
arduino
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></vector>
删除图标
arduino
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6V19zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/></vector>
设置图标
arduino
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94 0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61 l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41 h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22 L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58 c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54 c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96 c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6 s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/></vector>
设置页面
xml
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" tools:context=".activities.EditActivity"> <TextView android:id="@+id/titleLabel" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" android:text="# 未设置标题" android:layout_marginBottom="16dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="请输入计时器的名称" android:layout_marginBottom="8dp"/> <EditText android:id="@+id/titleEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="标题" android:layout_marginBottom="24dp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="目标日期" android:layout_marginBottom="8dp"/> <Button android:id="@+id/dateButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="2025/06/14"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@android:color/darker_gray" android:layout_marginVertical="16dp"/> <Button android:id="@+id/saveButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="保存" android:layout_marginTop="16dp"/></LinearLayout>
主页面
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=".activities.MainActivity"> <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginBottom="24dp"> <TextView android:id="@+id/timeTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" android:text="18:15:25"/> <TextView android:id="@+id/dateTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:text="2025/04/14 星期一"/> </LinearLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/countdownRecyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/header" android:layout_above="@+id/addButton"/> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/addButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_margin="16dp" app:srcCompat="@drawable/ic_add" app:tint="@android:color/white"/></RelativeLayout>
列表部分
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" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toStartOf="@+id/actionButtons" android:orientation="vertical"> <TextView android:id="@+id/titleTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:text="这里是title"/> <TextView android:id="@+id/daysLeftTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="14sp" android:text="距离 2025/06/14 星期二 还有 50天"/> </LinearLayout> <LinearLayout android:id="@+id/actionButtons" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:orientation="horizontal"> <ImageButton android:id="@+id/editButton" android:layout_width="40dp" android:layout_height="40dp" android:background="?attr/selectableItemBackgroundBorderless" app:srcCompat="@drawable/ic_settings" app:tint="@android:color/darker_gray"/> <ImageButton android:id="@+id/deleteButton" android:layout_width="40dp" android:layout_height="40dp" android:background="?attr/selectableItemBackgroundBorderless" app:srcCompat="@drawable/ic_delete" app:tint="@android:color/darker_gray"/> </LinearLayout></RelativeLayout>
开发环境与工具
-
Android Studio:版本 4.2.2,用于项目开发和调试。
-
Gradle:版本 6.7.1,用于项目构建。
-
JDK:版本 20,用于 Java 代码编译。
-
依赖库:
-
androidx.appcompat:appcompat:1.4.1
-
com.google.android.material:material:1.5.0
-
androidx.recyclerview:recyclerview:1.2.1
-
总结
这是我第一次接触安卓项目,主要还是通过AI实现的,本文章旨在分享以及记录,如若有什么不妥的地方,还望各位大佬点评,谢谢大家。