2016-08-19 16:03:30 rentee 阅读数 15903

属性动画的基础知识可以上郭霖的blog补下。地址:http://blog.csdn.net/guolin_blog/article/details/43536355


以下是自己另附的一些优化策略。
1.使用PropertyValuesHolder
想必大家都这样写过:

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

但是这样写会产生两个ObjectAnimator对象,效率较低,官方建立这样写:

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);

ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

这里使用了PropertyValuesHolder,只产生一个ObjectAnimator对象,更加高效。

一个view同时发生多种效果时,建议这种写法。


2.使用Keyframe
同样,我们肯定也这样写过:

AnimatorSet animSet = new AnimatorSet();

ObjectAnimator transYFirstAnim = ObjectAnimator.ofFloat(mView, "translationY", 0, 100);
ObjectAnimator transYSecondAnim = ObjectAnimator.ofFloat(mView, "translationY", 100, 0);

animSet.playSequentially(transYFirstAnim, transYSecondAnim);

产生两个ObjectAnimator对象,一个AnimatorSet,代码繁琐,对象冗杂。
这种情况建议使用Keyframe编写其行为。从词义上来理解Keyframe是关键帧,下面是使用关键帧的例子:

Keyframe k0 = Keyframe.ofFloat(0f, 0); //第一个参数为“何时”,第二个参数为“何地”
Keyframe k1 = Keyframe.ofFloat(0.5f, 100);
Keyframe k2 = Keyframe.ofFloat(1f, 0);

PropertyValuesHolder p = PropertyValuesHolder.ofKeyframe("translationY", k0, k1, k2);

ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mView, p);
objectAnimator.start();

所以效果就是:
开始时 位置为0;
动画开始1/2时 位置为100;
动画结束时 位置为0。

一个view的单个属性先后发生一系列变化时,建议使用Keyframe达到效果。

总结就是,如果是同一个view的一系列动画,均可使用以上组合方式达到只使用一个ObjectAnimator的效果 。多个view的动画用AnimatorSet进行动画组合和排序,代码架构和执行效率基本能达到最优化。


3.使用AnimatorListenerAdapter代替AnimatorListener
由于AnimatorListener是接口,所以实现它得实现它所有的方法,而我们有时只用到它的个别回调(如:onAnimationStart),使用它会导致代码看起来非常冗杂。而AnimatorListenerAdapter是默认实现了AnimatorListener的一个抽象类,你可以按需要重写其中的方法,代码会优雅一点。


4.使用ViewPropertyAnimator
属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。
但是,在绝大多数情况下,还是对View进行动画操作的。Android开发团队也是意识到了这一点,于是在Android 3.1系统当中补充了ViewPropertyAnimator这个机制。

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, “alpha”, 0f); animator.start();
等同
textview.animate().alpha(0f);

animate()方法就是在Android 3.1系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator对象。(简明方便)
textview.animate().x(500).y(500).setDuration(5000).setInterpolator(new BounceInterpolator());


5.属性动画其他一些实用方法:(暂时只讲了API 14—Android 4.0及以下的部分)

  1. setStartDelay():可以设置动画开始前的延迟时间。注意:此方法API 14加入;如果给动画添加了AnimatorListener,Listener的onAnimationStart方法会在动画最开始的时候回调,而不是delay一段时间后回调。

  2. isStarted()(API 14)与isRunning():这个得与setStartDelay()放在一起讲,也就是说,当动画在delay中并没有真正开始时,isStarted返回false,而isRunning返回true。也就是,isRunning的周期是大于isStarted的。

  3. cancel()与end():cancel方法会立即停止动画,并且停留在当前帧。end方法会立即停止动画,并且将动画迅速置到最后一帧的状态。

  4. setupStartValues()与setupEndValues():设置动画进行到的当前值为开始值(结束值)。示例如下:

以下这个示例中,当view移动了2.5s的时候,cancel当前动画,并重新设定起始位置为当前位置,结束位置为400,再开始此动画。

final ObjectAnimator o = ObjectAnimator.ofFloat(mTextView, "x", 0, 500);
o.setDuration(5000);
o.start();

mTextView.postDelayed(new Runnable() {
      @Override
      public void run() {
           if (o.isRunning()) {
                o.cancel();
            }
            o.setupStartValues();
            o.setFloatValues(400);
            o.start();
      }
},2500);

