补充:源码git地址
看了一下抖音的音乐裁剪(IOS),看上去很不错,所以决定实现一下。一路上写了三四个版本,遇到了很多问题,在这里分享一下。首先看下效果:
在说明怎么实现之前,我先分享下我在做的过程中的思路。
看到这个动画效果,第一反应就是属性动画,根据宽度动态绘制。那么这种动态效果怎么实现呢:刚开始想通过setXfermode来实现,后来发现颜色不太对,因为你的背景色是有透明度的,后来出来的图也是有透明度的,不好看。
因为绘制的时候,每个小柱子的宽度是按照屏幕宽度除以个数手动计算的,那么我为什么不直接用蓝色直接绘制一个覆盖到透明的上面,这里有个问题:你绘制的时候,小柱子是按照一个为单位绘制的,但是动画可能出现只在半个柱子上,所以还得切图,Bitmap.createBitmap控制起始位置和宽度,问题解决。
没有经过大脑的想法:既然一屏实现了,那么多屏的话,我在刚开始绘制view的时候,把宽度搞得很大不就行了!
Bitmap too large to be uploaded into a texture。当头棒喝。。。
查了一下是硬件加速的问题,但是禁止硬件加速有可能卡顿,虽然在demo里测试一下没有卡顿,但是想了想万一多屏的个数长达几十个呢?这个方案不行。
既然不能直接画出来大图,那我一个一个的放到scrollview里总行了吧。scrollview –>LinearLayout –> 好多个自定义view
试了一下,不错不错效果可以啊。
PS:这个时候我往里面添加了20个view
后来想了想,万一里面到时候需要几十甚至上百个view呢?因为按照抖音的逻辑,我一屏显示的宽度,代表录制视频的时间,总宽度代表音乐时间,如果我视频只有三秒,但是音乐是肖邦的夜曲呢(五分多钟),这时候是将近106屏,要死啊。试了一下:果然崩了!!! 原因就是OOM,想想也是,这么多view绘制怎么不蹦。
既然因为OOM崩了,那么我每次少绘制几个不就行了。
试想一下,其实我每次只需要绘制三屏,然后让后面的复用前面的就好了啊,为自己的机智点赞。
这是鸿洋_2014年写的,哈哈哈哈
Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
大概原理:每次当第三屏显示的时候,把第一屏删掉,然后在第三屏后面添加一个,这样就实现的无限使用。
但是有个问题啊,人家抖音的IOS版本可以支持快速滑动啊,这个快速滑动到第三屏就不行了,以后也只能快速滑动一屏。
怎么办?
对于view的复用这个关键字,我们就会想到RecycleView和ListView,那么我把view当做item添加进去不就可以了,复用什么的控件本身就帮我做了啊,越想越开心,说干就干。
由于前面的经验,很快就差不多了,快速滑动没问题,就差显示时候的动画了,根据前面的经验复制粘贴把动画加上,我靠。。。 怎么回事,为什么后面的view刚滑到就有部分动画了? 哦,是因为复用啊。。。没关系,我加上判断。
holder.myView_last.setSrcW_NOInvalidate(0);//清空属性动画改变的显示
holder.myView_last.clearAnimation();//如果有动画就清空
//根据type控制显示,这里的逻辑是对于不显示的view:
//type=0,只绘制白色;type=1只绘制蓝色。对于显示中的部分手动控制
holder.myView_last.setType(mDatas.get(position));
为啥加上了还不行,打个LOG看看。。。哦,原来是缓存的问题,RecycleView和listview不一样的地方在于,它每次缓存的个数是两个,那么我当前显示的第一个view的前一个,和最后一个view的后一个view都不会改变。没关系,我手动修改:
//由于缓存,手动刷新前一个item
mAdapter.notifyItemChanged(firstVisibleItemPosition - 1, TAG);
到目前位置,不显示的区域的状态尽在掌握中了,但是显示的区域的动画还得改。因为之前只做了一个view从头到尾的动画,现在滑动的时候,view显示一般是一个vie显示一部分,那么就应该对屏幕显示的两个view设置动画,第一个执行完了,再执行第二个。
不对啊
怎么获取不到当前显示的view,getchildAt不好使啊,啥?不能根据position获取啊,因为只保存显示中的view,我们通过getChildCount获取一下,结果是2,那好吧,试试别的方法:
private MyView_last getView(int position) {
View child = getLayoutManager().findViewByPosition(position);
HomeAdapter.MyViewHolder viewHolder = (HomeAdapter.MyViewHolder) getChildViewHolder(child);
MyView_last myView_last = viewHolder.myView_last;
if (myView_last == null) {
return null;
} else {
return myView_last;
}
}
不错不错,直接在第一个动画结束后执行第二个动画就好了。
啥
还要动画重复执行,没问题,在第二个动画执行完执行第一个就好了
啥
要刚进去就对第一屏开始动画,但是我设置完adapter不能马上获取第一个view怎么办?
//在设置adapter的时候
if (position == 0 && isFirst) {
move = ObjectAnimator.ofInt(holder.myView_last, "srcW", 1, 1080);
move.setDuration(3000);
move.setRepeatCount(-1);
move.setInterpolator(new LinearInterpolator());
move.start();
}
那就等我获取到这个动画对象以后,立刻执行就好了。
基本上到此为止,该解决的问题都差不多解决了。
如果有遇到相同问题的,欢迎交流。QQ:279825695
源码git地址