diff --git a/src/app/src/main/java/com/example/sleep/CalendarPage.java b/src/app/src/main/java/com/example/sleep/CalendarPage.java new file mode 100644 index 0000000..68a81bb --- /dev/null +++ b/src/app/src/main/java/com/example/sleep/CalendarPage.java @@ -0,0 +1,420 @@ +package com.example.sleep; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.view.ViewPager; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.bigkoo.pickerview.builder.TimePickerBuilder; +import com.bigkoo.pickerview.listener.OnTimeSelectListener; +import com.bigkoo.pickerview.view.TimePickerView; +import com.example.sleep.calendarInfo.CustomDayView; +import com.example.sleep.database.GetRecord; +import com.example.sleep.service.GoSleepService; +import com.ldf.calendar.component.CalendarAttr; +import com.ldf.calendar.component.CalendarViewAdapter; +import com.ldf.calendar.interf.OnSelectDateListener; +import com.ldf.calendar.model.CalendarDate; +import com.ldf.calendar.view.MonthPager; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; + +import static com.example.sleep.database.GetRecord.getRecord; + +/** + * 睡眠记录页面 + */ +public class CalendarPage extends Activity { + + Button btn_left; + Button btn_right; + // 获取记录的对象 + GetRecord mGetRecord; + // 记录数据的二维数组,用于标记每天是否有记录 + boolean[][] recordData; + // 时间选择视图 + private TimePickerView pvTime; + // 退出时间,用于控制双击返回键退出应用 + private long exitTime = 0; + + // 日历属性 + TextView tvYear; + TextView tvMonth; + CoordinatorLayout content; + MonthPager monthPager; + + // 当前日历视图的集合 + private ArrayList currentCalendars = new ArrayList<>(); + // 日历视图适配器 + private CalendarViewAdapter calendarAdapter; + // 日期选择监听器 + private OnSelectDateListener onSelectDateListener; + // 当前日期 + private CalendarDate currentDate; + // 是否已初始化标志 + private boolean initiated = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 设置布局文件 + setContentView(R.layout.calendar); + // 获取记录对象 + mGetRecord = getRecord(); + // 获取日历视图 + monthPager = findViewById(R.id.calendar_view); + // 初始化记录数据数组 + recordData = new boolean[13][]; + // 初始化视图和时间选择器 + initView(); + initTimePicker(); + } + + private void initView() { + // 初始化记录数据,并根据每个月的天数查询是否有记录 + int dayNum; + for (int m = 1; m <= 12; ++m) { + if (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) { + dayNum = 31; + } else if (m == 2) { + dayNum = 28; + } else { + dayNum = 30; + } + recordData[m] = mGetRecord.queryByMonth(Integer.toString(m), dayNum); + } + + // 初始化视图元素 + btn_left = findViewById(R.id.left); + btn_right = findViewById(R.id.right); + content = findViewById(R.id.content); + tvYear = findViewById(R.id.show_year_view); + tvMonth = findViewById(R.id.show_month_view); + // 初始化当前日期 + initCurrentDate(); + // 初始化日历视图 + initCalendarView(); + // 初始化工具栏点击监听 + initToolbarClickListener(); + } + + /** + * 初始化currentDate + */ + private void initCurrentDate() { + currentDate = new CalendarDate(); + // 设置年份 + tvYear.setText(String.format(getResources().getString(R.string.year), currentDate.getYear())); + // 设置月份 + tvMonth.setText(String.format(getResources().getString(R.string.month), currentDate.getMonth())); + } + + /** + * 初始化CustomDayView,并作为CalendarViewAdapter的参数传入 + */ + private void initCalendarView() { + // 初始化日历点击事件监听 + initListener(); + // 创建自定义日期视图 + CustomDayView customDayView = new CustomDayView(this, R.layout.custom_day); + calendarAdapter = new CalendarViewAdapter( + this, + onSelectDateListener, + CalendarAttr.CalendarType.MONTH, + CalendarAttr.WeekArrayType.Monday, + customDayView);// 创建日历视图适配器 + calendarAdapter.setOnCalendarTypeChangedListener(new CalendarViewAdapter.OnCalendarTypeChanged() { + @Override + // 日历类型改变时的操作 + public void onCalendarTypeChanged(CalendarAttr.CalendarType type) { + } + }); + // 初始化日历标记数据 + initMarkData(); + // 初始化月份切换功能 + initMonthPager(); + } + + //使用此方法回调日历点击事件 + private void initListener() { + onSelectDateListener = new OnSelectDateListener() { + @Override + public void onSelectDate(CalendarDate date) { + // 刷新点击日期的操作 + refreshClickDate(date); + Intent i = new Intent(); + int d; + d = date.getDay(); + if (recordData[date.month][d]) { + // 跳转到记录页面 + i.setClass(CalendarPage.this, Record.class); + // 将日期信息传递到记录页面 + i.putExtra("date", date.month + "-" + date.day); + CalendarPage.this.startActivity(i); + } else { + // 提示没有记录信息 + Toast.makeText(CalendarPage.this, "没有记录信息", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onSelectOtherMonth(int offset) { + //偏移量 -1表示上一个月 , 1表示下一个月 + monthPager.selectOtherMonth(offset); + } + }; + } + + //使用此方法初始化日历标记数据 + private void initMarkData() { + HashMap markData = new HashMap<>(); + //1表示红点,0表示灰点,只在日历上标注出灰点表示没有打卡的日期 + String s; + int dayNum; + for (int m = 1; m <= 12; m++) { + if (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) { + dayNum = 31; + } else if (m == 2) { + dayNum = 28; + } else { + dayNum = 30; + } + for (int i = 1; i <= dayNum; i++) { + if (recordData[m][i]) { + s = "2019-" + m + "-" + i; + // 标记为红点 + markData.put(s, "1"); + } + } + } + // 设置日历标记数据 + calendarAdapter.setMarkData(markData); + } + + /** + * 初始化monthPager,MonthPager继承自ViewPager + */ + private void initMonthPager() { + // 设置适配器 + monthPager.setAdapter(calendarAdapter); + // 设置当前选中项为当天 + monthPager.setCurrentItem(MonthPager.CURRENT_DAY_INDEX); + // 设置页面切换动画 + monthPager.setPageTransformer(false, new ViewPager.PageTransformer() { + @Override + public void transformPage(@NonNull View page, float position) { + position = (float) Math.sqrt(1 - Math.abs(position)); + page.setAlpha(position); + } + }); + // 添加页面变化监听器 + monthPager.addOnPageChangeListener(new MonthPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + // 页面滚动时的处理 + } + + @Override + public void onPageSelected(int position) { + // 页面选中时的处理 + currentCalendars = calendarAdapter.getPagers(); + if (currentCalendars.get(position % currentCalendars.size()) != null) { + CalendarDate date = currentCalendars.get(position % currentCalendars.size()).getSeedDate(); + currentDate = date; + tvYear.setText(String.format(getResources().getString(R.string.year), date.getYear())); + tvMonth.setText(String.format(getResources().getString(R.string.month), date.getMonth())); + } + } + @Override + public void onPageScrollStateChanged(int state) { + // 页面滚动状态改变时的处理 + } + }); + } + + /** + * 初始化对应功能的listener + */ + private void initToolbarClickListener() { + // 左按钮点击事件 + btn_left.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // 切换到上一个月 + monthPager.setCurrentItem(monthPager.getCurrentPosition() - 1); + } + }); + // 右按钮点击事件 + btn_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // 切换到下一个月 + monthPager.setCurrentItem(monthPager.getCurrentPosition() + 1); + } + }); + } + + /** + * 初始化时间选择器 + */ + private void initTimePicker() { + // 创建时间选择器 + pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() { + @Override + // 选中时间的回调处理 + public void onTimeSelect(Date date, View v) { + Toast.makeText(CalendarPage.this, "设置成功!", Toast.LENGTH_SHORT).show(); + mGetRecord.updateRemind(getTime(date)); + stopGoSleepService(); + startGoSleepService(); + } + }) + .setType(new boolean[]{false, false, false, true, true, false})// 默认全部显示 + .setCancelText("取消")//取消按钮文字 + .setSubmitText("确认")//确认按钮文字 + .setTitleSize(20)//标题文字大小 + .setTitleText("选择提醒时间")//标题文字 + .setOutSideCancelable(false)//点击屏幕,点在控件外部范围时,是否取消显示 + .isCyclic(true)//是否循环滚动 + .setTitleColor(Color.BLACK)//标题文字颜色 + .setSubmitColor(Color.BLUE)//确定按钮文字颜色 + .setCancelColor(Color.BLUE)//取消按钮文字颜色 + //.setTitleBgColor(0xFF666666)//标题背景颜色 Night mode + //.setBgColor(0xFF333333)//滚轮背景颜色 Night mode + .setLabel("年", "月", "日", "时", "分", "秒")//默认设置为年月日时分秒 + .isCenterLabel(false) //是否只显示中间选中项的label文字,false则每项item全部都带有label。 + .isDialog(true)//是否显示为对话框样式 + .build(); + + // 设置时间选择器对话框样式 + Dialog mDialog = pvTime.getDialog(); + if (mDialog != null) { + // 设置对话框在底部显示 + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.BOTTOM); + + params.leftMargin = 0; + params.rightMargin = 0; + pvTime.getDialogContainerLayout().setLayoutParams(params); + + Window dialogWindow = mDialog.getWindow(); + if (dialogWindow != null) { + //修改动画样式 + dialogWindow.setWindowAnimations(com.bigkoo.pickerview.R.style.picker_view_slide_anim); + dialogWindow.setGravity(Gravity.BOTTOM); + dialogWindow.setDimAmount(0.1f); + } + } + } + + private void refreshClickDate(CalendarDate date) { + currentDate = date; + tvYear.setText(String.format(getResources().getString(R.string.year), date.getYear())); + tvMonth.setText(String.format(getResources().getString(R.string.month), date.getMonth())); + } + + /** + * 点击提醒按钮 + */ + public void onClickRemind(View v) { + if (pvTime != null) { + // 弹出时间选择器,传递参数过去,回调的时候则可以绑定此view + pvTime.show(v); + } + } + /** + * 点击睡眠按钮 + */ + public void ClickSleep(View v) { + // 跳转到MainActivity并关闭当前页面 + Intent i = new Intent(); + i.setClass(CalendarPage.this, MainActivity.class); + CalendarPage.this.startActivity(i); + CalendarPage.this.finish(); + } + + @Override + // 当窗口焦点状态改变时调用,用于在应用获得焦点且未初始化时更新日历视图 + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus && !initiated) { + // 获取当天日期 + CalendarDate today = new CalendarDate(); + // 通知日历视图数据改变 + calendarAdapter.notifyDataChanged(today); + // 设置初始化标志为true + initiated = true; + } + } + + @Override + // 按键按下事件监听,用于实现双击返回键退出应用 + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { + if (System.currentTimeMillis() - exitTime > 2000) { + // 判断两次点击返回键的时间间隔 + // 提示再次点击退出 + Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show(); + // 更新退出时间 + exitTime = System.currentTimeMillis(); + } else { + // 关闭当前Activity + finish(); + // 退出应用 + System.exit(0); + } + return true; + } + return super.onKeyDown(keyCode, event); + } + + /** + * 获取时间字符串 + */ + private String getTime(Date date) { + return new SimpleDateFormat("HH:mm", Locale.getDefault()).format(date); + } + + /** + * 启动后台服务 + */ + public void startGoSleepService() { + Intent ifSleepIntent = new Intent(this, GoSleepService.class); + this.startService(ifSleepIntent); + } + + /** + * 停止后台服务 + */ + public void stopGoSleepService() { + Intent ifSleepIntent = new Intent(this, GoSleepService.class); + this.stopService(ifSleepIntent); + } + + /** + * 销毁Activity + */ + @Override + public void onDestroy() { + super.onDestroy(); + } +} \ No newline at end of file diff --git a/src/app/src/main/java/com/example/sleep/calendarInfo/CustomDayView.java b/src/app/src/main/java/com/example/sleep/calendarInfo/CustomDayView.java new file mode 100644 index 0000000..1d1ea18 --- /dev/null +++ b/src/app/src/main/java/com/example/sleep/calendarInfo/CustomDayView.java @@ -0,0 +1,96 @@ +package com.example.sleep.calendarInfo; + +import android.content.Context; +import android.graphics.Color; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.sleep.R; +import com.ldf.calendar.Utils; +import com.ldf.calendar.component.State; +import com.ldf.calendar.interf.IDayRenderer; +import com.ldf.calendar.model.CalendarDate; +import com.ldf.calendar.view.DayView; + +import java.util.Objects; + +public class CustomDayView extends DayView { + + private final CalendarDate today = new CalendarDate();//当前日期 + private TextView dateTv;//日期文本显示 + private ImageView marker;//标记图标 + private View selectedBackground;//选中背景 + private View todayBackground;//今天的背景 + + + public CustomDayView(Context context, int layoutResource) { + super(context, layoutResource); + dateTv = findViewById(R.id.date);//找到日期文本视图 + marker = findViewById(R.id.maker);// 找到标记图标视图 + selectedBackground = findViewById(R.id.selected_background); // 找到选中背景视图 + todayBackground = findViewById(R.id.today_background); // 找到今天的背景视图 + } + + @Override + public void refreshContent() { + super.refreshContent();// 刷新日历内容 + renderToday(day.getDate()); // 渲染今天的日期 + renderSelect(day.getState());// 渲染选中状态 + renderMarker(day.getDate(), day.getState());// 渲染标记 + dateTv.setTextSize(19);// 设置日期文本大小 + super.refreshContent();// 再次刷新日历内容 + } + + // 渲染标记的显示 + private void renderMarker(CalendarDate date, State state) { + if (Utils.loadMarkData().containsKey(date.toString())) { + if (state == State.SELECT || date.toString().equals(today.toString())) { + marker.setVisibility(GONE); + } else { + marker.setVisibility(VISIBLE); + if (Objects.equals(Utils.loadMarkData().get(date.toString()), "0")) { + marker.setEnabled(true); + } else { + marker.setEnabled(false); + } + } + } else { + marker.setVisibility(GONE); + } + } + + // 渲染选中状态 + private void renderSelect(State state) { + if (state == State.SELECT) { + selectedBackground.setVisibility(View.VISIBLE); + dateTv.setTextColor(Color.BLACK); + } else if (state == State.NEXT_MONTH || state == State.PAST_MONTH) { + selectedBackground.setVisibility(GONE); + dateTv.setTextColor(Color.parseColor("#3666a2")); + } else { + selectedBackground.setVisibility(GONE); + dateTv.setTextColor(Color.WHITE); + } + } + + // 渲染今天的日期显示 + private void renderToday(CalendarDate date) { + if (date != null) { + if (date.equals(today)) { + dateTv.setText("今"); + dateTv.setTextColor(Color.BLACK); + //todayBackground.setVisibility(VISIBLE); + } else { + dateTv.setText(date.day + ""); + todayBackground.setVisibility(GONE); + } + } + } + + @Override + // 复制当前的日历视图 + public IDayRenderer copy() { + return new CustomDayView(context, layoutResource); + } +} \ No newline at end of file diff --git a/src/app/src/main/java/com/example/sleep/utils/ApkHelper.java b/src/app/src/main/java/com/example/sleep/utils/ApkHelper.java new file mode 100644 index 0000000..d3167b4 --- /dev/null +++ b/src/app/src/main/java/com/example/sleep/utils/ApkHelper.java @@ -0,0 +1,41 @@ +package com.example.sleep.utils; + +import android.app.ActivityManager; +import android.content.Context; + +import java.util.List; +/** + * 辅助工具类,用于获取进程名称 + */ +public class ApkHelper { + /** + * 获取当前进程名称 + * + * @param context 应用程序上下文 + * @return 当前进程名称 + */ + public static String getProcessName(Context context) { + // 获取当前进程ID + int pid = android.os.Process.myPid(); + // 获取Activity管理器 + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (am == null) { + // 如果Activity管理器为空,返回空 + return null; + } + // 获取正在运行的进程列表 + List processes = am.getRunningAppProcesses(); + if (processes == null) { + // 如果进程列表为空,返回空 + return null; + } + for (ActivityManager.RunningAppProcessInfo info : processes) { + if (info.pid == pid) { + // 遍历进程列表,找到与当前进程ID匹配的进程名称并返回 + return info.processName; + } + } + // 未找到匹配的进程名称,返回空 + return null; + } +} \ No newline at end of file diff --git a/src/app/src/main/java/com/example/sleep/utils/JobSchedulerManager.java b/src/app/src/main/java/com/example/sleep/utils/JobSchedulerManager.java new file mode 100644 index 0000000..b7b04e2 --- /dev/null +++ b/src/app/src/main/java/com/example/sleep/utils/JobSchedulerManager.java @@ -0,0 +1,64 @@ +package com.example.sleep.utils; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; + +import com.example.sleep.service.AliveJobService; + +/** + * JobSchedulerManager是一个帮助类,用于管理作业调度和执行相关操作。 + */ +public class JobSchedulerManager { + private static final int JOB_ID = 1; // 定义作业ID + private static JobSchedulerManager mJobManager; // 单例模式中的实例 + private JobScheduler mJobScheduler; // 作业调度器 + private static Context mContext; // 上下文 + /** + * 构造函数,私有化,外部无法直接实例化该类 + * + * @param ctxt 上下文 + */ + private JobSchedulerManager(Context ctxt) { + mContext = ctxt; + mJobScheduler = (JobScheduler) ctxt.getSystemService(Context.JOB_SCHEDULER_SERVICE); + } + /** + * 获取JobSchedulerManager的实例,采用单例模式 + * + * @param ctxt 上下文 + * @return JobSchedulerManager的实例 + */ + public final static JobSchedulerManager getJobSchedulerInstance(Context ctxt) { + if (mJobManager == null) { + mJobManager = new JobSchedulerManager(ctxt); + } + return mJobManager; + } + /** + * 开始执行作业调度 + */ + public void startJobScheduler() { + // 如果作业服务已经在运行,则返回 + if (AliveJobService.isJobServiceAlive()) { + return; + } + // 构建JobInfo对象,传递给JobSchedulerService + JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, new ComponentName(mContext, AliveJobService.class)); + // 设置周期为3秒(实际上最低周期为15分钟) + builder.setPeriodic(3000); + // 当充电时执行该作业 + builder.setRequiresCharging(true); + // 构建JobInfo对象 + JobInfo info = builder.build(); + // 开始定时执行该系统作业 + mJobScheduler.schedule(info); + } + /** + * 停止作业调度 + */ + public void stopJobScheduler() { + mJobScheduler.cancelAll(); // 取消所有作业 + } +} \ No newline at end of file diff --git a/src/app/src/main/java/com/example/sleep/utils/SystemUtils.java b/src/app/src/main/java/com/example/sleep/utils/SystemUtils.java new file mode 100644 index 0000000..b291b7c --- /dev/null +++ b/src/app/src/main/java/com/example/sleep/utils/SystemUtils.java @@ -0,0 +1,36 @@ +package com.example.sleep.utils; + +import android.app.ActivityManager; +import android.content.Context; + +import java.util.List; + +/** + * 系统工具类,用于判断应用是否在前台运行 + */ +public class SystemUtils { + /** + * 判断应用是否在前台运行 + * + * @param mContext 应用程序上下文 + * @param packageName 应用包名 + * @return 应用是否在前台运行 + */ + public static boolean isAPPALive(Context mContext, String packageName) { + // 默认应用未在前台运行 + boolean isAPPRunning = false; + // 获取Activity管理器 + ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + // 获取正在运行的进程列表 + List appProcessInfoList = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appInfo : appProcessInfoList) { + if (packageName.equals(appInfo.processName)) { + // 如果应用包名与正在运行的进程列表中的某个进程匹配,则应用在前台运行 + isAPPRunning = true; + break; + } + } + // 返回应用是否在前台运行的结果 + return isAPPRunning; + } +} \ No newline at end of file