最近在项目中遇到一个问题,我有一个LinearLayout,里面装载了许多ImageView控件,ImageView控件显示着自己的图片,这个LinearLayout支持双指缩放,缩放采用ScaleAnimation来实现,但是但是在缩放过程中,屏幕十分卡顿,缩放效果根本没有跟上手指的缩放动作。后来在Google上查了一番,查到一个API,叫setAnimationDrawCacheEnabled(boolean enabled):

    /*** Enables or disables the children's drawing cache during a layout animation.* By default, the drawing cache is enabled but this will prevent nested* layout animations from working. To nest animations, you must disable the* cache.** @param enabled true to enable the animation cache, false otherwise** @see #isAnimationCacheEnabled()* @see View#setDrawingCacheEnabled(boolean)*/public void setAnimationCacheEnabled(boolean enabled) {setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);}

方法的注解我这里简单翻译一下:在执行一个Layout动画时开启或关闭子控件的绘制缓存。默认情况下,绘制缓存是开启的,但是这将阻止嵌套Layout动画的正常执行。对于嵌套动画,你必须禁用这个缓存。

先说drawing cache,绘制缓存的概念,Android为了提高View视图的绘制效率,提出了一个缓存的概念,其实就是一个Bitmap,用来存储View当前的绘制内容,在View的内容或者尺寸未发生改变时,这个缓存应该始终不被销毁,销毁了如果下次还用(开启了绘图缓存的前提下,API为setDrawingCacheEnabled(enabled),另外还可以设置绘图缓存Bitmap的质量,API为setDrawingCacheQuality(quality))就必须重建。

关于绘图缓存的相关介绍,可搜索这些相关API的介绍:

1)setDrawingCacheQuality(int quality)

2)setDrawingCacheEnabled(enabled)

3)setDrawingCacheBackgroundColor(color)

先看一段代码:

    /*** <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>** <p>If you call {@link #buildDrawingCache()} manually without calling* {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you* should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>** <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,* this method will create a bitmap of the same size as this view. Because this bitmap* will be drawn scaled by the parent ViewGroup, the result on screen might show* scaling artifacts. To avoid such artifacts, you should call this method by setting* the auto scaling to true. Doing so, however, will generate a bitmap of a different* size than the view. This implies that your application must be able to handle this* size.</p>** <p>You should avoid calling this method when hardware acceleration is enabled. If* you do not need the drawing cache bitmap, calling this method will increase memory* usage and cause the view to be rendered in software once, thus negatively impacting* performance.</p>** @see #getDrawingCache()* @see #destroyDrawingCache()*/public void buildDrawingCache(boolean autoScale) {if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?mDrawingCache == null : mUnscaledDrawingCache == null)) {mCachingFailed = false;int width = mRight - mLeft;int height = mBottom - mTop;final AttachInfo attachInfo = mAttachInfo;final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;if (autoScale && scalingRequired) {width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);}final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;       // 1.这里final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);final long drawingCacheSize =ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {if (width > 0 && height > 0) {Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs "+ projectedBitmapSize + " bytes, only "+ drawingCacheSize + " available");}destroyDrawingCache();mCachingFailed = true;return;}boolean clear = true;Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {Bitmap.Config quality;          // 2.这里if (!opaque) {// Never pick ARGB_4444 because it looks awful// Keep the DRAWING_CACHE_QUALITY_LOW flag just in caseswitch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {case DRAWING_CACHE_QUALITY_AUTO:case DRAWING_CACHE_QUALITY_LOW:case DRAWING_CACHE_QUALITY_HIGH:default:quality = Bitmap.Config.ARGB_8888;break;}} else {// Optimization for translucent windows// If the window is translucent, use a 32 bits bitmap to benefit from memcpy()quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;}// Try to cleanup memoryif (bitmap != null) bitmap.recycle();try {bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),width, height, quality);bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);if (autoScale) {mDrawingCache = bitmap;} else {mUnscaledDrawingCache = bitmap;}if (opaque && use32BitCache) bitmap.setHasAlpha(false);} catch (OutOfMemoryError e) {// If there is not enough memory to create the bitmap cache, just// ignore the issue as bitmap caches are not required to draw the// view hierarchyif (autoScale) {mDrawingCache = null;} else {mUnscaledDrawingCache = null;}mCachingFailed = true;return;}clear = drawingCacheBackgroundColor != 0;}Canvas canvas;if (attachInfo != null) {canvas = attachInfo.mCanvas;if (canvas == null) {canvas = new Canvas();}canvas.setBitmap(bitmap);// Temporarily clobber the cached Canvas in case one of our children// is also using a drawing cache. Without this, the children would// steal the canvas by attaching their own bitmap to it and bad, bad// thing would happen (invisible views, corrupted drawings, etc.)attachInfo.mCanvas = null;} else {// This case should hopefully never or seldom happencanvas = new Canvas(bitmap);}if (clear) {bitmap.eraseColor(drawingCacheBackgroundColor);}computeScroll();final int restoreCount = canvas.save();if (autoScale && scalingRequired) {final float scale = attachInfo.mApplicationScale;canvas.scale(scale, scale);}canvas.translate(-mScrollX, -mScrollY);mPrivateFlags |= PFLAG_DRAWN;if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;}// Fast path for layouts with no backgroundsif ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {mPrivateFlags &= ~PFLAG_DIRTY_MASK;dispatchDraw(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}} else {draw(canvas);}canvas.restoreToCount(restoreCount);canvas.setBitmap(null);if (attachInfo != null) {// Restore the cached Canvas for our siblingsattachInfo.mCanvas = canvas;}}}

