当前位置: 首页 > news >正文

Android实战进阶 - 启动页

场景:当启动页处于倒计时阶段,用户将其切换为后台的多任务卡片状态,倒计时会继续执行,直到最后执行相关逻辑(一般会跳转引导页、进入主页等)
期望:而综合市场来看,一般我们期望的是当其处于多卡片状态,需要暂停倒计时,只有恢复前台状态后继续计时。而不是重新计时或已计时完毕

关于启动页的一些基础内容,之前已经做过总结了,此篇主要用于解决上方提到的业务场景

  • Android进阶之路 - Splash、Welcome欢迎页面简单实现(当年入门时候写的,Demo级入门示例)
  • Android进阶之路 - 快速实现启动页(基础版,可用于实战项目)

业务实战

    • 项目实战
    • AI提供方案
      • CountDownTimer + 生命周期感知
      • ViewModel + LiveData

项目实战

以下是我从项目中剥离的伪代码,主要用于解决不同生命周期,计时器带来的影响,核心思想有以下几点

  • 倒计时长根据当前计时器的变化而实时变更
  • 当处于onPause(后台)时,取消计时器
  • 当处于onResume(前台)时,将计时器剩余时长传入计时器中

因为我们不考虑横竖屏切换场景,所以在 AndroidMainfest 中直接为启动页 Activityandroid:screenOrientation="portrait"

   <activityandroid:name=".loading.SplashActivity"android:exported="true"android:screenOrientation="portrait"android:theme="@style/SplashTheme"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>

实现方式

package cn.xxxximport android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import me.jessyan.autosize.internal.CancelAdapt
import timber.log.Timber
import javax.inject.Inject@SuppressLint("CustomSplashScreen")
@AndroidEntryPoint
class SplashActivity : AppCompatActivity(), CancelAdapt {lateinit var tvJump: TextView// 倒计时长var remainingTimeInMillis: Long? = null//计时器private var countDownTimer: CountDownTimer? = null@SuppressLint("SourceLockedOrientationActivity", "MissingInflatedId")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_splash_l)//右上角跳过的视图tvJump = findViewById<TextView>(R.id.tv_jump)//初始化为计时器时间remainingTimeInMillis = 5 * 1000//跳过逻辑tvJump.onClick {countDownTimer?.cancel()next()}}override fun onResume() {super.onResume()//之所以写是因为在项目里广告时间是后台返回,如果只是单纯固定时长可去除该判断if (!remainingTimeInMillis.isNull()) {// Activity回到前台时,检查剩余时间if ((remainingTimeInMillis ?: 0) <= 0) {// 如果时间已经耗尽,直接跳转next()} else {// 如果时间还有剩余,重新启动一个计时器,从剩余时间开始startTimer(remainingTimeInMillis ?: 0)}}}override fun onPause() {super.onPause()// Activity进入后台时,立即取消计时器(防止onFinish在后台被调用),remainingTimeInMillis还保存着最新的剩余时间countDownTimer?.cancel()}//计时器private fun startTimer(time: Long) {countDownTimer?.cancel()countDownTimer = object : CountDownTimer(time, 1000) {override fun onTick(millisUntilFinished: Long) {//实时更新剩余的倒计时长remainingTimeInMillis = millisUntilFinishedmainHandler.post { tvJump.text = "${millisUntilFinished / 1000 + 1}s跳过" }}override fun onFinish() {//倒计时结束,进入对应逻辑next()}}.start()}override fun onDestroy() {super.onDestroy()countDownTimer?.cancel()countDownTimer = null}private fun next() {//可自行根据业务场景,决定跳转逻辑	// 以下为项目伪代码:判断是否首次登录,运行过帮助引导val landingVersion = SPUtils.AppSP().get(ConstValue.VER_IS_THIS_VERSION_OPEN_BEFORE, 0) as? Int?if ((landingVersion ?: 0) >= 4) {RouterPath.APP_MAIN_ACT //首页} else {RouterPath.MAIN_LANDING_ACT //引导页}.also {startRouterAndFinish(it) { putBoolean("firstStart", true) }}}
}

activity_splash_l

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"android:orientation="vertical"tools:ignore="MissingDefaultResource"><ImageViewandroid:id="@+id/background_type_1"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/drawable_app_launch"android:visibility="visible" /><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right"android:layout_marginTop="62dp"android:layout_marginEnd="15dp"android:background="@drawable/shape_splash_btn_jump_bg"android:paddingHorizontal="12dp"android:paddingVertical="4dp"><TextViewandroid:id="@+id/tv_jump"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="跳过"android:textColor="#FFFFFF"android:textSize="12dp" /></RelativeLayout>
</FrameLayout>

AI提供方案

