【Android】[2] 如何制作启动倒计时页

2023-10-27

前言

实现效果

倒计时截图

源码地址

https://github.com/littlecurl/AppProjects

进去找AndroidCountDown或者AndroidCountDown.zip进行下载

前提条件

【红黑联盟】《Android性能优化之Splash页应该这样设计》 主要讲了启动页优化,要把Activity变成Fragment

【深夜网】《App启动页和引导页注意事项》 主要讲了心理学中的七秒理论

【CSDN】《Android CountDownTimer 倒计时器的简单使用》 注意将了避免内存泄漏

正文

本来想直接写出优化后的效果,也就是直接用Fragment来写,但是那样对于第一次接触的人来说,学习曲线过于陡峭,所以我还是花点时间写两份吧,先写一份Activity的,再写一封优化后的Fragment的。

CountDownActivity

制作倒计时页面流程

  1. new一个新坑,在对应包下创建一个EmptyActivity,起名为CountDownActivity

  2. 修改AndroidManifest.xml文件中启动页为CountDownActivity

5850
3. 设置布局activity_count_down.xml,主布局设置背景图片,这里我还是沿用上次的写bug图,布局内只放一个TextView控件即可,内容如下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
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:background="@drawable/bug"
    tools:context=".CountDownActivity">

    <!-- android:gravity="center" 使文字在background内居中 -->
    <TextView
        android:id="@+id/tv_count_down"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:background="@drawable/bg_circle_countdown"
        android:gravity="center"
        android:text="跳过"
        android:textColor="#2c2c2c"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />


</androidx.constraintlayout.widget.ConstraintLayout>

注意TextView也有background,这个background是一个xml文件,放在了drawable目录下(话说自己动手用xml画一个圆形,我还是头一次见)

<?xml version="1.0" encoding="utf-8"?>
<!-- 定义一个半径为50dp的圆形 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#64ffffff" />
    <size
        android:width="50dp"
        android:height="50dp" />
</shape>
  1. 布局设置完毕,就该写CountDownActivity了,内容如下

    package cn.edu.heuet.androidcountdown;
    
    import androidx.appcompat.app.AppCompatActivity;
    import android.annotation.SuppressLint;
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.CountDownTimer;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
    import android.widget.TextView;
    
    public class CountDownActivity extends AppCompatActivity {
        private CountDownTimer timer;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setFullScreen();
            // 设置全屏一定要在setContentView()之前,否则有可能不起作用
            setContentView(R.layout.activity_count_down);
    
            TextView countDownText = findViewById(R.id.tv_count_down);
    
            initCountDown(countDownText);
        }
    
        // 全屏显示
        private void setFullScreen() {
            // 如果该类是 extends Activity ,下面这句代码起作用
            // 去除ActionBar(因使用的是NoActionBar的主题,故此句有无皆可)
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            // 如果该类是 extends AppCompatActivity, 下面这句代码起作用
            if (getSupportActionBar() != null){ getSupportActionBar().hide(); }
            // 去除状态栏,如 电量、Wifi信号等
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
    
        // 倒计时逻辑
        private void initCountDown(final TextView countDownText) {
            // 判断当前Activity是否isFinishing(),
            // 避免在finish,所有对象都为null的状态下执行CountDown造成内存泄漏
            if (!isFinishing()) {
                timer = new CountDownTimer(1000 * 6, 1000) {
                    @SuppressLint("SetTextI18n")
                    @Override
                    public void onTick(long millisUntilFinished) {
                        // TODO: 耗时操作,如异步登录
                        // ......
                        int time = (int) millisUntilFinished;
                        countDownText.setText(time / 1000 + " 跳过");
                        countDownText.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                checkToJump();
                            }
                        });
                    }
    
                    @Override
                    public void onFinish() {
                        checkToJump();
                    }
                }.start();
            }
        }
    
        // 首次进入引导页判断
        private void checkToJump() {
            //  TODO:首次安装判断
            // 如果是首次安装打开,则跳至引导页;否则跳至主界面
            // 这里先不放引导页,直接跳到主界面
            startActivity(new Intent(CountDownActivity.this, MainActivity.class));
    
            // 回收内存
            destoryTimer();
            finish();
        }
    
        public void destoryTimer() {
            // 避免内存泄漏
            if (timer != null) {
                timer.cancel();
                timer = null;
            }
        }
    }
    
    

    这样就能实现一个Android APP的倒计时页了,接下来对其进行优化。

