手把手教你做视频播放器(四)-刷新与停止刷新列表

本文介绍了一个应用程序中视频刷新功能的设计与实现过程,包括如何通过菜单项控制刷新与停止刷新,解决刷新过程中出现的问题如避免重复刷新和清理已被删除的视频记录。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第5节 刷新与停止刷新列表

虽然经过我们的假设,忽略了很多不需要关注的视频文件,但设备上依然有可能有很多的满足了我们假设条件的视频存在,这时就需要一个“取消刷新”的功能。

如果视频还没有刷新完,就被取消了,然后又希望继续刷新,那么还需要一个手动开始“刷新”的功能。

因此,准备在ActionBar的右上角,设置一个菜单项,让用户可以“刷新”,也能“停止刷新”。

5.1 添加刷新菜单项

在制作“计算器”的文档里面,我们已经介绍了如何添加ActionBar菜单项,以及如何响应菜单项的点击,

定义菜单项到res\xml\menu.xml文件中,app:showAsAction属性设置成walways

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_refresh"
        app:showAsAction="always"
        android:title="@string/refresh"/>
</menu>

5.2 实现“刷新”与“停止刷新”的功能

  1. onCreateOptionsMenu()函数中,使用定义的菜单,获取“刷新”功能的菜单项,根据当前VideoUpdateTask的状态,来确定要显示的菜单名字,

    private MenuItem mRefreshMenuItem;
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
    
        //获取“刷新”菜单项
        mRefreshMenuItem = menu.findItem(R.id.menu_refresh);
    
        //当VideoUpdateTask处于运行的状态时,菜单项的标题显示“停止刷新”,
        if((mVideoUpdateTask != null) && (mVideoUpdateTask.getStatus() == AsyncTask.Status.RUNNING)) {
            mRefreshMenuItem.setTitle(R.string.in_refresh);
        }
        //当VideoUpdateTask没有处于运行的状态时,菜单项的标题显示“刷新”,
        else {
            mRefreshMenuItem.setTitle(R.string.refresh);
        }
    
        return true;
    }
  2. onOptionsItemSelected()中,根据当前VideoUpdateTask的状态,来确定如何响应用户的点击操作,

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    
        switch(item.getItemId())
        {
            case R.id.menu_refresh:
            {
                if((mVideoUpdateTask != null) && (mVideoUpdateTask.getStatus() == AsyncTask.Status.RUNNING)) {
                    //当VideoUpdateTask处于运行的状态时,取消VideoUpdateTask的工作
                    mVideoUpdateTask.cancel(true);
                    mVideoUpdateTask = null;
                }
                else {
                    //当VideoUpdateTask没有处于运行的状态时,启动VideoUpdateTask的工作
                    mVideoUpdateTask = new VideoUpdateTask();
                    mVideoUpdateTask.execute();
                    //修改菜单项的标题为“停止刷新”
                    if(mRefreshMenuItem != null) {
                        mRefreshMenuItem.setTitle(R.string.in_refresh);
                    }
                }
            }
            break;
    
    
            default:
                return super.onContextItemSelected(item);
    
        }
    
        return true;
    }
  3. VideoUpdateTask被成功取消工作后,它的onCancelled()函数会被调用;在VideoUpdateTask工作顺利完成后,它的onPostExecute()函数会被调用,它们是在主线程中运行的,所以可以在这里修改界面,

    @Override
    protected void onPostExecute(Void result) {
        updateResult()
    }
    
    @Override
    protected void onCancelled() {
        updateResult();
    }
    
    private void updateResult()
    {
        //修改菜单项的标题为“停止刷新”
        if(mRefreshMenuItem != null) {
            mRefreshMenuItem.setTitle(R.string.refresh);
        }
    }

5.3 刷新遗留的问题

5.3.1 问题1

当应用启动后,VideoUpdateTask开始更新视频信息,此时用户点击“暂停刷新”,任务停止了,然后用户又点击“刷新”。我们会发现以前被列出的视频再次被列了出来。

原来,刷新的时候,没有将已经显示的视频与没有显示的视频区分开,已经添加过的又被重新添加了。