自从使用AI后,觉得很多基础性的知识没有了记Blog的必要,更多的可能还行记录项目中遇到的问题

CountDownTimer + 生命周期感知

考虑到了横竖屏场景,兼容场景更多一些

核心要点

  1. onPause()中取消计时器:阻止它在后台触发 onFinish()跳转。
  2. 保存剩余时间:在 onTick()中持续更新 remainingTimeInMillis变量。
  3. onResume()中恢复计时:根据保存的剩余时间重新开始计时。如果时间已到,直接跳转。
  4. 处理配置变更:通过 onSaveInstanceState保存数据,防止屏幕旋转等问题。
class CountdownActivity : AppCompatActivity() {private var countDownTimer: CountDownTimer? = nullprivate var remainingTimeInMillis: Long = 10000 // 总计时时间,例如10秒private val totalTimeInMillis: Long = 10000 // 保存总时间用于恢复override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_countdown)// 从保存的状态中恢复时间(防止旋转屏幕等配置变更)if (savedInstanceState != null) {remainingTimeInMillis = savedInstanceState.getLong("REMAINING_TIME", totalTimeInMillis)}startCountdown(remainingTimeInMillis)}override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)// 保存当前剩余时间,防止配置变更(如屏幕旋转)导致时间重置outState.putLong("REMAINING_TIME", remainingTimeInMillis)}private fun startCountdown(millisInFuture: Long) {// 每次启动新计时器前,取消旧的countDownTimer?.cancel()countDownTimer = object : CountDownTimer(millisInFuture, 1000) {override fun onTick(millisUntilFinished: Long) {// 更新UI,显示剩余时间remainingTimeInMillis = millisUntilFinishedval seconds = millisUntilFinished / 1000textView_countdown.text = "剩余时间: ${seconds}秒"}override fun onFinish() {// 只有在Activity处于前台时,才执行跳转逻辑proceedToNextStep()}}.start()}private fun proceedToNextStep() {// 执行你的下一步操作,例如跳转页面val intent = Intent(this, NextActivity::class.java)startActivity(intent)finish()}override fun onPause() {super.onPause()// Activity进入后台时,立即取消计时器(防止onFinish在后台被调用)countDownTimer?.cancel()// 注意:这里我们只是取消了计时器,并没有改变remainingTimeInMillis的值// 所以remainingTimeInMillis还保存着最新的剩余时间}override fun onResume() {super.onResume()// Activity回到前台时,检查剩余时间if (remainingTimeInMillis <= 0) {// 如果时间已经耗尽,直接跳转proceedToNextStep()} else {// 如果时间还有剩余,重新启动一个计时器,从剩余时间开始startCountdown(remainingTimeInMillis)}}override fun onDestroy() {super.onDestroy()// 彻底销毁Activity时,释放计时器资源countDownTimer?.cancel()}
}

ViewModel + LiveData

符合当下主流框架、组件,适用性、兼容性高,但是对于未使用过的朋友,需要一点时间学下组件

Android Architecture Components 架构组件

  • 组件化之路 - Lifecycle一知半解
  • 组件化之路 - LiveData一知半解
  • 组件化之路 - ViewModel一知半解
  • 组件化之路 - LiveData + ViewModel一知半解

优势

  • 生命周期感知: ViewModel独立于UI生命周期,配置变更时数据不会丢失。
  • 关注点分离:计时逻辑在 ViewModel中,UI控制只在Activity中。
  • 更健壮:使用 Coroutines处理后台任务,更加现代和安全。

创建 ViewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launchclass CountdownViewModel : ViewModel() {private val _remainingTime = MutableLiveData<Long>()val remainingTime: LiveData<Long> = _remainingTimeprivate val _countdownFinished = MutableLiveData<Boolean>()val countdownFinished: LiveData<Boolean> = _countdownFinishedprivate var countdownJob: Job? = nullprivate var initialDuration: Long = 0Lfun startCountdown(duration: Long) {initialDuration = duration_remainingTime.value = duration_countdownFinished.value = falsecountdownJob?.cancel() // 取消之前的任务countdownJob = viewModelScope.launch {var timeLeft = durationwhile (timeLeft > 0 && isActive) {delay(1000)timeLeft -= 1000_remainingTime.postValue(timeLeft) // 使用postValue确保在主线程更新}if (isActive && timeLeft <= 0) {_countdownFinished.postValue(true)}}}fun pauseCountdown() {countdownJob?.cancel()}// 获取当前剩余时间,用于在UI层判断fun getCurrentTime(): Long = _remainingTime.value ?: initialDurationoverride fun onCleared() {super.onCleared()pauseCountdown()}
}

在 Activity/Fragment 中使用 ViewModel

class CountdownActivity : AppCompatActivity() {private lateinit var viewModel: CountdownViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_countdown)// 初始化ViewModelviewModel = ViewModelProvider(this).get(CountdownViewModel::class.java)// 观察剩余时间并更新UIviewModel.remainingTime.observe(this) { timeMillis ->val seconds = timeMillis / 1000textView_countdown.text = "剩余时间: ${seconds}秒"}// 观察倒计时是否结束viewModel.countdownFinished.observe(this) { isFinished ->if (isFinished) {proceedToNextStep()}}// 如果是第一次创建,开始计时if (savedInstanceState == null) {viewModel.startCountdown(10000)}}private fun proceedToNextStep() {val intent = Intent(this, NextActivity::class.java)startActivity(intent)finish()}override fun onPause() {super.onPause()// 进入后台时暂停计时viewModel.pauseCountdown()}override fun onResume() {super.onResume()val currentTime = viewModel.getCurrentTime()if (currentTime <= 0) {// 如果ViewModel中记录的时间已经用完,直接跳转proceedToNextStep()} else {// 否则,重新开始计时(从剩余时间开始)viewModel.startCountdown(currentTime)}}
}