6.做一个属性动画的方式:(摘自Android开发艺术探索)
1.给你的对象加上get和set方法,前提是你有权限。
2.用一个类来包装原始对象,间接为其提供get和set方法。

    /**
     * 用一个类来包装原始对象,间接为其提供get和set方法。
     */
    private static class ViewWrapper {
        private View mTarget;

        public ViewWrapper(View target) {
            mTarget = target;
        }

        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }

    /**
     * TextView不能直接设置width,这里即采用了包装的方式,对textview的width进行属性动画
     */
    private void animViewWidthByWidth() {
        ViewWrapper wrapper = new ViewWrapper(mTextView);
        ObjectAnimator widthAnim = ObjectAnimator.ofObject(wrapper, "width", new IntEvaluator(), mTextView2.getWidth(), mTextView2.getWidth() * 2);
        widthAnim.setDuration(1000);
        widthAnim.setRepeatCount(100);
        widthAnim.start();
    }

3.采用ValueAnimator,监听动画过程,自己实现属性的改变。

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float currentValue = (float) animation.getAnimatedValue();
        //此处即可根据currentValue的值自己监听过程,实现计算。
        Log.d("TAG", "cuurent value is " + currentValue);
    }
});
anim.start();

7.哪些线程能做属性动画
属性动画的start()方法,首先就有判定:

private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
    ......
}

说明,需要有Looper的线程才可以做属性动画。很明显,Android原生环境中,主线程和HandlerThread是可以做属性动画的,当然,也可以自己实现一个有Looper的Thread。


8.属性动画的内存泄露问题
许多时候用到了无限循环的动画,我们会这样写:animator.setRepeatCount(ValueAnimator.INFINITE);
这样写如果没有及时取消,会导致此属性动画持有被动画对象的引用而导致内存泄露,故在activity生命周期结束时,如onDestroy方法中,及时的cancel掉这种动画。
补间动画无此问题,它是给view做动画,在view的onDetachedFromWindow方法会取消掉动画,所以不会导致内存泄露。

2016-08-12 16:47:42 u014267222 阅读数 1786
   开发过程中做了一个动画,一个位移与旋转相结合的组合动画AnimationSet,然而整个页面有四个布局一开重叠在一起然后四散而开,由于布局的复杂和动画重叠导致整个动画不是很流畅,然后用FPS Meter(需要rote)测试了一下帧频,结果动化只有15左右(android最高帧频现在为60)。
   查了半天,网上说的大多就是简易下布局什么,最后找到View的一个方法:
    view.setLayerType(int layerType,null);
   上面这个方法是为View设置硬件加速功能,而layerType 有三个值:分别是 
   LAYER_TYPE_NONE  
     不为这个View树建立单独的layer 。
   LAYER_TYPE_SOFTWARE 
     无论硬件加速是否打开,都会有一张Bitmap(software layer),并在上面对WebView进行软渲染。
   LAYER_TYPE_HARDWARE
     硬件加速打开时会在FBO(Framebuffer Object)上面做渲染。

硬件加速级别:

    Application级别
    往您的应用程序AndroidManifest.xml文件为application标签添加如下的属性即可为整个应用程序开启硬件加速:
    <application android:hardwareAccelerated="true" ...>

    Activity级别
    您还可以控制每个activity是否开启硬件加速,只需在activity元素中添加android:hardwareAccelerated属性即可办到。比如下面的例子,在application级别开启硬件加速,但在某个activity上关闭硬件加速。

    <application android:hardwareAccelerated="true">    <activity ... />    <activity android:hardwareAccelerated="false" /></application>

Window级别

    如果您需要更小粒度的控制,可以使用如下代码开启某个window的硬件加速:

    getWindow().setFlags(    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
    注:目前还不能在window级别关闭硬件加速。

View级别

    您可以在运行时用以下的代码关闭单个view的硬件加速:

    myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
2017-04-04 19:46:57 xanxus46 阅读数 2040

Android上如果使用逐帧动画的话,可以很方便地使用AnimationDrawable,无论是先声明xml还是直接代码里设置,都是几分钟的事,但使用AnimationDrawable有一个致命的弱点,那就是需要一次性加载所有图片到内存,万一帧数多了或者每张图片都比较大,很容易就报out of memory的异常了,所以有必要进行优化。

这里我们利用View.postDelayed方法延时替换图片,这样就能做到逐帧动画的效果了,然后在替换图片之前,强制回收ImageView当前bitmap就可以减少内存消耗了,废话少说,上代码。

public class SceneAnimation {
    private ImageView mImageView;
    private int[] mFrameRess;
    private int[] mDurations;
    private int mDuration;
    private int mLastFrameNo;
    private long mBreakDelay = 0L;
    private int mLastPlayFrameNo = 0;
    private boolean isStop = true;

    public SceneAnimation(ImageView pImageView, int[] pFrameRess,
                          int[] pDurations) {
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDurations = pDurations;
        mLastFrameNo = pFrameRess.length - 1;
        // mImageView.setBackgroundResource(mFrameRess[0]);
    }

    public SceneAnimation(ImageView pImageView, int[] pFrameRess, int pDuration) {
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDuration = pDuration;
        mLastFrameNo = pFrameRess.length - 1;
        //   mImageView.setBackgroundResource(mFrameRess[0]);
    }

    public SceneAnimation(ImageView pImageView, int[] pFrameRess,
                          int pDuration, long pBreakDelay) {
        mImageView = pImageView;
        mFrameRess = pFrameRess;
        mDuration = pDuration;
        mLastFrameNo = pFrameRess.length - 1;
        mBreakDelay = pBreakDelay;
        //   mImageView.setBackgroundResource(mFrameRess[0]);
    }

    @SuppressWarnings("unused")
    private void play(final int pFrameNo) {
        mImageView.postDelayed(new Runnable() {
            public void run() {
                if (pFrameNo != mLastPlayFrameNo) {
                    recycleImage();
                    mLastPlayFrameNo = pFrameNo;
                }
                mImageView.setBackgroundResource(mFrameRess[pFrameNo]);
                if (!isStop) {
                    if (pFrameNo == mLastFrameNo)
                        play(0);
                    else
                        play(pFrameNo + 1);
                }
            }
        }, mDurations[pFrameNo]);
    }

    private void playConstant(final int pFrameNo) {
        mImageView.postDelayed(new Runnable() {
            public void run() {
                if (pFrameNo != mLastPlayFrameNo) {
                    recycleImage();
                    mLastPlayFrameNo = pFrameNo;
                }
                mImageView.setBackgroundResource(mFrameRess[pFrameNo]);
                if (!isStop) {
                    if (pFrameNo == mLastFrameNo)
                        playConstant(0);
                    else
                        playConstant(pFrameNo + 1);
                }
            }
        }, pFrameNo == mLastFrameNo && mBreakDelay > 0 ? mBreakDelay
                : mDuration);
    }

    public void stopPlay() {
        isStop = true;
//        recycleImage();
    }

    public void playConstant() {
        isStop = false;
        playConstant(mLastPlayFrameNo);
    }

    private void recycleImage() {
        BitmapUtil.recycleImageView(mImageView);
    }

    public void playOnce(FinishCallback callback) {
        isStop = false;
        playOnce(callback, 0);
    }

    private void playOnce(FinishCallback callback, int frameNo) {
        mImageView.postDelayed(new Runnable() {
            public void run() {
                if (frameNo != 0)
                    recycleImage();
                mImageView.setBackgroundResource(mFrameRess[frameNo]);
                if (!isStop) {
                    if (frameNo == mLastFrameNo) {
                        isStop = true;
                        if (callback != null)
                            callback.onFinish(SceneAnimation.this);
                    } else
                        playOnce(callback, frameNo + 1);
                }
            }
        }, frameNo == mLastFrameNo && mBreakDelay > 0 ? mBreakDelay
                : mDuration);
    }

    public interface FinishCallback {
        public void onFinish(SceneAnimation sceneAnimation);
    }

    public boolean isRunning() {
        return !isStop;
    }
}
上面的类提供了两种方法,循环播放和只播放一次,stopPlay是停止当前动画,而mLastPlayFrameNo是当前图片是所有图片中的第几张,循环中当当前的frameNo不等于mLastPlayFrameNo时回收图片,这个相当重要,处理不当可能会报出使用回收后的bitmap的异常,因为有可能用户一开始ImageView设置的src就是第0张,又或者用户停止动画后又想重新播放,那么就会发生上面的情况。

好了,讲述完这个类,看一下如何使用吧,很简单。

SceneAnimation waitAnim = new SceneAnimation(waitImageView, waitResIds, 100); // 指定绑定的ImageView和图片资源数组以及每张图片的延时
waitAnim.playConstant(); // 循环播放
waitAnim.stopPlay(); // 停止播放

逐帧动画优化到这里结束了,后期我们或许可以继续优化,就是防止一个图片帧太大,加载时间过长,我们可以缓存多张,而不是现在的只缓存一张。

2018-08-31 18:29:35 iblade 阅读数 876

属性动画优化

思路:

①使用PropertyValuesHolder 

②使用Keyframe 

③animator.setRepeatCount(ValueAnimator.INFINITE)及时cancel()

④动画卡顿,可以考虑使用自定义控件实现,如果一个自定义不行,那就是两个。。。(待续)

一,PropertyValuesHolder 

        
        float currentX = textView.getTranslationX();
        float currentY = textView.getTranslationY();
        //传统写法
        ObjectAnimator tX = ObjectAnimator.ofFloat(textView, "translationX", currentX, -300, currentX);
        ObjectAnimator tY = ObjectAnimator.ofFloat(textView, "translationY", currentY, 200, currentY);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(1000);
        set.playTogether(tX, tY);//或者  set.play(tX).with(tY);
        set.start();

        //一个view同时发生多种效果时,建议使用PropertyValuesHolder,这样ObjectAnimator只有一个对象
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", currentX, 500, currentX);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", currentY, 400, currentY);
        ObjectAnimator.ofPropertyValuesHolder(textView, pvhX, pvhY).setDuration(1000).start();

