diff --git a/positions/build.gradle b/positions/build.gradle index 356a27a..00f8e5e 100644 --- a/positions/build.gradle +++ b/positions/build.gradle @@ -46,6 +46,9 @@ android { dependencies { api fileTree(dir: 'libs', include: ['*.jar']) + // https://mvnrepository.com/artifact/com.jzxiang.pickerview/TimePickerDialog + api 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + // 谷歌定位服务核心依赖(FusedLocationProviderClient所在库) api 'com.google.android.gms:play-services-location:21.0.1' diff --git a/positions/build.properties b/positions/build.properties index dc9c7d6..6f18f6d 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Oct 08 21:19:35 HKT 2025 +#Tue Oct 21 21:35:04 GMT 2025 stageCount=12 libraryProject= baseVersion=15.0 publishVersion=15.0.11 -buildCount=0 +buildCount=47 baseBetaVersion=15.0.12 diff --git a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java index c527cdc..7412c0b 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java +++ b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java @@ -37,11 +37,17 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; // 新增:定时器时间单位依赖 public class MainService extends Service { public static final String TAG = "MainService"; + // ---------------------- 新增:定时器相关变量 ---------------------- + private ScheduledExecutorService taskCheckTimer; // 任务校验定时器 + private static final long TASK_CHECK_INTERVAL = 1; // 定时间隔(1分钟) + private static final long TASK_CHECK_INIT_DELAY = 0; // 初始延迟(0分钟:立即启动) + // GPS监听接口(Java 7 标准接口定义,无Lambda依赖) public interface GpsUpdateListener { void onGpsPositionUpdated(PositionModel currentGpsPos); @@ -84,6 +90,51 @@ public class MainService extends Service { private static Context sAppContext; + // ========================================================================= + // 新增:定时器初始化方法(创建单线程定时器,每1分钟调用任务校验) + // ========================================================================= + private void initTaskCheckTimer() { + // 先销毁旧定时器(避免重复创建导致多线程问题) + if (taskCheckTimer != null && !taskCheckTimer.isShutdown()) { + taskCheckTimer.shutdown(); + } + + // 创建单线程定时器(确保任务串行执行,避免并发异常) + taskCheckTimer = Executors.newSingleThreadScheduledExecutor(); + // 定时任务:初始延迟0分钟,每1分钟执行一次 + taskCheckTimer.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + LogUtils.d(TAG, "定时任务触发:开始校验任务(间隔1分钟)"); + // 调用任务校验核心方法(与GPS位置变化时逻辑一致) + checkAllTaskTriggerCondition(); + } + }, TASK_CHECK_INIT_DELAY, TASK_CHECK_INTERVAL, TimeUnit.MINUTES); + + LogUtils.d(TAG, "任务校验定时器已启动(间隔:" + TASK_CHECK_INTERVAL + "分钟)"); + } + + // ========================================================================= + // 新增:定时器销毁方法(服务销毁时调用,避免内存泄漏) + // ========================================================================= + private void destroyTaskCheckTimer() { + if (taskCheckTimer != null && !taskCheckTimer.isShutdown()) { + taskCheckTimer.shutdown(); // 优雅关闭:等待已提交任务执行完成 + try { + // 等待1秒,若未终止则强制关闭 + if (!taskCheckTimer.awaitTermination(1, TimeUnit.SECONDS)) { + taskCheckTimer.shutdownNow(); // 强制终止未完成任务 + } + } catch (InterruptedException e) { + taskCheckTimer.shutdownNow(); // 捕获中断异常,强制关闭 + Thread.currentThread().interrupt(); // 恢复线程中断状态 + } finally { + taskCheckTimer = null; // 置空,避免重复操作 + LogUtils.d(TAG, "任务校验定时器已销毁"); + } + } + } + // ========================================================================= // 任务操作核心接口(Java 7 实现,全迭代器遍历,无ConcurrentModificationException) // ========================================================================= @@ -149,7 +200,7 @@ public class MainService extends Service { public ArrayList getAllTasks() { return new ArrayList(mAllTasks); // Java 7 集合拷贝方式 } - + public void updateTask(PositionTaskModel updatedTask) { if (updatedTask == null || updatedTask.getTaskId() == null) return; for (int i = 0; i < mAllTasks.size(); i++) { @@ -173,8 +224,8 @@ public class MainService extends Service { } saveAllTasks(); // 持久化状态变更 } - - + + /** * 删除任务(Adapter调用,通过迭代器安全删除,避免并发异常) * @param taskId 待删除任务的ID @@ -317,6 +368,7 @@ public class MainService extends Service { /** * 服务核心逻辑(启动前台服务、初始化GPS、加载数据等) + * 【关键修改】新增定时器初始化,每1分钟调用任务校验 */ public void run() { if (mAppConfigsUtil.isEnableMainService(true)) { @@ -339,9 +391,13 @@ public class MainService extends Service { PositionModel.loadBeanList(MainService.this, mPositionList, PositionModel.class); PositionTaskModel.loadBeanList(MainService.this, mAllTasks, PositionTaskModel.class); - // 提示与日志(Java 7 基础调用) + // 提示与日志(Java 7 基础调用) ToastUtils.show(initialStatus); LogUtils.i(TAG, initialStatus); + + // ---------------------- 关键新增:启动任务校验定时器 ---------------------- + //checkAllTaskTriggerCondition(); + initTaskCheckTimer(); } } } @@ -356,6 +412,7 @@ public class MainService extends Service { /** * 服务销毁回调(清理资源、停止GPS、清空数据、反注册监听等) + * 【关键修改】新增定时器销毁,避免内存泄漏 */ @Override public void onDestroy() { @@ -373,6 +430,13 @@ public class MainService extends Service { mTaskListeners.clear(); } + // ---------------------- 关键新增:销毁任务校验定时器 ---------------------- + destroyTaskCheckTimer(); + // 销毁距离计算线程池(原有逻辑,补充确保线程安全) + if (distanceExecutor != null && !distanceExecutor.isShutdown()) { + distanceExecutor.shutdown(); + } + // 重置状态变量 _mIsServiceRunning = false; isGpsEnabled = false; @@ -609,7 +673,7 @@ public class MainService extends Service { } // 距离刷新后校验任务触发条件+通知GPS监听者 - checkAllTaskTriggerCondition(); + //checkAllTaskTriggerCondition(); notifyAllGpsListeners(mCurrentGpsPosition); } @@ -652,6 +716,11 @@ public class MainService extends Service { continue; } + // 校验任务开始时间 + if(task.getStartTime() > System.currentTimeMillis()) { + continue; + } + // 校验距离条件(判断是否满足任务触发阈值) double currentDistance = bindPos.getRealPositionDistance(); if (currentDistance < 0) { @@ -753,7 +822,7 @@ public class MainService extends Service { } /** - * 唤醒并绑定辅助服务(检查服务状态,未存活则启动+绑定) + * 唤醒并绑定辅助服务(检查服务状态,未存活则启动+绑定) */ void wakeupAndBindAssistant() { // 检查辅助服务是否存活(Java 7 静态方法调用,无方法引用) @@ -795,6 +864,7 @@ public class MainService extends Service { // 同步GPS位置+刷新距离+日志(原逻辑保留) syncCurrentGpsPosition(gpsPos); forceRefreshDistance(); + checkAllTaskTriggerCondition(); LogUtils.d(TAG, "GPS位置更新:纬度=" + location.getLatitude() + ",经度=" + location.getLongitude()); } } diff --git a/positions/src/main/java/cc/winboll/studio/positions/utils/DensityUtils.java b/positions/src/main/java/cc/winboll/studio/positions/utils/DensityUtils.java new file mode 100644 index 0000000..828d376 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/utils/DensityUtils.java @@ -0,0 +1,45 @@ +package cc.winboll.studio.positions.utils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/10/22 01:16 + * @Describe DensityUtils + */ + +import android.content.Context; +import android.util.DisplayMetrics; +import android.view.WindowManager; + +/** + * 屏幕密度工具类(dp/sp 转 px、获取屏幕尺寸) + */ +public class DensityUtils { + public static final String TAG = "DensityUtils"; + + /** + * dp 转 px(根据屏幕密度) + */ + public static int dp2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + /** + * sp 转 px(根据文字缩放比例) + */ + public static int sp2px(Context context, float spValue) { + final float scale = context.getResources().getDisplayMetrics().scaledDensity; + return (int) (spValue * scale + 0.5f); + } + + /** + * 获取屏幕宽度(像素) + */ + public static int getScreenWidth(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(dm); + return dm.widthPixels; + } +} + diff --git a/positions/src/main/java/cc/winboll/studio/positions/views/DateTimePickerPopup.java b/positions/src/main/java/cc/winboll/studio/positions/views/DateTimePickerPopup.java new file mode 100644 index 0000000..692bad8 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/views/DateTimePickerPopup.java @@ -0,0 +1,254 @@ +package cc.winboll.studio.positions.views; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/10/22 02:15 + * @Describe DateTimePickerPopup + */ +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.NumberPicker; +import android.widget.PopupWindow; +import java.util.Calendar; +import cc.winboll.studio.positions.R; +import cc.winboll.studio.positions.utils.DensityUtils; + +/** + * 日期时间选择弹窗(竖直滚动行:年、月、日、时、分) + */ +public class DateTimePickerPopup extends PopupWindow { + public static final String TAG = "DateTimePickerPopup"; + + private Context mContext; + private NumberPicker mPickerYear; + private NumberPicker mPickerMonth; + private NumberPicker mPickerDay; + private NumberPicker mPickerHour; + private NumberPicker mPickerMinute; + private Button mBtnCancel; + private Button mBtnConfirm; + private OnDateTimeSelectedListener mListener; + + // 时间范围默认值 + private int mMinYear = 2000; + private int mMaxYear = Calendar.getInstance().get(Calendar.YEAR) + 10; + private int mMinMonth = 1; + private int mMaxMonth = 12; + private int mMinDay = 1; + private int mMaxDay = 31; + private int mMinHour = 0; + private int mMaxHour = 23; + private int mMinMinute = 0; + private int mMaxMinute = 59; + + // 默认选中时间 + private int mDefaultYear; + private int mDefaultMonth; + private int mDefaultDay; + private int mDefaultHour; + private int mDefaultMinute; + + /** + * 日期时间选择回调 + */ + public interface OnDateTimeSelectedListener { + void onDateTimeSelected(int year, int month, int day, int hour, int minute); + void onCancel(); + } + + /** + * Builder 模式 + */ + public static class Builder { + private Context mContext; + private DateTimePickerPopup mPopup; + + public Builder(Context context) { + this.mContext = context; + mPopup = new DateTimePickerPopup(context); + Calendar calendar = Calendar.getInstance(); + mPopup.mDefaultYear = calendar.get(Calendar.YEAR); + mPopup.mDefaultMonth = calendar.get(Calendar.MONTH) + 1; + mPopup.mDefaultDay = calendar.get(Calendar.DAY_OF_MONTH); + mPopup.mDefaultHour = calendar.get(Calendar.HOUR_OF_DAY); + mPopup.mDefaultMinute = calendar.get(Calendar.MINUTE); + } + + public Builder setDateTimeRange(int minYear, int maxYear, int minMonth, int maxMonth, + int minDay, int maxDay, int minHour, int maxHour, + int minMinute, int maxMinute) { + mPopup.mMinYear = minYear; + mPopup.mMaxYear = maxYear; + mPopup.mMinMonth = minMonth; + mPopup.mMaxMonth = maxMonth; + mPopup.mMinDay = minDay; + mPopup.mMaxDay = maxDay; + mPopup.mMinHour = minHour; + mPopup.mMaxHour = maxHour; + mPopup.mMinMinute = minMinute; + mPopup.mMaxMinute = maxMinute; + return this; + } + + public Builder setDefaultDateTime(int year, int month, int day, int hour, int minute) { + mPopup.mDefaultYear = year; + mPopup.mDefaultMonth = month; + mPopup.mDefaultDay = day; + mPopup.mDefaultHour = hour; + mPopup.mDefaultMinute = minute; + return this; + } + + public Builder setOnDateTimeSelectedListener(OnDateTimeSelectedListener listener) { + mPopup.mListener = listener; + return this; + } + + public DateTimePickerPopup build() { + mPopup.initView(); + mPopup.initPickers(); + mPopup.bindButtonClick(); + mPopup.setPopupStyle(); + return mPopup; + } + } + + private DateTimePickerPopup(Context context) { + super(context); + this.mContext = context; + } + + private void initView() { + LayoutInflater inflater = LayoutInflater.from(mContext); + View rootView = inflater.inflate(R.layout.dialog_date_time_picker, null, false); + setContentView(rootView); + + mPickerYear = (NumberPicker) rootView.findViewById(R.id.picker_year); + mPickerMonth = (NumberPicker) rootView.findViewById(R.id.picker_month); + mPickerDay = (NumberPicker) rootView.findViewById(R.id.picker_day); + mPickerHour = (NumberPicker) rootView.findViewById(R.id.picker_hour); + mPickerMinute = (NumberPicker) rootView.findViewById(R.id.picker_minute); + mBtnCancel = (Button) rootView.findViewById(R.id.btn_cancel); + mBtnConfirm = (Button) rootView.findViewById(R.id.btn_confirm); + } + + private void initPickers() { + // 初始化年选择器 + mPickerYear.setMinValue(mMinYear); + mPickerYear.setMaxValue(mMaxYear); + mPickerYear.setValue(mDefaultYear); + mPickerYear.setWrapSelectorWheel(false); + + // 初始化月选择器 + mPickerMonth.setMinValue(mMinMonth); + mPickerMonth.setMaxValue(mMaxMonth); + mPickerMonth.setValue(mDefaultMonth); + mPickerMonth.setWrapSelectorWheel(false); + + // 初始化日选择器(根据年月动态调整范围) + updateDayRange(mDefaultYear, mDefaultMonth); + mPickerDay.setValue(mDefaultDay); + mPickerDay.setWrapSelectorWheel(false); + + // 初始化时选择器 + mPickerHour.setMinValue(mMinHour); + mPickerHour.setMaxValue(mMaxHour); + mPickerHour.setValue(mDefaultHour); + mPickerHour.setWrapSelectorWheel(false); + + // 初始化分选择器 + mPickerMinute.setMinValue(mMinMinute); + mPickerMinute.setMaxValue(mMaxMinute); + mPickerMinute.setValue(mDefaultMinute); + mPickerMinute.setWrapSelectorWheel(false); + + // 年月变化时更新日范围(Java 7 匿名内部类) + mPickerYear.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateDayRange(newVal, mPickerMonth.getValue()); + } + }); + + mPickerMonth.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateDayRange(mPickerYear.getValue(), newVal); + } + }); + } + + private void updateDayRange(int year, int month) { + int maxDay; + switch (month) { + case 2: + if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { + maxDay = 29; + } else { + maxDay = 28; + } + break; + case 4: + case 6: + case 9: + case 11: + maxDay = 30; + break; + default: + maxDay = 31; + } + mPickerDay.setMaxValue(maxDay); + if (mPickerDay.getValue() > maxDay) { + mPickerDay.setValue(maxDay); + } + } + + private void bindButtonClick() { + // 取消按钮(Java 7 匿名内部类) + mBtnCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + if (mListener != null) { + mListener.onCancel(); + } + } + }); + + // 确认按钮(Java 7 匿名内部类) + mBtnConfirm.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int year = mPickerYear.getValue(); + int month = mPickerMonth.getValue(); + int day = mPickerDay.getValue(); + int hour = mPickerHour.getValue(); + int minute = mPickerMinute.getValue(); + + if (mListener != null) { + mListener.onDateTimeSelected(year, month, day, hour, minute); + } + dismiss(); + } + }); + } + + private void setPopupStyle() { + int width = (int) (DensityUtils.getScreenWidth(mContext) * 0.85f); + int height = ViewGroup.LayoutParams.WRAP_CONTENT; + + setWidth(width); + setHeight(height); + setFocusable(true); + setOutsideTouchable(true); + setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.bg_dialog_round)); + setAnimationStyle(R.style.PopupDateTimePickerAnim); + } + + public void showAsDropDown(View anchorView) { + super.showAsDropDown(anchorView, 0, DensityUtils.dp2px(mContext, 10)); + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java b/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java index 49f7a12..b9661b3 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java +++ b/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java @@ -14,7 +14,6 @@ import android.widget.Button; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.LinearLayout; -import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; @@ -26,8 +25,14 @@ import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.positions.R; import cc.winboll.studio.positions.models.PositionTaskModel; import cc.winboll.studio.positions.services.MainService; +import com.jzxiang.pickerview.TimePickerDialog; +import com.jzxiang.pickerview.listener.OnDateSetListener; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.Locale; public class PositionTaskListView extends LinearLayout { // 视图模式常量 @@ -321,6 +326,7 @@ public class PositionTaskListView extends LinearLayout { // 任务描述 String taskDesc = task.getTaskDescription() == null ? "未设置描述" : task.getTaskDescription(); simpleHolder.tvSimpleTaskDesc.setText(String.format("任务:%s", taskDesc)); + simpleHolder.tvStartTime.setText(genSelectedTimeText(task.getStartTime())); // 距离条件(大于/小于+距离值) String distanceCond = task.isGreaterThan() ? "大于" : "小于"; simpleHolder.tvSimpleDistanceCond.setText(String.format("条件:距离 %s %d 米", distanceCond, task.getDiscussDistance())); @@ -328,10 +334,7 @@ public class PositionTaskListView extends LinearLayout { simpleHolder.tvSimpleIsEnable.setText(task.isEnable() ? "状态:已启用" : "状态:已禁用"); // isBingo红点(任务触发时显示,未触发时隐藏) simpleHolder.vBingoDot.setVisibility(task.isBingo() ? View.VISIBLE : View.GONE); - } - - // 4. 编辑模式绑定(核心调整:所有修改操作后同步MainService) - else if (holder instanceof TaskContentViewHolder) { + } else if (holder instanceof TaskContentViewHolder) { TaskContentViewHolder contentHolder = (TaskContentViewHolder) holder; bindEditModeTask(contentHolder, task, position); } @@ -346,6 +349,7 @@ public class PositionTaskListView extends LinearLayout { holder.tvTaskDesc.setText(String.format("任务:%s", taskDesc)); String distanceCond = task.isGreaterThan() ? "大于" : "小于"; holder.tvTaskDistance.setText(String.format("条件:%s %d 米", distanceCond, task.getDiscussDistance())); + holder.tvStartTime.setText(genSelectedTimeText(task.getStartTime())); // 4.2 绑定“启用开关”(修复:先解绑监听→设值→再绑定监听,避免设值触发回调) holder.cbTaskEnable.setOnCheckedChangeListener(null); @@ -372,10 +376,11 @@ public class PositionTaskListView extends LinearLayout { LogUtils.d(TAG, "调用MainService删除任务:ID=" + task.getTaskId() + "(位置ID=" + mBindPositionId + ")"); // 步骤2:从Adapter数据源移除任务(避免等待同步,立即反馈UI) - mAdapterData.remove(position); + //mAdapterData.remove(position); // 步骤3:刷新Adapter(局部刷新+范围通知,避免列表错乱) notifyItemRemoved(position); notifyItemRangeChanged(position, mAdapterData.size()); + LogUtils.d(TAG, "Adapter已移除任务,刷新列表(位置索引=" + position + ")"); // 步骤4:通知外部(如Activity)任务已更新 @@ -452,12 +457,20 @@ public class PositionTaskListView extends LinearLayout { } }); } + + private String genSelectedTimeText(long timeMillis) { + // 2. 格式化时间字符串(Java 7 用 SimpleDateFormat,需处理 ParseException) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); + String formattedDateTime = sdf.format(new Date(timeMillis)); // Date 需导入 java.util.Date + + return formattedDateTime; + } /** * 编辑任务弹窗(核心:保存修改→同步MainService→刷新Adapter) */ private void showEditTaskDialog(final PositionTaskModel task, final int position) { - Context context = getContext(); + final Context context = getContext(); if (context == null) { LogUtils.w(TAG, "编辑弹窗无法显示:上下文为空"); return; @@ -468,9 +481,72 @@ public class PositionTaskListView extends LinearLayout { final EditText etEditDesc = dialogView.findViewById(R.id.et_edit_task_desc); final RadioGroup rgDistanceCondition = dialogView.findViewById(R.id.rg_distance_condition); final EditText etEditDistance = dialogView.findViewById(R.id.et_edit_distance); - Button btnCancel = dialogView.findViewById(R.id.btn_dialog_cancel); + Button btnCancel = dialogView.findViewById(R.id.btn_dialog_cancel); Button btnSave = dialogView.findViewById(R.id.btn_dialog_save); + + // 绑定外层对话框内的控件 + final Button btnSelectTime = (Button) dialogView.findViewById(R.id.btn_select_time); + final TextView tv_SelectedTime = (TextView) dialogView.findViewById(R.id.tv_selected_time); + + tv_SelectedTime.setText(genSelectedTimeText(task.getStartTime())); + + // 核心:从 long 时间戳解析年月日时分,用于初始化弹窗 + // -------------------------- + Calendar initCalendar = Calendar.getInstance(); + initCalendar.setTimeInMillis(task.getStartTime()); // 将 long 时间戳传入 Calendar + + // 从 Calendar 中解析出年月日时分(对应弹窗需要的参数格式) + int initYear = initCalendar.get(Calendar.YEAR); + int initMonth = initCalendar.get(Calendar.MONTH) + 1; // 关键:Calendar 月份0-11,转成1-12 + int initDay = initCalendar.get(Calendar.DAY_OF_MONTH); + int initHour = initCalendar.get(Calendar.HOUR_OF_DAY); // 24小时制 + int initMinute = initCalendar.get(Calendar.MINUTE); + + // 初始化弹窗,用解析出的年月日时分设置默认选中时间 + final DateTimePickerPopup dateTimePopup = new DateTimePickerPopup.Builder(context) + .setDateTimeRange(2020, 2030, 1, 12, 1, 31, 0, 23, 0, 59) + // 用 long 时间戳解析出的参数设置初始时间 + .setDefaultDateTime(initYear, initMonth, initDay, initHour, initMinute) + .setOnDateTimeSelectedListener(new DateTimePickerPopup.OnDateTimeSelectedListener() { + @Override + public void onDateTimeSelected(int year, int month, int day, int hour, int minute) { + // 处理选择的日期时间 + // 1. 创建 Calendar 实例(用于组装日期时间) + Calendar calendar = Calendar.getInstance(); + // 2. 设置 Calendar 的年、月、日、时、分(注意:Calendar 月份从 0 开始,需减 1) + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); // 关键:传入的 month 是 1-12,需转为 0-11 + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); // 24小时制,对应参数中的 hour(0-23) + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, 0); // 秒默认设为 0,避免随机值 + calendar.set(Calendar.MILLISECOND, 0); // 毫秒默认设为 0,确保时间戳精确 + + // 3. 转为 long 类型时间戳(单位:毫秒,从 1970-01-01 00:00:00 开始计算) + long timeMillis = calendar.getTimeInMillis(); + + // 4. 后续使用(示例:打印时间戳或传递给其他逻辑) + tv_SelectedTime.setText(genSelectedTimeText(timeMillis)); + task.setStartTime(timeMillis); + } + + @Override + public void onCancel() { + // 处理取消操作 + } + }) + .build(); + + // 3. “选择时间”按钮点击事件(弹出 TimePickerPopup) + btnSelectTime.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + dateTimePopup.showAsDropDown(btnSelectTime); + } + }); + + // 初始化弹窗数据(填充当前任务信息) etEditDesc.setText(task.getTaskDescription() == null ? "" : task.getTaskDescription()); etEditDesc.setSelection(etEditDesc.getText().length()); // 光标定位到末尾 @@ -489,6 +565,14 @@ public class PositionTaskListView extends LinearLayout { .create(); dialog.show(); + + + final OnDateSetListener onSateSetListener = new OnDateSetListener(){ + @Override + public void onDateSet(TimePickerDialog timePickerDialog, long p) { + } + }; + // 取消按钮:关闭弹窗(不做操作) btnCancel.setOnClickListener(new View.OnClickListener() { @Override @@ -583,6 +667,7 @@ public class PositionTaskListView extends LinearLayout { public class SimpleTaskViewHolder extends TaskViewHolder { TextView tvSimpleTaskDesc; // 任务描述 TextView tvSimpleDistanceCond;// 距离条件(大于/小于+距离) + TextView tvStartTime; TextView tvSimpleIsEnable; // 启用状态(已启用/已禁用) View vBingoDot; // isBingo红点(任务触发时显示) @@ -591,6 +676,7 @@ public class PositionTaskListView extends LinearLayout { // 绑定简单模式布局控件(与XML控件ID严格对应) tvSimpleTaskDesc = itemView.findViewById(R.id.tv_simple_task_desc); tvSimpleDistanceCond = itemView.findViewById(R.id.tv_simple_distance_cond); + tvStartTime = itemView.findViewById(R.id.tv_starttime); tvSimpleIsEnable = itemView.findViewById(R.id.tv_simple_is_enable); vBingoDot = itemView.findViewById(R.id.v_bingo_dot); } @@ -600,6 +686,7 @@ public class PositionTaskListView extends LinearLayout { public class TaskContentViewHolder extends TaskViewHolder { TextView tvTaskDesc; // 任务描述 TextView tvTaskDistance; // 距离条件 + TextView tvStartTime; CompoundButton cbTaskEnable; // 启用开关 Button btnEditTask; // 编辑按钮 Button btnDeleteTask; // 删除按钮 @@ -609,6 +696,7 @@ public class PositionTaskListView extends LinearLayout { // 绑定编辑模式布局控件(与XML控件ID严格对应) tvTaskDesc = itemView.findViewById(R.id.tv_task_desc); tvTaskDistance = itemView.findViewById(R.id.tv_task_distance); + tvStartTime = itemView.findViewById(R.id.tv_starttime); cbTaskEnable = itemView.findViewById(R.id.cb_task_enable); btnEditTask = itemView.findViewById(R.id.btn_edit_task); btnDeleteTask = itemView.findViewById(R.id.btn_delete_task); diff --git a/positions/src/main/res/anim/popup_date_time_picker_in.xml b/positions/src/main/res/anim/popup_date_time_picker_in.xml new file mode 100644 index 0000000..3998a38 --- /dev/null +++ b/positions/src/main/res/anim/popup_date_time_picker_in.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/positions/src/main/res/anim/popup_date_time_picker_out.xml b/positions/src/main/res/anim/popup_date_time_picker_out.xml new file mode 100644 index 0000000..0619581 --- /dev/null +++ b/positions/src/main/res/anim/popup_date_time_picker_out.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/positions/src/main/res/drawable/bg_dialog_round.xml b/positions/src/main/res/drawable/bg_dialog_round.xml new file mode 100644 index 0000000..1cacbaa --- /dev/null +++ b/positions/src/main/res/drawable/bg_dialog_round.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/positions/src/main/res/drawable/btn_dialog_cancel.xml b/positions/src/main/res/drawable/btn_dialog_cancel.xml new file mode 100644 index 0000000..3c04c0f --- /dev/null +++ b/positions/src/main/res/drawable/btn_dialog_cancel.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/positions/src/main/res/drawable/btn_dialog_confirm.xml b/positions/src/main/res/drawable/btn_dialog_confirm.xml new file mode 100644 index 0000000..fa1e750 --- /dev/null +++ b/positions/src/main/res/drawable/btn_dialog_confirm.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/positions/src/main/res/layout/dialog_date_time_picker.xml b/positions/src/main/res/layout/dialog_date_time_picker.xml new file mode 100644 index 0000000..1929dc6 --- /dev/null +++ b/positions/src/main/res/layout/dialog_date_time_picker.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + +