当我反向运行动画时,Android会跳过onDraw()

我有一个Sliding Fragments DevByte的实现.除了将片段滑入视图,我想在它遮挡的内容上画一个阴影.我已经从视频中修改了FractionalLinearLayout,以屏幕宽度的两倍来测量自身,并将其子项放在右半部分.在动画循环期间,我在左半部分绘制一个增加alpha的黑色矩形,并将我的X坐标设置为负值,使右半部分进入视图.

我的问题是,当我将内容滑入视图时,这可以正常工作,但是当我将内容滑回视图时失败.在路上,我得到了我想要的行为:翻译和变暗的阴影.在出路的时候,我只得到了翻译,阴影仍然只是动画中的第一帧颜色.

我在日志记录中看到的是,在进入的过程中,setPercentOnScreen()和onDraw()方法被交替调用,正如预期的那样.然而,在出路的时候,我接到一次调用setPercentageOnScreen(),然后调用onDraw(),然后只调用setPercentOnScreen(). Android正在优化绘图,但我无法弄清楚原因.

更新:有趣的是,我只在运行Android 4.4的模拟器上看到此行为.运行4.0.3和4.3的仿真器在两个方向上按预期运行动画.一个旧的Nexus 7出现了问题,另一个不同的仿真器4.4.它似乎在设备上是一致的,但在设备之间有所不同.

再次更新:我已经提取了一个示例项目并将其放在GitHub上:barend/android-slidingfragment.GitHub上的自述文件包含十几个设备上的测试结果.对于模拟器,问题与“启用主机GPU”功能相关,但仅限于Jelly Bean和KitKat;不在ICS上.

再次更新:进一步测试显示问题发生在运行Jelly Bean及更高版本的物理设备上,以及启用了“使用主机GPU”运行Jelly Bean或更高版本的ARM仿真器上.无论Android版本如何,在没有“使用主机GPU”的x86仿真器和ARM仿真器上都不会发生这种情况.我的测试的确切表可以在上面链接的github项目中找到.

// Imports left out
public class HorizontalSlidingLayout extends FrameLayout {
    /**
     * The fraction by which the content has slid into view. Legal range: from 0.0 (all content
     * off-screen) to 1.0 (all content visible).
     */
    private float percentOnScreen;
    private int screenWidth, screenHeight, shift;
    private Paint shadowPaint;

    // Constructors left out, all three call super, then init().

    private void init() {
        if (isInEditMode()) {
            // Ensure content is visible in edit mode.
            percentOnScreen = 1.0f;
        } else {
            setWillNotDraw(false);
            percentOnScreen = 0.0f;
            shadowPaint = new Paint();
            shadowPaint.setAlpha(0x00);
            shadowPaint.setColor(0x000000);
            shadowPaint.setStyle(Paint.Style.FILL);
        }
    }

    /** Reports our own size as (2w, h) and measures all children at (w, h). */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        screenWidth = MeasureSpec.getSize(widthMeasureSpec);
        screenHeight = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(2 * screenWidth, screenHeight);
        for (int i = 0, max = getChildCount(); i < max; i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    /** Lays out the children in the right half of the view. */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (int i = 0, max = getChildCount(); i < max; i++) {
            View child = getChildAt(i);
            child.layout(screenWidth, top, right, bottom);
        }
    }

    /**
     * Draws a translucent shadow in the left half of the view, darkening by
     * {@code percentOnScreen}, then lets superclass draw children in the right half.
     */
    @Override
    protected void onDraw(Canvas canvas) {
        // Maintain 30% translucency
        if (percentOnScreen < 0.7f) {
            shadowPaint.setAlpha((int) (percentOnScreen * 0xFF));
        }
        android.util.Log.i("Slider", "onDraw(" + percentOnScreen + ") -> alpha(" + shadowPaint.getAlpha() + ')');
        canvas.drawRect(shift, 0, screenWidth, screenHeight, shadowPaint);
        super.onDraw(canvas);
    }

    @SuppressWarnings("unused")
    public float getPercentOnScreen() {
        return percentOnScreen;
    }

    /** Repeatedly invoked by an Animator. */
    @SuppressWarnings("unused")
    public void setPercentOnScreen(float fraction) {
        this.percentOnScreen = fraction;
        shift = (int)(fraction < 1.0 ? fraction * screenWidth : screenWidth);
        setX(-shift);
        android.util.Log.i("Slider", "setPOS(" + fraction + ") -> invalidate(" + shift + ',' + screenWidth + ')');
        invalidate(shift, 0, screenWidth, screenHeight);
        //invalidate() // Makes no difference
    }
}

奇怪的是,这违反了对称性.当我滑入时滑动时,我正在反向做同样的事情,但行为是不同的.我可能忽视了一些愚蠢的事情.有任何想法吗?

最佳答案
这是Android中硬件加速视图的失效/重绘逻辑中的错误.我希望默认情况下会在JB中看到相同的错误,但只有在选择硬件加速时才会在ICS上看到(从JB开始默认启用hw accel).

问题是当从层次结构中删除视图然后进行动画处理时(例如应用程序中的片段事务中发生的事情,或者当删除的视图淡出时在LayoutTransition中,或者在淡化删除的视图的AlphaAnimation中) ,它们不参与普通/父级子视图所做的相同的失效/重绘逻辑.它们被正确地重新显示(因此我们看到片段滑出),但它们没有重绘,所以如果它们的内容在此期间实际发生了变化,它们将不会重新显示这些变化.应用程序中的错误的影响是片段正确滑出,但没有绘制阴影,因为这需要重新绘制视图以获取这些更改.

您使视图无效的方式是正确的,但该错误意味着失效无效.

之前没有出现过这个错误,因为我相信,消失的视图在改变它们的外观时并不常见,因为它们被动画化了(它们通常只是滑动或淡出,效果很好).

该bug应该在将来的版本中修复(我已经修复了它).同时,针对您的特定情况的解决方法是添加父容器的失效;这将强制重绘视图并正确显示阴影:

if (getParent() instanceof ViewGroup) {
    ((ViewGroup) getParent()).invalidate();
}

转载注明原文:当我反向运行动画时,Android会跳过onDraw() - 代码日志