解决的办法就是,在添加视频信息到数据列表里面之前,先检查一下这些视频是否已经被添加过了,如果添加过了,那就不用再添加了。

  1. 重写VideoItem的比较方法,让比较两个VideoItem的原则变成:只要文件所在的路径是相同的,就认为这两个比较项指的是同一个,

    @Override
    public boolean equals(Object o) {
        VideoItem another = (VideoItem) o;
    
        return another.path.equals(this.path);
    }
  2. VideoUpdateTaskdoInBackground()函数中,添加视频信息到ListView的数据集-mVideoItemList之前,先判断里面是否已经包含了这个视频,如果没有包含,才发送给主线程更新界面;比较视频是否相同的依据,就是前面重写的equals()函数,

    @Override
    protected Void doInBackground(Object... params) {
    
        ......
        if(cursor != null)
        {
            while(cursor.moveToNext())
            {
                ......
                VideoItem item = new VideoItem(path, name, createdTime);
    
                //判断出之前没有这个视频,才发送给主线程更新界面
                if(mVideoList.contains(data) == false) {
    
                    publishProgress(item);  
    
                }
            }
    
            cursor.close();
        }
    
        return null;
    }

5.3.2 问题2

问题1的解法又引入了另一个问题:如果有的视频是重复的,那么能不能在给它创建视频缩略图的时候,先忽略掉它,因为创建视频缩略图是个非常消耗内存和时间的事情。

所以我们要修改一下创建缩略图的策略,不要让它已开始就创建,而是让我们根据条件来决定是否要创建缩略图。

  1. 修改VideoItem的设计成,

    VideoItem(String strPath, String strName, String createdTime) {
    
        this.path = strPath;
        this.name = strName;
    
        SimpleDateFormat sf = new SimpleDateFormat("yy年MM月dd日HH时mm分");
        Date d = new Date(Long.valueOf(createdTime)*1000);
        this.createdTime = sf.format(d);;
    }
    
    //将创建缩略图的功能独立出来
    void createThumb()
    {
        if(this.thumb == null)
        {
            this.thumb = ThumbnailUtils.createVideoThumbnail(this.path, MediaStore.Images.Thumbnails.MINI_KIND);
        }
    }
    
    //与createThumb()对应,需要一个释放bitmap资源的方法
    void releaseThumb()
    {
        if(this.thumb != null){
            this.thumb.recycle();
            this.thumb = null;
        }
    }
  2. VideoUpdateTaskdoInBackground()函数中,

    @Override
    protected Void doInBackground(Object... params) {
    
        ......
        if(cursor != null)
        {
            while(cursor.moveToNext())
            {
                ......
                VideoItem item = new VideoItem(path, name, createdTime);
    
                if(mVideoList.contains(data) == false) {
                    //判断需要添加,才创建缩略图
                    item.createThumb();
                    publishProgress(item);  
    
                }
            }
    
            cursor.close();
        }
    
        return null;
    }

5.3.3 问题3

如果用户暂停刷新后,设备中的视频因为别的原因被删除了一个,即使采用问题1的解决办法也不会有效果,因为ListView中的保存的视频信息个数比真实刷新到的要多,没有把ListView中多余的数据给清除掉。

所以,要把ListView中所有多余的视频清除,只能在视频查询完成后才能进行,而且这些真正存在的视频信息还得保存下来,在最后的比较中会使用到。

  1. 将真正存在的视频信息,再保存一份,

    List<VideoItem> mDataList = new ArrayList<VideoItem>();
    
    @Override
    protected Void doInBackground(Object... params) {
    
        ......
        if(cursor != null)
        {
            while(cursor.moveToNext())
            {
                ......
                VideoItem item = new VideoItem(path, name, createdTime);
    
                if(mVideoList.contains(data) == false) {
                    data.createThumb();
                    publishProgress(item);  
    
                }
    
                //保存起来
                mDataList.add(data);
    
            }
    
            cursor.close();
        }
    
        return null;
    }
  2. VideoUpdateTask结束工作以后(无论是取消还是顺利完成),会调用到onCancelled() onPostExecute(),所以可以在它们共同调用的updateResult()中清除ListView中多余的视频信息,

    private void updateResult() {
    
        ......
        for(int i = 0; i < mVideoList.size(); i++)
        {
            if(!mDataList.contains(mVideoList.get(i)))
            {
                //释放缩略图占用的内存资源
                mVideoList.get(i).releaseThumb();
                //从ListView的数据集中移除多余的视频信息
                mVideoList.remove(i);
                //因为移除了一个视频项,下一个视频项的序号就被减小了一个1
                i--;
            }
        }
        mDataList.clear();
    
        //通知`ListView`数据有改变,需要刷新
        VideoItemAdapter adapter = (VideoItemAdapter) mVideoListView.getAdapter();
        adapter.notifyDataSetChanged();
    }

至此,视频刷新的功能就完成了。


