第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 实现“刷新”与“停止刷新”的功能
- 在
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; }
- 在
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; }
- 在
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
开始更新视频信息,此时用户点击“暂停刷新”,任务停止了,然后用户又点击“刷新”。我们会发现以前被列出的视频再次被列了出来。
原来,刷新的时候,没有将已经显示的视频与没有显示的视频区分开,已经添加过的又被重新添加了。
解决的办法就是,在添加视频信息到数据列表里面之前,先检查一下这些视频是否已经被添加过了,如果添加过了,那就不用再添加了。
- 重写
VideoItem
的比较方法,让比较两个VideoItem
的原则变成:只要文件所在的路径是相同的,就认为这两个比较项指的是同一个,@Override public boolean equals(Object o) { VideoItem another = (VideoItem) o; return another.path.equals(this.path); }
- 在
VideoUpdateTask
的doInBackground()
函数中,添加视频信息到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的解法又引入了另一个问题:如果有的视频是重复的,那么能不能在给它创建视频缩略图的时候,先忽略掉它,因为创建视频缩略图是个非常消耗内存和时间的事情。
所以我们要修改一下创建缩略图的策略,不要让它已开始就创建,而是让我们根据条件来决定是否要创建缩略图。
- 修改
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; } }
- 在
VideoUpdateTask
的doInBackground()
函数中,@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
中所有多余的视频清除,只能在视频查询完成后才能进行,而且这些真正存在的视频信息还得保存下来,在最后的比较中会使用到。
- 将真正存在的视频信息,再保存一份,
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; }
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(); }
至此,视频刷新的功能就完成了。
第6节 视频列表的横屏
设备在横放或者竖放的时候都会自动调整应用的布局,进行对应的横屏显示或者竖屏显示。我们的视频列表也是如此。
我们可以用两种方法处理设备旋转的问题,
- 让Activity不跟随设备方向的旋转而旋转,它只有竖屏(或只有横屏)的界面。要做到这一点很容易,在
AndroidManifest.xml
文件中,给这个Activity组件加上android:screenOrientation="portrait"
(保持竖屏)或者
android:screenOrientation="landscape"
(保持横屏)的属性就可以了;<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.anddle.anddleplayer"> <application ......> <activity android:name=".VideoListActivity" android:screenOrientation="landscape"> </activity> ...... </application> </manifest>
- 让Activity跟随设备方向的旋转而旋转。
我们准备采用第二种处理方式。
6.1 Activity周期切换
屏幕方向变化时,Activity的生命周期也将发生变化。
从一个Activity创建出来,到显示,再到用户主动退出销毁这个Activity,它将经历:
onCreate()
->
onStart()
->
onResume()
->
用户可以与Activity交互->
onPause()
->
onStop()
->
onDestroy()
;
如果一个Activity从创建出来,到显示,然后旋转,那么它将经历:
onCreate()
->
onStart()
->
onResume()
->
用户可以与Activity交互,此时屏幕进行旋转,从竖屏变横屏->
onSaveInstanceState()
->
onPause()
->
onStop()
->
onDestroy()
->
onCreate()
->
onStart()
->
onRestoreInstanceState()
->
onResume()
->
用户可以与Activity交互;
可以看到,旋转的时候onCreate()
函数会被再次调用。在这里,如果拥有横屏布局文件,onCreate()
中的setContentView()
将会使用横屏的布局,如果没有,依然使用默认的布局文件。
6.2 屏幕旋转产生的问题
如果视频列表从竖屏变成横屏,那么会先执行onDestroy()
,再进行一次onCreate()
创建的过程。我们在界面上就会看到,一个已经展现了所有视频信息的列表,在旋转之后,又要重新开始查询一次。
这显然没有必要。因此我们需要对视频列表界面,做一点修改,让它不要每次旋转就去完全刷新。
在AndroidManifest.xml
文件中,给这个Activity组件加上android:screenOrientation="orientation|screenSize"
的属性就可以了;
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.anddle.anddleplayer">
<application
......>
<activity
android:name=".VideoListActivity"
android:configChanges="orientation|screenSize">
</activity>
......
</application>
</manifest>
这种Activity从创建出来,到显示,然后旋转,那么它将经历:
onCreate()
->
onStart()
->
onResume()
->
用户可以与Activity交互,此时屏幕进行旋转,从竖屏变横屏->
onConfigurationChanged()
;
如此一来,这个Activity在旋转时就不会重走销毁、创建的过程了,而只是在旋转后经历一个onConfigurationChanged()
。这种情况下,如果存在它对应的横屏布局文件,那么这个布局也不会被使用到,因为onCreate()
并没有被调用到。