CountDownFragment

一旦使用了Fragment,好像一切东西都变得复杂了。

但换个角度说,还是用的少,这些编码技术相关的东西,无他,熟能生巧耳!

所以我们紧要的事情就是去见识它们,接近它们,理解他们,运用他们。

接下来进入正题

我们先修改一下AndroidManifest.xml文件,省的一会忘了。

4403
然后我们创建一个CountDownFragment 类,extends Fragment.

前几天我写代码还是继承自v4包里的Fragment,这几天用上了AndroidStudio3.5,结果就是不再继承v4包了,而是换成了androidx包,但不要慌张,仅仅是包名换了,用法一点没变。变化详情可以参考下面这篇文章:

【简书】《Android AndroidX的迁移》 主要介绍了Android控件库的迁移及需要做的改变

如果对Fragment声明周期(就是方法执行顺序)还不了解的,可以参考

【博客园】《Fragment 生命周期的详情》 主要将官网的一些英文翻译了一下,并且配有Fragment官方声明周期图

我们先总览一下CountDownFragment类中的内容,总共就5部分

6872
具体代码如下

package cn.edu.heuet.androidcountdown;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;

public class CountDownFragment extends Fragment {

    private View rootView;
    private CountDownTimer timer;
    private ConstraintLayout cl_main_activity;
    
    // 官方文档里说了,最好每个Fragment都有一个空的构造方法
    public CountDownFragment(){

    }

    @Nullable
    @Override
    public View onCreateView(@Nullable LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState){
        rootView = inflater.inflate(R.layout.fragment_count_down, container, false);
        return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        TextView countDownText = rootView.findViewById(R.id.tv_count_down);
        // 隐藏主布局,注意这里用getActivity()来获取布局
        // rootView 指代的是 R.layout.fragment_count_down
        // getActivity() 指代的是 R.layout.activity_main
        cl_main_activity = getActivity().findViewById(R.id.cl_main_activity);
        cl_main_activity.setVisibility(View.GONE);
        initCountDown(countDownText);
    }

    // 倒计时逻辑
    private void initCountDown(final TextView countDownText) {
        // 判断当前Fragment是否加入到了Activity中
        if (isAdded()) {
            timer = new CountDownTimer(1000 * 6, 1000) {
                @SuppressLint("SetTextI18n")
                @Override
                public void onTick(long millisUntilFinished) {
                    // TODO: 耗时操作,如异步登录
                    // ......
                    int time = (int) millisUntilFinished;
                    countDownText.setText(time / 1000 + " 跳过");
                    countDownText.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            checkToJump();
                        }
                    });
                }

                @Override
                public void onFinish() {
                    checkToJump();
                }
            }.start();
        }
    }

    // 首次进入引导页判断
    private void checkToJump() {
        //  TODO:首次安装判断
        // 如果是首次安装打开,则跳至引导页;否则跳至主界面

        // 回收内存
        destoryTimer();
        // 移除Fragment,相当于Activity中的finish()
        if (isAdded() && getFragmentManager() != null) {
            getFragmentManager().beginTransaction().remove(this).commit();
            // 显示主布局
            cl_main_activity.setVisibility(View.VISIBLE);
        }
    }

    private void destoryTimer() {
        // 避免内存泄漏
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }
}

CountDownFragment类写好之后,就是在MainActivity中让他显示出来。

为此,我们修改main_activity.xml布局如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fl_count_down"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" >
    </FrameLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/cl_main_activity"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/tv_to_count_down_activity"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点我去CountDownActivity"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

        </TextView>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

可以看到,专门为Fragment开辟了一个FrameLayout布局,将其他布局一并放入嵌套的ConstraintLayout中作为主布局。

package cn.edu.heuet.androidcountdown;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        countDownFragment();

        TextView tv_to_count_down_activity = findViewById(R.id.tv_to_count_down_activity);
        tv_to_count_down_activity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, CountDownActivity.class));
            }
        });
    }
    private void countDownFragment(){
        // 开启事务
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        Fragment currentFragment = new CountDownFragment();
        // 没有被加入的话,先加入
        if (!currentFragment.isAdded()) {
            getSupportFragmentManager().beginTransaction().remove(currentFragment).commit();
            ft.add(R.id.fl_count_down, currentFragment);
        }
        // 显示Fragment
        ft.show(currentFragment);
        // commit
        ft.commitAllowingStateLoss();
    }
}

MainActivity类方法内容不是很多,主要难理解的就是Fragment使用了事务的方式来进行添加,展示。还是那句话,无他,孰能生巧耳!

本文结束!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Android】[2] 如何制作启动倒计时页 的相关文章

  • 在 Java/Android 中检查字符串是否包含 URL 的最佳方法是什么?

    在 Java Android 中检查字符串是否包含 URL 的最佳方法是什么 最好的方法是检查字符串是否包含 com net org info 其他 或者有更好的方法吗 url 输入到 Android 中的 EditText 中 它可以是粘
  • 停止在列表视图中滚动

    我的活动中有一个列表视图和一个图像按钮 当我单击图像按钮时 我想转到列表中的特定位置 我通过调用列表上的 setSelection intposition 来实现此目的 当用户滑动列表视图然后单击图像按钮时会出现问题 列表将转到指定位置但继
  • Android Activity 重新创建自身

    我的应用程序通常运行得很好 直到我在特定设备上遇到奇怪的问题 App中有2个活动 当我在 ActivityA 内启动 ActivityB 后 ActivityA 启动时没有问题 但是 当我通过按下硬件按钮或调用 finish 返回 Acti
  • Android Wear - 在没有 WearableListenerService 的情况下从 Wear 接收消息?

    我正在开发一款适用于 Android Wear 的应用程序 它可以在手机上启动活动 一切都很好 但我不想依赖手机上不断运行的后台服务来接收此消息 理想情况下 我会让 WearableListenerService 与应用程序同时启动 并保持
  • Android 堆栈大小

    我如何获取和更改 Android 应用程序的堆栈大小 即使是主线程 主线程堆栈大小是在固件中设置的 无法修改 除非修改您自己手机的固件 正如斯特朗先生指出的那样 对于您分叉的线程 您可以设置自己的堆栈大小
  • Android 4.3 虚拟设备 CPU/ABI - 未安装系统映像(eclipse)

    I m trying to set up a new device but I can not continue with the process I think it s because the target version is 4 3
  • 显示葡萄牙语字符 Android

    我正在开发一个 Android 应用程序 当它应该写 n o 或 cabe a 等单词 例如带有 或 的字符 时 它会写 或 我已经检查过源文件编码 它是UTF 8 看起来 UTF8 被解释为 ISO 8859 x 理论上 你可以提供 ja
  • Android中的OpenGL用于视频显示

    是否可以使用 OpenGL 来显示视频并能够在运行时调整视图大小 是的 它的工作原理是通过 glTexSubImage2D 将每个帧作为纹理上传 我已经测试了基于 FFmpeg 的解码器的输出 效果很好
  • Android studio 模拟器中运行的WebView无法访问互联网,但Chrome可以?

    我安装了 Android studio 1 0 1 并按照此页面操作https developer chrome com multidevice webview gettingstarted https developer chrome c
  • 在 Android 上使用 Fluidsynth 从 SoundFonts 播放音符

    有没有办法让android通过使用FluidSynth使用SoundFont文件播放声音 我一直在看jOrgan http sourceforge net apps mediawiki jorgan index php title Deve
  • 使用 Kotlin 协程替换 LocalBroadcastManager 以进行 Firebase 消息传递

    使用时Firebase 云消息传递 https firebase google com docs cloud messaging android client在 Android 上 通常需要通知当前Activity传入的推送通知 推荐的方法
  • 重置首选项的默认值

    我正在使用 CheckBoxPreference 作为设置屏幕 XML 是
  • BaseAdapter:将 hasStableIds() 设置为 false?

    我有一个ListView使用的子类BaseAdapter 适配器使用项目索引 位置 作为 id 因此 id 不稳定 底层数据的操作之一是在两个数据项之间交换 我需要在我的适配器中覆盖吗hasStableIds to return false
  • 如何在Android JUnit测试用例中调用Button.performClick?

    我是 Android 测试方面的新手 我想测试单击按钮是否会打开相应的活动 我做了一些研究 发现我需要使用 ActivityManager 来进行检查 问题是 我无法让 点击 部分正常工作 我正在尝试使用Button performClic
  • 设置 LinearLayout 的最大宽度

    如何设置水平线的最大宽度LinearLayout 因此 如果内容较短 例如某些文本 布局会缩小 如果内容较长 则布局不会扩展超过某个最大宽度值 我更喜欢在 XML 级别执行此操作 这就是我所需要的超出了之前答案中的建议 https stac
  • Android:列表视图崩溃

    我正在使用 android listview 并且它工作得很好 我的实现如下 ListView listview ListView findViewById R id list setListAdapter new ArrayAdapter
  • 如何使用Android Invalidate()

    在我的主要活动中 我定义了两个视图和一个菜单 浏览次数 1 自定义视图游戏 2 按钮btn 菜单 1 打开项目用于打开文件 菜单布局在不同的活动中定义 基本上 当主活动启动时 它会绘制没有任何内容的自定义视图和按钮 然后我使用菜单中的 打开
  • AAR 可以包含传递依赖吗? [复制]

    这个问题在这里已经有答案了 现在我有一个库项目 比如项目 Foo 它依赖于像 OkHttp 这样的库 现在 Foo 有一个 Maven 构建步骤 可以生成 AAR 并将其推送到公共位置 现在假设我有项目 B 我们将其称为 Bar Bar是一
  • 膨胀类 android.support.design.internal.BottomNavigationView 时出错

    我正在制作我的第一个应用程序 这是一个简单的应用程序 带有启动屏幕和主要活动 现在我有两个构建变体 免费版本 活动底部有 Admob 横幅 付费版本 该应用程序不会在底部显示 admob 横幅 而是将其替换为用于切换活动的底部导航视图 我将
  • 如何在不显示父活动的情况下将一个全屏对话框片段替换为另一个全屏对话框片段?

    我有一个使用单个自定义 DialogFragment 类的活动 它的外观是数据驱动的 因此不同的调用看起来可能相当不同 它是 全屏 即 setStyle DialogFragment STYLE NO FRAME android R sty

