先发个图, 让你们知道我说的是什么:
在这之前, 写过一个用于 Xamarin.Form (以下简称 XF ) 的 FlipView, Android 下是用 HorizontalScrollView 模拟的.
上上周, 无意间看到了 ViewPager 如何实现无限循环的方法 , 于是捉摸在 Xamarin 下把它给实现一下.
在 XF 下, 你可以看到从网络加载的 大图 显示的很流畅 , 因为 XF 下, UriImageSource 这个图片源有这么两个属性:
CacheValidity 缓存多长时间
CachingEnabled 是否启用缓存
这说明在 XF 下, 已经封装好了类似于 Android 的 LRUCache 或 DiskLRUCache , 注意, 这仅仅是在 XF 下才有这个功能.
一开始不了解这个个梗, 在 Android 项目(不是 XF 项目) 遇到了这几个问题:
1, 在主线程上无法加载网络图片
不了解这个的原因, 查到的资料说在 Android 4.0 以后 (未验证) , 不允许阻塞主线程.
直接从网络加载图片明显会有阻塞, 所以运行肯定是没有结果的等待, 直至崩溃.
2, OOM
这个问题, 对搞 Android 开发的人来说, 在普通不过了.全称是 Out Of Memory, 就是内存溢出.
OOM 的解决办法介绍最多的就是用缓存, 也就是上面提到的 LRUCache (这个Android SDK 中提供的有) 和 DiskLRUCache (SDK中没有, 但是有源码).
LRUCache 是放到内存中的, 应用一关闭, 缓存的数据就丢失了.
DiskLRUCache 是把文件存放到手机的 "硬盘" (区分于内存)中.
3, Adapter 的数据源为 IEnumerable
自定义的 Adapter, 如果数据源是一个 IEnumerable , 如果传给 Adapter 之前, 没有 ToList, 在执行的时候, 会一直的去调用这个 IEnumerable 的生成函数.
如这样:
1 private IEnumerable<AV.View> GetChildrenViews() { 2 foreach (var v in this.Element.Children) { 3 var render = RendererFactory.GetRenderer(v); 4 var c = new AW.FrameLayout(this.Context); 5 c.SetBackgroundColor(Color.Blue.ToAndroid()); 6 c.AddView(render.ViewGroup, LayoutParams.MatchParent, LayoutParams.MatchParent); 7 yield return c; 8 } 9 }
如果不 ToList 就把这个函数的结果传入 Adapter, foreach 这块会一直的被调用.
4, System.IO.IOException: Sharing violation on path XXX
报这个问题, 是因为使用了 Mono 的 IsolatedStorage, 在 Stream.CopyToAsync 时如果不指定 bufferSize 就会报这个错,
诡异的是, 在公司的电脑上如果指定 bufferSize , 就没有这个错误, 在家里的电脑上指不指定 bufferSize 都会报这个错.
但是放try..catch.. 之中, 文件又可以写成功 (从调试输出的内容看, 第二次运行,不会在从网络上加载图片).
是不是我写法有问题?
1 public async Task WriteStream(string file, Stream stm) { 2 var path = this.GetPath(file); 3 4 try { 5 using (var fs = ISF.OpenFile(path, FileMode.OpenOrCreate, FileAccess.Write)) { 6 //await stm.CopyToAsync(fs); 如果不指定 buff size , 会报错 7 await stm.CopyToAsync(fs, 16384); 8 } 9 } catch { 10 11 } 12 }
5, 如何实现无限循环
这部分是参考别人的 (来源找不到了), 通俗来说, 是这样的:
A, Adapter 的 Count 属性返回真实数据条数的整数, 假如我只有3条数据, 我会欺骗 Adapter , 我有6条.
B, 在确定要显示哪一条数据的时候, 要获取给出 position 对应的真实的数据条目. 用这个 position 求余 真实数据的条目.
即在 Adapter 的 InstantiateItem 方法中, 用 position %= this.Items.Count() 来取出真正的要显示哪条数据. 假如有 3 条数据, 当前的 position 是 5, 其实要显示的就是第2条数据.
C, Adapter 的 FinishUpdate 方法.
1 public override void FinishUpdate(ViewGroup container) { 2 var vp = container as ViewPager; 3 var pos = vp.CurrentItem; 4 5 if (pos == 0) { 6 pos = this.Items.Count();///////////// 7 } else if (pos == this.Count - 1) { 8 pos = this.Items.Count() - 1;/////////////// 9 } 10 //System.Diagnostics.Debug.WriteLine("====> pos {0}", pos); 11 vp.SetCurrentItem(pos, false); 12 }
如果真实条数是 3, Adapter 的 Count 被设为 6, 就会这样:
0 1 2 0 1 2
如果当前的 pos 是 0 , 就设置 pos 为第4位上的那个0, 即第二轮的第0位 (下标从0开始啊).
如果 pos 为 5 (下标从0开始), 就设置 pos 为第一轮的最后一个, 即第一个2.
-----------------
OK , 就这些, 照例, 上源码:
直接的 Android 项目: https://github.com/gruan01/Xamarin-Example/tree/master/FlipView
用于 Xamarin.Form 的: https://github.com/gruan01/FlipView
另外, 我并没有实现网上流传的那个 DiskLruCache , 而是用 IsolatedStorage 实现了个简单的. 很大可能隐藏其它问题.