二,Keyframe 

//传统写法
        ObjectAnimator transYFirstAnim = ObjectAnimator.ofFloat(textView, "translationY", 0, 100);
        ObjectAnimator transYSecondAnim = ObjectAnimator.ofFloat(textView, "translationY", 100, 0);
        AnimatorSet animSet = new AnimatorSet();
        animSet.playSequentially(transYFirstAnim, transYSecondAnim);


        //一个view的单个属性先后发生一系列变化时,建议使用Keyframe达到效果。从词义上来理解Keyframe是关键帧
        Keyframe k0 = Keyframe.ofFloat(0f, 0); //第一个参数为“何时”,第二个参数为“何地”
        Keyframe k1 = Keyframe.ofFloat(0.5f, 100);
        Keyframe k2 = Keyframe.ofFloat(1f, 0);
        PropertyValuesHolder p = PropertyValuesHolder.ofKeyframe("translationY", k0, k1, k2);
        ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(textView, p);
        objectAnimator.start();
        /*
            效果就是:
         开始时 位置为0;
         动画开始1/2时 位置为100;
         动画结束时 位置为0。
         */

总结就是,如果是同一个view的一系列动画,均可使用以上组合方式达到只使用一个ObjectAnimator的效果 。多个view的动画用AnimatorSet进行动画组合和排序,代码架构和执行效率基本能达到最优化。

三,及时取消无限循环动画

许多时候用到了无限循环的动画,我们会这样写:animator.setRepeatCount(ValueAnimator.INFINITE); 
这样写如果没有及时取消,会导致此属性动画持有被动画对象的引用而导致内存泄露,故在activity生命周期结束时,如onDestroy方法中,及时的cancel掉这种动画。 
补间动画无此问题,它是给view做动画,在view的onDetachedFromWindow方法会取消掉动画,所以不会导致内存泄露。

 

补充建议:

  1. 尽量不要在刷新时做耗时操作,必须准备数据,创建图片,图片变换等,数据和图片都应该在之前就加载到内存中,图片变换用canvas的变换来实现。
  2. 同一个界面中多个动画重叠出现时,尽量将动画的刷新过程统一进行刷新,避免频繁的invalidate,尤其是多个动画有时序上的关系时更应该统一。
  3. 尽量使用带有参数的invalidate来刷新,这样可以减少很多运算量。
  4. 合理的环境下使用surfaceview来操作,比如播放视频等,这种刷新耗时比较大的情况。
  5. 开启硬件加速,硬件加速由于采用了显示列表的概念,所以刷新过程也有很大的优化,但是会增加额外的8M内存占用。

属性动画使用方法详解 可以参考郭霖大神的博客:https://blog.csdn.net/guolin_blog/article/details/43536355

