mr-wangyong / imageframe Goto Github PK
View Code? Open in Web Editor NEW高效省内存播放序列帧动画控件
License: Apache License 2.0
高效省内存播放序列帧动画控件
License: Apache License 2.0
不知道具体是不是掉帧,图片会缺一块那种现象
用Kotlin实现的另外一种方式的思路。看一下是否有参考价值
用法:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anim);
View imageView = findViewById(R.id.imageView);
List<Integer> resources = new ArrayList<>(32);
Resources res = getResources();
final String packageName = getPackageName();
for (int i=1; i<=210; i++){
String resName = "gift_" + i;
int imageResId = res.getIdentifier(resName, "drawable", packageName);
resources.add(imageResId);
}
controller = FrameAnim.Companion.create()
.target(imageView) //target支持任意View
.resources(resources)
.duration(16)
.controller();
controller.start();
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (controller == null) {
return;
}
if (controller.isPaused()) {
controller.resume();
} else {
controller.pause();
}
}
});
}
主要功能类如下:
import android.graphics.drawable.BitmapDrawable
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.support.v4.view.ViewCompat
import android.util.Log
import android.view.View
import com.mrwang.imageframe.BitmapLoadUtils
import com.mrwang.imageframe.ImageCache
import java.lang.ref.SoftReference
/**
* Created by young on 2018/2/8.
*/
class FrameAnim {
companion object {
fun create(): FrameAnim {
return FrameAnim()
}
}
interface AnimEndCallBack{
fun onAnimEnd()
}
interface AnimLoopedCallBack{
fun onAnimLooped(loopedCount:Int)
}
/**
* 遥控器
*/
interface Controller {
fun start()
fun isPaused(): Boolean
fun pause()
fun resume()
fun destroy()
}
private inner class ControllerImpl(val ext: FrameAnim) : Controller {
override fun start() {
ext.start()
}
override fun destroy() {
ext.destroy()
}
override fun isPaused(): Boolean {
return ext.paused
}
override fun pause() {
ext.pause()
}
override fun resume() {
ext.resume()
}
}
private val TAG = javaClass.simpleName
private var target: View? = null
private var list: List<Int>? = null
private var duration: Long = 300
private var index = 0
private val decodeThread = DecodeThread()
private var decodeHandler: Handler? = null
private val uiHandler = Handler(Looper.getMainLooper())
private var bitmapDrawable: BitmapDrawable? = null
private var isInitLoad = true
private var loop = true
private var loopedCount = 0
private var paused = false
private var destroyed = false
private val controller: Controller = ControllerImpl(this)
private var animEndCallBack:AnimEndCallBack? = null
private var animLoopedCallBack:AnimLoopedCallBack? = null
private val autoDestroy:IAutoDestroy = object : IAutoDestroy {
override fun autoDestroy() {
destroy()
}
}
fun target(target: View): FrameAnim {
this.target = target
return this
}
fun resources(list: List<Int>): FrameAnim {
this.list = list
return this
}
fun duration(duration: Long): FrameAnim {
this.duration = duration
return this
}
fun loop(lp: Boolean): FrameAnim {
this.loop = lp
return this
}
fun callBackForAnimEnd(endCallBack: AnimEndCallBack): FrameAnim {
this.animEndCallBack = endCallBack
return this
}
fun callBackForAnimLooped(loopedCallBack: AnimLoopedCallBack): FrameAnim {
this.animLoopedCallBack = loopedCallBack
return this
}
fun controller():Controller{
return controller
}
fun start(): Controller {
if (target == null) {
Log.w(TAG, "The target is Null")
throw IllegalArgumentException("The target must not be Null")
}
if (list == null || list!!.isEmpty()) {
Log.w(TAG, "The resources is Empty")
throw IllegalArgumentException("The resources must not be Null or Empty")
}
val ctx = target!!.context
ctx.registerAutoDestroy(autoDestroy)
if (decodeHandler!=null){
Log.e(TAG, "Task already started")
return controller
}
if (paused) {
Log.e(TAG, "Task is paused, Please use resume() to continue")
return controller
}
decodeThread.start()
if (decodeHandler == null) {
decodeHandler = Handler(decodeThread.looper)
}
load()
return controller
}
private fun pause() {
if (decodeHandler == null) {
throw IllegalStateException("Have u forgot to invoke the ``fun start()``")
}
paused = true
decodeHandler!!.removeCallbacksAndMessages(null)
uiHandler.removeCallbacksAndMessages(null)
}
private fun resume() {
if (decodeHandler == null) {
throw IllegalStateException("Have u forgot to invoke the ``fun start()``")
}
paused = false
load()
}
fun destroy() {
if (destroyed){
return
}
destroyed = true
decodeThread.quit()
uiHandler.removeCallbacks(uiCallBack)
println("Component has destroyed")
}
private var uiCallBack: Runnable? = null
private val imageCache = ImageCache()
private fun load() {
val resources = list!!
if (index >= resources.size && loop) {
index = 0
loopedCount++
animLoopedCallBack?.onAnimLooped(loopedCount)
} else if (index >= resources.size && !loop) {
Log.d(TAG, "The End")
println("The End")
animEndCallBack?.onAnimEnd()
return
}
if (bitmapDrawable != null) {
imageCache.mReusableBitmaps.add(SoftReference(bitmapDrawable!!.bitmap))
}
val resId = resources[index]
decodeHandler!!.post {
val start = System.currentTimeMillis()
val resource = target!!.resources
bitmapDrawable = BitmapLoadUtils.decodeSampledBitmapFromRes(resource, resId, 0,
0,
imageCache, true)
val end = System.currentTimeMillis()
val decodeConsuming = end - start
val delay = duration - decodeConsuming
val actDelay = if (delay > 0) delay else 0
val finalDelay = if (isInitLoad) 0 else actDelay
isInitLoad = false
uiCallBack = Runnable {
ViewCompat.setBackground(target, bitmapDrawable)
index++
load()
}
uiHandler.postDelayed(uiCallBack, finalDelay)
}
}
inner class DecodeThread : HandlerThread("DecodeThread")
}
自动处理生命周期:
interface IAutoDestroy {
fun autoDestroy()
}
class LifeFragment : Fragment() {
private val set: MutableSet<IAutoDestroy> = mutableSetOf()
fun register(autoDestroy: IAutoDestroy) {
set.add(autoDestroy)
}
override fun onDestroy() {
super.onDestroy()
for (autoDestroy in set) {
autoDestroy.autoDestroy()
}
}
}
fun Context.registerAutoDestroy(autoDestroy: IAutoDestroy) {
if (this is FragmentActivity) {
var exist = supportFragmentManager.findFragmentByTag("Hello")
if (exist == null) {
exist = LifeFragment()
supportFragmentManager.beginTransaction().add(exist, "Hello").commit()
}
if (exist is LifeFragment) {
exist.register(autoDestroy)
}
}else {
Log.e("registerAutoDestroy", "The context type do not support fragment")
}
}
用Monkey测试出现
java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
at com.gubei.tool.imageframe.WorkHandler$1.handleMessage(WorkHandler.java:28)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:179)
at android.os.HandlerThread.run(HandlerThread.java:61)
public void removeMessageProxy(WorkMessageProxy proxy) {
initMessageProxyList();
messageProxyList.remove(proxy);
}好像是List移除时出错 不能确定
为什么越放越快?
只有暂停,没有继续呀~
#1944 java.util.ConcurrentModificationException
com.example.imageframelibs.ImageFrame.WorkHandler$1.void handleMessage(android.os.Message)(SourceFile:34)
解决方案
该异常表示迭代器迭代过程中,迭代的对象发生了改变,如数据项增加或删除。
[解决方案]:由于迭代对象不是线程安全,在迭代的过程中,会检查modCount是否和初始modCount即expectedModCount一致,如果不一致,则认为数据有变化,迭代终止并抛出异常。常出现的场景是,两个线程同时对集合进行操作,线程1对集合进行遍历,而线程2对集合进行增加、删除操作,此时将会发生ConcurrentModificationException异常。
具体方法:多线程访问时要增加同步锁,或者建议使用线程安全的集合:
错误信息如下:
E/AndroidRuntime: FATAL EXCEPTION: WorkHandler
Process: com.example.administrator.liveanimation, PID: 32047
java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:578)
at com.mrwang.imageframe.WorkHandler$1.handleMessage(WorkHandler.java:34)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:207)
at android.os.HandlerThread.run(HandlerThread.java:61)
java.lang.NullPointerException: Attempt to invoke virtual method 'java.io.InputStream android.content.res.Resources.openRawResource(int)' on a null object reference
at com.mrwang.imageframe.BitmapLoadUtils.decodeSampledBitmapFromRes(BitmapLoadUtils.java:69)
图片1为什么播放不出来呢?设置从第N帧开始播放为什么不起作用呢?是在初始化的时候,设置一次,还是每次播放都要设置呢?
rt
第105行到顶108行代码
} else { index++; loadInThread(files); }
是否应该改为这样:
} else if(file.isDirectory()){ index++; loadInThread(file.listFiles()); }
我是点击一个开始按钮,然后加载帧动画。加载完成后,再次点击就会加载不出来,会一直走onPlayFinish这个回调。能不能看下修复一下啊?
再下拉刷新中使用需要实现,每次下拉动画都从第一张图开始,现在使用pause() 动画会停住然后调用start()继续开始
使用stop()之后直接停住,能否实现stop()之后动画从第一张图重新开始
https://github.com/sahasbhop/apng-view
楼主如果能把U13替换掉,就牛逼了!
如题, 加载手机sdcard的图片文件,执行一次帧动画, 然后就不会再执行,loadInThreadFromFile(File[] files)方法是一直在反复执行的。。。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.