两个标注了红色的地方,说明了这个mDrawingCacheBackgroundColor变量的作用,因此如果使用默认值,那么缓存Bitmap使用的是Bitmap.Config.ARGB_8888,比Bitmap.Config.RGB_565多占用了一半的内存,因此如果不想使用太大的内存,担心内存泄露,可以设置给mDrawingCacheBackgroundColor一个值,例如:

setDrawingCacheBackgroundColor(0xFF0C0C0C);

那么,那么,这个绘图缓存如何优化绘图速率,又怎么阻碍了Animation的执行?

先看两个方法:

1)ViewGroup -> dispatchDraw(Canvas canvas) 方法:

    /*** {@inheritDoc}*/@Overrideprotected void dispatchDraw(Canvas canvas) {final int count = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;// 关键字:FLAG_RUN_ANIMATION,FLAG_ANIMATION_CACHE,cache,buildCacheif ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;final boolean buildCache = !isHardwareAccelerated();for (int i = 0; i < count; i++) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {final LayoutParams params = child.getLayoutParams();attachLayoutAnimationParameters(child, params, i, count);bindLayoutAnimation(child);if (cache) {child.setDrawingCacheEnabled(true);if (buildCache) {                        child.buildDrawingCache(true);}}}}final LayoutAnimationController controller = mLayoutAnimationController;if (controller.willOverlap()) {mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;}controller.start();mGroupFlags &= ~FLAG_RUN_ANIMATION;mGroupFlags &= ~FLAG_ANIMATION_DONE;if (cache) {mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;}if (mAnimationListener != null) {mAnimationListener.onAnimationStart(controller.getAnimation());}}int saveCount = 0;final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;if (clipToPadding) {saveCount = canvas.save();canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom);}// We will draw our child's animation, let's reset the flagmPrivateFlags &= ~PFLAG_DRAW_ANIMATION;mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;boolean more = false;final long drawingTime = getDrawingTime();if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {for (int i = 0; i < count; i++) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |= drawChild(canvas, child, drawingTime);}}} else {for (int i = 0; i < count; i++) {final View child = children[getChildDrawingOrder(count, i)];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |= drawChild(canvas, child, drawingTime);}}}// Draw any disappearing views that have animationsif (mDisappearingChildren != null) {final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finishfor (int i = disappearingCount; i >= 0; i--) {final View child = disappearingChildren.get(i);more |= drawChild(canvas, child, drawingTime);}}if (debugDraw()) {onDebugDraw(canvas);}if (clipToPadding) {canvas.restoreToCount(saveCount);}// mGroupFlags might have been updated by drawChild()flags = mGroupFlags;if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {invalidate(true);}if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&mLayoutAnimationController.isDone() && !more) {// We want to erase the drawing cache and notify the listener after the// next frame is drawn because one extra invalidate() is caused by// drawChild() after the animation is overmGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() {public void run() {notifyAnimationListener();}};post(end);}}

从中可以看出这个FLAG_ANIMATION_CACHE的作用了,当然还和硬件加速扯上了关系,这里先不补充相关知识,想了解的可以度娘or谷歌。

附:对硬件加速带源码分析的比较好的一篇文章:

Android硬件加速绘制过程源码分析(一)Android硬件加速绘制过程源码分析(二)——DisplayList录制绘制操作Android硬件加速绘制过程源码分析(三)——DisplayList的绘制过程Android硬件加速绘制过程源码分析(四)——离屏硬件缓存HardwareLayer

2)View -> draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法;

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {boolean caching;……………final int flags = parent.mGroupFlags;…………..if ((flags & ViewGroup.FLAG_CHILDREN_DRAWN_WITH_CACHE) != 0 ||(flags & ViewGroup.FLAG_ALWAYS_DRAWN_WITH_CACHE) != 0) {caching = true;// Auto-scaled apps are not hw-accelerated, no need to set scaling flag on DisplayListif (mAttachInfo != null) scalingRequired =         mAttachInfo.mScalingRequired;} else {caching = (layerType != LAYER_TYPE_NONE) || hardwareAccelerated;}…………..if (caching) {if (!hardwareAccelerated) {if (layerType != LAYER_TYPE_NONE) {layerType = LAYER_TYPE_SOFTWARE;buildDrawingCache(true);}cache = getDrawingCache(true);} else {switch (layerType) {case LAYER_TYPE_SOFTWARE:if (useDisplayListProperties) {hasDisplayList = canHaveDisplayList();} else {buildDrawingCache(true);cache = getDrawingCache(true);}break;case LAYER_TYPE_HARDWARE:if (useDisplayListProperties) {hasDisplayList = canHaveDisplayList();}break;case LAYER_TYPE_NONE:// Delay getting the display list until animation-driven alpha values are// set up and possibly passed on to the viewhasDisplayList = canHaveDisplayList();break;}}}…………………..}    

从上面的代码可以分析出来,如果不禁止绘图缓存,那么每次绘制子View时都要更新缓存并且将缓存画到画布中。这无疑是多了一步,画一个bitmap,animation需要不停的画所以也就多了很多操作,但是这个缓存不是说是对绘制视图的优化嘛,这个秘密就在View的invalidate中,当子View需要 invalidate时,事实上也是交给父布局去分发的。

    /*** This is where the invalidate() work actually happens. A full invalidate()* causes the drawing cache to be invalidated, but this function can be called with* invalidateCache set to false to skip that invalidation step for cases that do not* need it (for example, a component that remains at the same dimensions with the same* content).** @param invalidateCache Whether the drawing cache for this view should be invalidated as* well. This is usually true for a full invalidate, but may be set to false if the* View's contents or dimensions have not changed.   * 指示在视图刷新时,是否也要刷新绘图缓存,对于一个完全的刷新操作,比如视图内容发生了变化,   * 或者控件尺寸发生变化了,那么应该设置true,但是如果不是二者任何一个,则应该设置为false。*/void invalidate(boolean invalidateCache) {if (skipInvalidate()) {return;}if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||(invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||(mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {mLastIsOpaque = isOpaque();mPrivateFlags &= ~PFLAG_DRAWN;mPrivateFlags |= PFLAG_DIRTY;if (invalidateCache) {mPrivateFlags |= PFLAG_INVALIDATED;mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}final AttachInfo ai = mAttachInfo;final ViewParent p = mParent;//noinspection PointlessBooleanExpression,ConstantConditionsif (!HardwareRenderer.RENDER_DIRTY_REGIONS) {if (p != null && ai != null && ai.mHardwareAccelerated) {// fast-track for GL-enabled applications; just invalidate the whole hierarchy// with a null dirty rect, which tells the ViewAncestor to redraw everythingp.invalidateChild(this, null);return;}}if (p != null && ai != null) {final Rect r = ai.mTmpInvalRect;r.set(0, 0, mRight - mLeft, mBottom - mTop);// Don't call invalidate -- we don't want to internally scroll// our own boundsp.invalidateChild(this, r);}}}

接着,咱们再看一个ViewGroup的方法,setPersistentDrawingCache(int drawingCacheToKeep):

    /*** Indicates what types of drawing caches should be kept in memory after* they have been created.** @see #getPersistentDrawingCache()* @see #setAnimationCacheEnabled(boolean)** @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},*        {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}*        and {@link #PERSISTENT_ALL_CACHES}*/public void setPersistentDrawingCache(int drawingCacheToKeep) {mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;}

这个方法的作用,便是控制绘图缓存在被创建之后,什么时候使用。

方法的可用参数,系统提供了四个值:

1)PERSISTENT_ANIMATION_CACHE:动画前不可用,动画结束时可用,并保存此时的Cache。

2)PERSISTENT_SCROLLING_CACHE:滚动式不可用,滚动结束时可用,并保存此时的Cache。

3)PERSISTENT_ALL_CACHES:不管在什么时候,都是用缓存。

4)PERSISTENT_NO_CACHE:不适用缓存。

因此,你可以手动的控制AnimationDrawCache(在执行动画前禁用,执行完毕后启用)或者调用这个方法传入适用于相应场景的参数值就可以自动实现控制了。

大概的明白了,有木有!其实我理解的也不深入,都是在别人分析的基础上总结出来,希望对大家有用,也对自己有用。

Over!

参考:

转载于:https://www.cnblogs.com/emmet7life/p/4185182.html

Android学习分享:执行某ViewGroup的动画时,子控件太多导致动画执行卡顿的问题...相关推荐

  1. android学习总结(16.08.29)进度条控件ProgressBar和ProgressDialog

    ProgressBar progress属性:进度条的进度,默认上限值为100当然也可以用setMax方法修改 setProgress   getProgress  设置和获取progress的值 s ...

  2. Android自定义ViewGroup实现朋友圈九宫格控件

    在我们的实际应用中,经常需要用到自定义控件,比如自定义圆形头像,自定义计步器等等,这篇文章主要给大家介绍了关于Android自定义ViewGroup实现朋友圈九宫格控件的相关资料,需要的朋友可以参考下 ...

  3. 在layoutsubviews中设置子控件的frame,保证执行alpha和frame动画流畅度

    在viewDidLoad中初始化需要的子控件,然后提供改变这些子控件的开放接口,然后使用一个bool变量来保存是否已经设置了子类控件的frame类似 -(void)layoutSubviews {[s ...

  4. Android开发详解:第4章《UI 控件》

    Android开发详解:第4章<UI 控件> 控件是Android程序设计的基本组成单位,通过使用控件可以高效地开发Android应用程序.所以熟练掌握控件的使用是合理.有效地进行Andr ...

  5. 《撸代码 学习 IOC注入技术1 》—— 布局注入 与 控件注入

    不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨-https://blog.csdn.net/cjm2484836553/article/details/104539874 [源代码下载地 ...

  6. android item 点击 获取position,Android ListView 子控件onClick正确获取position的方法

    在实际开发中,我们有时候不仅需要响应ListView的onItemClick,还需要响应其子控件的点击事件,这个时候我们就会 发现,由于复用等原因,如果直接在子控件的onClick事件中调用getVi ...

  7. auto.js B0013 查找父控件子控件进入阅读文章视频学习每日答题2021-10-03

    log("$$$$$$$$$$$$$$$$$$$$$$$$$$$") var list=className("android.widget.ListView") ...

  8. Kotlin 第一弹:自定义 ViewGroup 实现流式标签控件

    古人学问无遗力, 少壮工夫老始成.纸上得来终觉浅, 绝知此事要躬行. – 陆游 <冬夜读书示子聿> 上周 Google I/O 大会的召开,宣布了 Kotlin 语言正式成为了官方开发语言 ...

  9. android button imagebutton 区别,ImageView子控件,ImageButton和ZoomButton使用

    原标题:ImageView子控件,ImageButton和ZoomButton使用 上一期我们学习了ImageView的使用,那么本期来学习ImageView的两个子控件ImageButton和Zoo ...

  10. android 控件覆盖关系,安卓子控件抢占父控件点击事件或者焦点问题

    开发中很常见的一个问题,项目中的lListview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,在adapter中按照需求进行编写,问题就出现 ...

最新文章

  1. *** WARNING L1: UNRESOLVED EXTERNAL SYMBOL
  2. UI5 control inheritance implementation question
  3. aelf帮助C#工程师10分钟零门槛搭建DAPP私有链开发环境
  4. 【快速幂】小明解密码 (jzoj 2146)
  5. 奥鹏20年12月作业考核(C语言专科),《C语言(专科)》20年12月作业考核【答案100分】...
  6. 如何评估深度学习模型效果?阿里工程师这么做 1
  7. Python基础学习总结、学习展望
  8. Linux系统编程(18)——正则表达式实用举例
  9. Java学习之——泛型
  10. exchange功能在线测试
  11. Mysql 用户管理
  12. webdriver中PDF控件无法显示的问题(IE兼容性)
  13. Java扫码点餐小程序源码 智慧点餐系统源码 点餐APP SaaS模式
  14. tv盒子管理助手android版本,TV盒子工具 管理电视盒子的好助手
  15. Ubuntu类似与xshell 和crt的软件 pac- Ásbrú Connection Manager
  16. 【每日早报】2019/08/08
  17. 5G技术全面融入ROS2新一代机器人操作系统大量成果推出(2020整理翻译版)
  18. tensorflow RNN实例
  19. ActiveSync同步Emulator
  20. Unity LWRP修改Blit Shader来使Camera的Depth Only生效

热门文章

  1. 高可用分布式非关系型数据库-Cassandra
  2. 打造线上的大数据风控,我们发现了这三个坑
  3. 13家公司半年报业绩预喜 分布式将成光伏产业发展方向
  4. discuz清空session,导致session保存机制失败,session无法更新与解决
  5. Linux core文件生成及设置 查看core文件由哪个程序生成的
  6. HDU5828 Rikka with Sequence
  7. 黑马vue实战项目-(七)订单管理页面的开发
  8. Pod--初始化容器
  9. Linux下安装mysql(yum、二进制包、源码包)
  10. mysql事务的两点特性_MySQL基础篇(06):事务管理,锁机制案例详解