/*******************************************************************/
* 版权声明
* 本教程只在优快云安豆网发布,其他网站出现本教程均属侵权。

*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

<think>好的,我现在需要帮用户生成一个用点阵显示9到0倒计时牌的代码,参考的是宋雪松的《手把手教你学51单片机——C语言版(第2版)》。首先,我得理清楚这个任务的关键点。 首先,点阵显示通常用的是8x8的LED点阵,或者可能用更大的,比如16x16?不过常见的例子可能用8x8。用户可能用的是8x8,因为教材里可能以这个为例。接下来,倒计时从9到0,每个数字需要对应的点阵数据。所以第一步是确认每个数字的显示编码,也就是字模数据。 然后,硬件连接方面,点阵的行和列需要连接到单片机的IO口。通常行是控制阳极,列是阴极,或者反过来,这取决于电路设计。可能需要用到锁存器如74HC595来扩展IO口,或者直接连接,具体得看教材里的示例。假设用户使用的是共阳型点阵,行接P0口,列由P2和P3控制,或者其他端口组合。需要确定具体的引脚连接,但如果没有具体信息,可能需要做一个通用的例子,或者参考教材里的典型接法。 接下来是代码结构。需要初始化定时器,用于动态扫描显示,因为点阵是逐行或逐列扫描的。动态扫描的原理是快速逐行显示,利用人眼的视觉暂留效果。所以定时器中断服务程序需要定期更新当前显示的行数据。 然后,每个数字对应的点阵数据需要定义成数组。比如数字0到9,每个数字8个字节,每个字节对应一行的LED状态。例如,0可能是一个环形,中间空的点阵排列。 倒计时的逻辑部分,主函数里需要控制倒计时的时间,比如每秒减一,从9到0。这里可能需要使用定时器来精确计时,或者用简单的延时函数。不过为了准确,定时器可能更合适。但考虑到代码复杂度,用户可能希望先看到基本实现,可能先用延时函数,后面再优化。 可能的步骤: 1. 定义数字0-9的点阵字模数组。 2. 配置定时器中断,用于动态刷新点阵显示。 3. 主函数中循环倒计时,每个数字显示1秒。 4. 中断服务函数中,逐行扫描显示当前数字的点阵数据。 需要检查点阵的扫描方向是否正确,比如行是从上到下还是下到上,列是高位在前还是低位,这会影响字模的定义是否正确。如果显示出现乱码,可能需要调整字模的顺序或扫描的方式。 此外,硬件连接可能涉及到行和列的驱动电路,比如使用上拉电阻或者晶体管来增强电流驱动能力,但代码中可能不需要处理这部分,只要正确设置IO口的高低电平即可。 现在,编写具体的代码: 首先,定义字模数据。例如,假设8x8点阵,每个数字8字节,每个字节代表一列(或一行,需要确认扫描方式)。比如数字0的字模可能是0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C,但需要确认实际排列是否正确。或者可能是行扫描,每个字节对应一行,比如第一行是0b00111100(0x3C)等。这需要根据点阵的实际连接方式确定。 假设点阵的行由P0口控制,列由P2和P3控制,共16列?不,8x8的话应该只有8行和8列。可能行用P0,列用P2或其他端口。例如,行接P0口,列接P1口,或者反过来。这可能需要查阅教材中的示例电路。比如教材中可能用P0控制列,P2控制行?或者相反。 例如,假设行选择(阳极)由P2的低8位控制,列数据(阴极)由P0口控制,共阳型。那么要显示某一行,需要将对应的行置高,然后P0输出该行的列数据取反(因为共阳的话,列是低电平点亮)。 或者,可能行列都是通过锁存器连接的,但如果没有锁存器的话,可能需要直接连接IO口。 这部分可能需要假设,比如行由P2口控制,列由P0口控制。假设行是选择哪一行,列是控制该行的哪些LED点亮。 例如,动态扫描时,依次选中每一行,输出该行对应的列数据。例如,第一行选中,P2=0x01,然后P0输出该行对应的数据,延时一段时间,然后切换到下一行。 但为了使用定时器中断,可以在中断中切换当前行,并输出对应的数据。 现在,开始编写代码结构: 1. 包含头文件,如reg52.h。 2. 定义数字的字模数组,如code unsigned char num[10][8]。 3. 声明变量:当前显示的数字(count),当前扫描的行(row)。 4. 初始化定时器,设置中断。 5. 主函数中循环倒计时,每秒减一。 6. 中断服务函数处理显示刷新。 但需要注意,点阵显示需要不断刷新,所以倒计时的延时不能使用普通的延时函数,否则会导致显示闪烁。应该利用定时器中断来管理时间,同时主函数中检测时间是否到达1秒。 可能的调整:使用定时器中断来刷新显示,同时另一个定时器或计数器来计算秒数。或者,在中断中累加计数,达到一定次数后认为1秒到了。 例如,假设定时器中断每1ms触发一次,用于刷新显示,同时每1000次中断(即1秒)后,减少倒计时的数字。 这样,代码的大致结构: - 定义字模数组。 - 全局变量:count(当前数字),row(当前行),timerCount(计时用)。 - 定时器初始化,1ms中断一次。 - 中断服务函数:刷新显示行,并累加timerCount,当达到1000时,count减一。 - 主函数初始化后,进入循环,等待count变化。 但需要注意变量在中断和主循环中的修改,可能需要使用volatile类型,并处理计数器的重置。 现在,具体代码: 首先,字模数组的定义。假设每个数字有8行,每行8位,例如: code unsigned char NumTab[10][8] = { // 0的字模 {0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C}, // 1的字模 {0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x08, 0x3E}, // ... 其他数字的字模需要正确填写 }; 但实际每个数字的字模可能需要根据点阵的排列来调整。例如,不同的扫描方向可能导致数据顺序不同。这里可能需要用户根据实际硬件调整字模,或者参考教材中的示例。 然后,硬件连接假设:行选择由P2口控制,列数据由P0口控制,共阳型。例如,行选择是P2.0到P2.7,每一行为高电平选中。列数据需要输出低电平来点亮LED。例如,当P2=0x01(选中第一行),P0输出对应的该行数据(如0x3C对应二进制00111100,即低电平的位会点亮)。 所以,在中断函数中,每次选中一行,输出对应的列数据,然后切换到下一行。 代码示例: #include <reg52.h> code unsigned char NumTab[10][8] = { // 0 {0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C}, // 1 {0x08, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C}, // 2 {0x3E, 0x42, 0x02, 0x0C, 0x30, 0x40, 0x40, 0x7E}, // 3 {0x3C, 0x42, 0x02, 0x1C, 0x02, 0x02, 0x42, 0x3C}, // 4 {0x04, 0x0C, 0x14, 0x24, 0x7E, 0x04, 0x04, 0x04}, // 5 {0x7E, 0x40, 0x40, 0x7C, 0x02, 0x02, 0x42, 0x3C}, // 6 {0x1C, 0x20, 0x40, 0x7C, 0x42, 0x42, 0x42, 0x3C}, // 7 {0x7E, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10}, // 8 {0x3C, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x42, 0x3C}, // 9 {0x3C, 0x42, 0x42, 0x42, 0x3E, 0x02, 0x04, 0x38}, }; unsigned char count = 9; // 当前显示的数字 unsigned char row = 0; // 当前扫描的行 unsigned int timer = 0; // 计时用变量 void Timer0_Init(); void Display(unsigned char num); void main() { Timer0_Init(); EA = 1; // 开启总中断 while (1) { // 主循环中不需要其他操作,倒计时由定时器处理 } } void Timer0_ISR() interrupt 1 { TH0 = (65536 - 2000) / 256; // 2ms定时 TL0 = (65536 - 2000) % 256; Display(count); // 显示当前数字 timer++; if (timer >= 500) // 2ms * 500 = 1000ms { timer = 0; if (count > 0) { count--; } else { count = 9; // 循环倒计时 } } } void Display(unsigned char num) { // 显示指定数字 P2 = 0x01 << row; // 选中当前行 P0 = ~NumTab[num][row]; // 输出列数据(取反,因为共阳) row++; if (row >= 8) { row = 0; } } 这里可能存在的问题: 1. 字模数据是否正确。由于每个教材或点阵的排列可能不同,字模可能需要调整。例如,某些点阵的行列顺序可能相反,或者数据方向不同。 2. 定时器中断的时间设置。这里假设每2ms中断一次,每次中断显示一行,这样8行需要16ms刷新一次,刷新率约为60Hz,不会有闪烁。同时,计时到500次中断即1秒。 3. 共阳或共阴的配置是否正确。如果点阵是共阴型,那么P0的输出不需要取反,行选择也可能是低电平有效。这里假设为共阳,所以列输出低电平点亮,所以取反字模数据。 4. 硬件连接是否正确。例如,行选择由P2控制,列由P0控制,这可能需要根据实际电路调整。例如,如果列接P1口,则代码中的P0需要改为P1。 5. 初始数字设置为9,倒计时到0后可能回到9,这里用户可能需要只进行一次倒计时,可以修改在count为0时停止,或者重新开始。 6. 另外,点阵的引脚可能需要驱动能力,比如P0口需要上拉电阻,因为P0口在51单片机是开漏输出,所以可能需要接上拉电阻才能输出高电平。或者代码中使用P2口作为行选择,而P0口作为列数据输出,并且有上拉电阻。 这些细节可能需要用户根据实际硬件调整。特别是字模数据和端口连接部分,可能需要用户根据教材中的示例电路修改。 总结:提供的代码是一个基于假设的示例,用户可能需要根据实际硬件调整端口定义和字模数据,并测试显示效果。</think>根据宋雪松教材内容,以下是基于8x8点阵的倒计时显示代码实现步骤及示例: ### 一、硬件连接假设 1. **点阵类型**:共阳型8x8 LED点阵 2. **行控制**:P2口(行选择,高电平有效) 3. **列控制**:P0口(列数据,低电平点亮) 4. **晶振频率**:11.0592MHz ### 二、代码实现 ```c #include <reg52.h> // 数字0-9的点阵字模(共阳型,列数据取反) code unsigned char NumTab[10][8] = { {0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C}, // 0 {0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x08, 0x3E}, // 1 {0x3E, 0x42, 0x02, 0x0C, 0x30, 0x40, 0x40, 0x7E}, // 2 {0x3C, 0x42, 0x02, 0x1C, 0x02, 0x42, 0x42, 0x3C}, // 3 {0x04, 0x0C, 0x14, 0x24, 0x7E, 0x04, 0x04, 0x04}, // 4 {0x7E, 0x40, 0x7C, 0x02, 0x02, 0x42, 0x42, 0x3C}, // 5 {0x3C, 0x42, 0x40, 0x7C, 0x42, 0x42, 0x42, 0x3C}, // 6 {0x7E, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10}, // 7 {0x3C, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x42, 0x3C}, // 8 {0x3C, 0x42, 0x42, 0x3E, 0x02, 0x04, 0x08, 0x30} // 9 }; unsigned char count = 9; // 当前显示数字 unsigned char row = 0; // 当前扫描行 unsigned int timer = 0; // 计时变量 void Timer0_Init(); void Display(unsigned char num); void main() { Timer0_Init(); EA = 1; // 开启总中断 while (1) { // 主循环保持空转,逻辑由中断处理 } } // 定时器0初始化(2ms中断) void Timer0_Init() { TMOD = 0x01; TH0 = (65536 - 2000) >> 8; TL0 = (65536 - 2000) & 0xFF; TR0 = 1; ET0 = 1; } // 定时器0中断服务函数 void Timer0_ISR() interrupt 1 { TH0 = (65536 - 2000) >> 8; // 重新加载初值 TL0 = (65536 - 2000) & 0xFF; Display(count); // 显示当前数字 timer++; if (timer >= 500) { // 500*2ms=1s timer = 0; if (count > 0) count--; else count = 9; // 循环计数 } } // 点阵显示函数 void Display(unsigned char num) { P2 = 0x01 << row; // 行选通(P2.0~P2.7) P0 = ~NumTab[num][row]; // 列数据取反输出 row = (row + 1) % 8; // 行扫描循环 } ``` ### 三、代码说明 1. **字模数据** - 使用$8\times8$矩阵编码,每个数字对应8字节数据 - 共阳型点阵需对列数据取反(`~`操作符) 2. **定时器配置** - 定时器0每2ms中断一次,实现动态扫描 - 计算公式: $$T = \frac{12}{11.0592\text{MHz}} \times (65536 - N)$$ 设置$N=2000$实现约2ms定时 3. **动态扫描原理** - 每次中断刷新一行,8行扫描周期为$16\text{ms}$ - 扫描频率$f = \frac{1}{16\text{ms}} \approx 62.5\text{Hz}$,无闪烁 4. **倒计时逻辑** - 每500次中断(1秒)更新数字 - 倒计时到0后自动重置为9 ### 、调试建议 1. **字模验证**:可通过Proteus仿真观察显示效果,调整编码 2. **硬件适配**:若使用共阴型点阵,需删除`P0=~`操作 3. **驱动增强**:P0口建议外接$1\text{k}\Omega$上拉电阻增强驱动能力 > 实际开发中请根据硬件电路调整行/列控制端口及字模数据。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值