2019-08-23 16:39:13 qq_23081779 阅读数 48

android绘制优化

android绘制优化,主要包括以下几块:布局优化、刷新优化和动画优化

布局优化

布局优化主要就是避免过度绘制

过度绘制

  • 布局优化主要就是避免过度绘制
  • 什么是过度绘制
    • 过度绘制,是指在一帧的时间内(16.67ms)某个像素点被绘制了多次,理论上一个像素点每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这个操作耗时超过16.67毫秒时,就会出现掉帧现象,也就是我们说的卡顿,所以对重叠不可见元素的重复绘制会产生额外的开销,需要尽量减少Overdraw的发生
  • 查看过度绘制
    • 开发者选项中,选择“Debug GPU Overdraw”,选择“Show overdraw areas”
    • 没有颜色:表示没有过度绘制,像素只画了一次
    • 蓝色:表示overdraw 1倍,像素绘制了2此。大片的蓝色还是可以接收的
    • 绿色:表示过度绘制2倍,像素绘制了3次。中等大小的绿色区域是可以接收的但应该尝试优化,减少区域
    • 浅红:表示过度绘制3倍,像素绘制了4次,小范围可以接收
    • 暗红:表示过度绘制4倍,像素绘制了5次或者更多,这是错误的,要修复

布局优化五大工具

布局优化常常会借助于五大工具来完成,Lint+systrace+TraceView+Hierarchy View+LayoutInspector,Lint可以帮助检查一些明显的问题,systrace可以大致判断哪一块有问题,TraceView可以定位到某个具体方法,Hierarchy View可以查看布局嵌套问题,LayoutInspector为android studio自带的可以查看布局嵌套的工具

  1. Lint
  • lint是android studio自带的工具,使用比较简单
  • 打开方式:
    选择 Analyse -> Inspect Code, as会自动检查问题,提示错误的一定要去解决,警告可以酌情处理
  1. systrace
  2. TraceView
    可以看到某个具体方法的耗时和调用次数
  • 方法分析,代码中添加如下代码
    • android.os.Debug.startMethodTracing(“trace”)
    • android.os.Debug.stopMethodTracing()
  • 看图
    • 同一个色块是同一个方法
  1. Hierarchy View
  • 可以查看布局的嵌套树
  • 打开方式:打开DDMS,选择右上角的+号,选择“Hierarchy View”
  1. LayoutInspector
  • 可以查看布局的嵌套树
  • 打开方式:打开android studio,选择tools,再选择LayoutInspector,即可打开

刷新机制

  • 尽量减少刷新次数
    • 控制刷新频率:进度条,变化1%,完全没必要刷新
    • 避免没有必要的刷新:数据没有变化,需要在刷新的控件在不同区域;一个View不可见到可见,一定要刷新一次
  • 尽量避免后台有高CPU的线程运行
  • 缩小刷新区域
    • invalidate(Rect dirty)
    • intvalidate(int left,int top,int right,int bottom)
    • RecyclerView

动画

  • 帧动画:消耗资源最多
  • 补间动画:平移、缩放,一般没有问题
  • 属性动画
  • 硬件加速:尽可能小范围的硬件加速,尽可能避免大范围的硬件加速
    • Application级别:
    • Activity级别:
    • Window级别:getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
    • View级别:View.setLayerType(View.LAYER_TYPE_SOFTWARE,null)
      • LAYER_TYPE_NONE
      • LAYER_TYPE_HARDWARE: 绘制为硬件纹理
      • LAYER_TYPE_SOFTWARE: 此view通过软件渲染为一个Bitmap

如何优化

  • 合理使用五大布局,尽量用约束布局去减少层级
  • 去掉Window的默认背景
  • 去掉不必要的背景
  • ViewStub
  • Merge
  • 善用draw9Patch
  • 慎用Alpha
  • 避免过度设计/OverDesign
  • ClipRect & QuickReject
    • 为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗,但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体执行什么操作,系统无法监控并自动优化,也就无法避免过度绘制了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect还可以帮助节约CPU和GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制,除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形香蕉,从而跳过那些非矩形区域内的绘制操作
  • 如果UI复杂,使用系统控件,不得不过度绘制时,可考虑自定义View来实现

导航

Android 优化篇

阅读数 142

没有更多推荐了,返回首页