随机推荐

  • Lock wait timeout exceeded; try restarting transaction

    2016 08 05 09 21 52 com zhishi common controller BaseController ERROR Error updating database Cause com mysql jdbc excep
  • 教师怎样将成绩单独发送给学生和家长?

    在工作学习中 我们经常会遇到怎样将成绩单独发送给学生和家长这样的问题 因此 面对怎样将成绩单独发送给学生和家长我们应该有努力探索的精神 诚挚的心灵 是学生情感的钥匙 高尚的师德 是学生心灵的明镜 对于这个问题也是一样的 读书忌死读 死读钻牛
  • redhat7.6安装weblogic12c

    目录 一 环境准备 二 使用root创建用户和组 三 创建部署目录 四 上传安装包 五 创建 oraInst loc 文件 六 创建wls rsp 响应文件 七 进行安装 八 使用 wlst sh 离线模式创建一个域 九 启动服务 十 浏览
  • 电脑wps可以语音录入吗_怎样用word进行语音录入文字

    怎样用 word 进行语音录入文字 有时候我们不方便手敲键盘输入时 可以利用 word 的语音录入功能进行输入 以下是学习啦小编为您带来的关于用 Word 进行语音录入文字 希望对您有所帮助 用 Word 进行语音录入文字 语音输入功能添加
  • 最适合程序猿的笔记软件

    因为这几天小编要去听课 所以心血来潮找了几个适合程序猿的笔记软件 经过几天在csdn上的扒饭之后 我结合自己的某些要求 为大家整理出了这几个软件 有需要直接去后面找 一 必须支持markdown markdown的重要性不需要在这里多说了吧
  • TypeScript 联合类型(union type)

    TS是JS的超集 在JS的基础上添加了一套类型系统 这样的TS可以被静态分析带来的好处显而易见 let val string val 声明一个string类型的变量val let val string val val 1 Type numb
  • hive异常MetaException-Metastore contains multiple versions

    在执行hive运行脚本时 出现了MetaException Metastore contains multiple versions异常错误 Exception in thread main java lang RuntimeExcepti
  • Java 手动分页

    功能需求背景 今天负责短信后台定时任务时 需要定时向用户发送短信信息 但数据库记录的待发送记录数量比较大 无法一次查询出结果 需要手动分页 手动分页核心功能代码 Date now DateUtils getBeforeMouth new D
  • Arduino基本知识

    analogWrite 将一个模拟数值写进Arduino引脚 这个操作可以用来控制LED的亮度 或者控制电机的转速 Arduino每一次对引脚执行analogWrite 指令 都会给该引脚一个固定频率的PWM信号 digitalRead 读
  • 基于FPGA的图像采集之二 GEN_FRAME(成帧)模块

    距离上次的博客已经有段时间了 这写些日子一直在调SDRAM的模块以及文档的书写 SDRAM的子模块比较多 包括init 初始化模块 refresh 刷新模块 write 写模块 read 读模块 使用起来相比之前的USB控制模块 今天的GE
  • JAVA中队列,数据结构队列入队操作

    java中构造函数和构造方法的区别 Java中什么是构造函数 构造函数和普通函数的区别如下 1 写法上的不同 施工方法 Public modifier class 定义类的关键字 Test 类名 没有参数 测试 类名 接受一个参数 测试 类
  • 使用Visual Studio写一个简单的Windows窗体应用登录界面

    需要的知识 C 的基本语法 以及Visual Studio的基本操作方法 编辑软件 Sql Server 2017 Visual Studio 2017 前提 Sql Server 中有一个名为 MY LAPTOP 的服务器 一个名为 Te
  • 初学(9)——Hadoop错误:ssh: Could not resolve hostname master: Name or service not known

    进行ssh访问时出现错误 ssh Could not resolve hostname master Name or service not known 解决方法 修改hosts文件 将名称和IP建立联系 1 打开 etc目录下hosts文
  • 深入理解Go——context(2)

    文章目录 结构体 emptyCtx cancleCtx timerCtx valueCtx 结构体 emptyCtx 源码中定义了 Context 接口后 并且给出了一个实现 type emptyCtx int func emptyCtx
  • Spring Boot 实现各种参数校验

    目录 1 简单使用 1 引入依赖 2 requestBody参数校验 3 requestParam PathVariable参数校验 4 统一异常处理 2 进阶使用 1 分组校验 2 嵌套校验 3 集合校验 4 自定义校验 5 编程式校验
  • 完整的微信小程序支付开发记录(亲测)

    这次呢是开发小程序的支付功能 因为没有做过 特此记录 做一个小总结 以便以后使用以及给小伙伴们提供一个像我一样的小白一个参考 我也是一点一点摸索过来的 此文只针对开发支付流程而言以及出现的问题 其它则会略过 只讲解实际动手开发过程 名词和实
  • findbugs错误总结

    本篇是从别人那找到的 为了让我回头查看findbugs错误怎么解决而保存的 有很多问题其实挺隐晦的 比如第三条 还有人会使用 来判断常量字符串和String类型是否相等 这个就是基础不牢的缘故了 记得把findbugs尽量清零哦 1 NP
  • 简单的postgersql存储过程样例

    建表 CREATE TABLE Employees Id serial Name VARCHAR 100 DateOfBirth Date City VARCHAR 100 Designation VARCHAR 100 JoiningDa
  • 小米r3g openwrt软件源,换源

    小米r3g openwrt软件源 换源 自带的源不好用 自己找了一个 还是在中科大源站上改的 中科大这个网站上的资源太多了 ssh登录后替换 etc opkg distfeeds conf 里面的源地址 也可能是 etc opkg conf
  • 【Android】[2] 如何制作启动倒计时页

    前言 实现效果 源码地址 https github com littlecurl AppProjects 进去找AndroidCountDown或者AndroidCountDown zip进行下载 前提条件 红黑联盟 Android性能优化