文章转载自:

http://yjIZNMHT.Lqrpk.cn
http://U5fUqUT7.Lqrpk.cn
http://p3CnrHYY.Lqrpk.cn
http://dG2g7qed.Lqrpk.cn
http://fe7v8J1N.Lqrpk.cn
http://cPTBY3v9.Lqrpk.cn
http://aZiUg32T.Lqrpk.cn
http://Cro0BJ2l.Lqrpk.cn
http://zNpNrxFi.Lqrpk.cn
http://tYeGoc2Q.Lqrpk.cn
http://PwAWulSO.Lqrpk.cn
http://jXqX1TjX.Lqrpk.cn
http://b3eG4COH.Lqrpk.cn
http://Ox8JwKC5.Lqrpk.cn
http://Egkcpl2g.Lqrpk.cn
http://dTfYi2JR.Lqrpk.cn
http://NaddQbsm.Lqrpk.cn
http://Qg2lCofM.Lqrpk.cn
http://XsYYh3OC.Lqrpk.cn
http://btAeAkfj.Lqrpk.cn
http://7vzKCsnX.Lqrpk.cn
http://3Kp8kAOw.Lqrpk.cn
http://8KIGGOYt.Lqrpk.cn
http://6pDp66s7.Lqrpk.cn
http://nCL9QPUp.Lqrpk.cn
http://a7UHNgCB.Lqrpk.cn
http://CMhhLZPq.Lqrpk.cn
http://zFwQDcxH.Lqrpk.cn
http://bL4kgqqB.Lqrpk.cn
http://AHNMZOnj.Lqrpk.cn
http://www.dtcms.com/a/375526.html

相关文章:

  • 【从零开始编写数据库系统】基于Python语言实现存储引擎
  • 【Pywinauto库】8.3 pywinauto.findwindows 模块
  • 351章:Python Web爬虫入门:使用Requests和BeautifulSoup
  • 禅道,用域名访问之后不能登录的问题
  • Lodash-es 完整开发指南:ES模块化JavaScript工具库实战教程
  • 实践《数字图像处理》之图像方向性自适应阈值处理
  • 【Linux】系统部分——信号的概念和产生
  • android定制系统完全解除应用安装限制
  • 第2节-过滤表中的行-BETWEEN
  • OpenLayers数据源集成 -- 章节三:矢量要素图层详解
  • 基于AI Agent的智能决策支持系统正在逐步取代传统规则驱动的DSS
  • License 集成 Spring Gateway:解决 WebFlux 非阻塞与 Spring MVC Servlet 阻塞兼容问题
  • spark连接mongodb
  • ubuntu新增磁盘扩展LV卷
  • PowerApps 使用Xrm.Navigation.navigateTo无法打开CustomPage的问题
  • C/C++中基本数据类型在32位/64位系统下的大小
  • TensorFlow 和 PyTorch两大深度学习框架训练数据,并协作一个电商推荐系统
  • ceph scrub 参数
  • JavaWeb--day1--HTMLCSS
  • 全国连锁贸易公司数字化管理软件-优德普SAP零售行业解决方案
  • C++面向对象之继承
  • AI原生编程:智能系统自动扩展术
  • Wireshark TS | 接收数据超出接收窗口
  • 第一代:嵌入式本地状态(Flink 1.x)
  • 4.1-中间件之Redis
  • Django ModelForm:快速构建数据库表单
  • 【迭代】:本地高性能c++对话系统e2e_voice
  • SSE与Websocket、Http的关系
  • 蓓韵安禧DHA展现温和配方的藻油与鱼油营养特色
  • 基于UNet的视网膜血管分割系统