关闭

Android LayerDrawable 和 Drawable.Callback

170人阅读 评论(0) 收藏 举报
分类:


Android LayerDrawable 和 Drawable.Callback

LayerDrawable是一个特殊的Drawable,它内部保持着一个Drawable数组,其中每一个Drawable都是视图中的一层。如果你不了解LayerDrawable的机制,当程序出了问题后是很难去找到bug在哪里的。我发这些文章就是为了分享在使用LayerDrawableDrawable.Callback时可能出现的一个bug。

Callback调用链

LayerDrawable中,每层视图(Drawable)都会将LayerDrawable注册为它的Drawable.Callback。这允许Drawable能够在需要重绘自己的时候告知LayerDrawable重绘它。我们可以在下面这个Callback.invalidateSelf()函数中看到是由注册callback端(在此处为LayerDrawable)来执行invalidateDrawable(Drawable drawable)的。

<code class="language-java hljs  has-numbering" sizcache="23" sizset="30"><span style="font-size:18px;"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">invalidateSelf</span>() {
    <span class="hljs-comment">/* 获取注册的Callback实例,如果无则返回null。 */</span>
    <span class="hljs-keyword">final</span> Callback callback = getCallback();
    <span class="hljs-keyword">if</span> (callback != <span class="hljs-keyword">null</span>) {
        callback.invalidateDrawable(<span class="hljs-keyword">this</span>);
    }
}</span></code><ul class="pre-numbering" style="ZOOM: 1; FILTER:  "><li><span style="font-size:18px;">1</span></li></ul>

我们知道View是实现了Drawable.Callback接口的,所以当图片需要重绘的时候就能够告知View。如果我们把View的背景图片设置成了LayerDrawable,在Drawable需要更新的时候callback的调用将有一个传递的过程,首先会调用注册的LayerDrawableinvalidateDrawable(Drawable drawable)方法,LayerDrawable又会调用ViewinvalidateDrawable(Drawable drawable)方法。如下图所示:

Alt text

View改变背景时移除原背景Callback

ViewsetBackgroundDrawable(Drawable background)中有这么一段代码:

<code class="language-java hljs  has-numbering" sizcache="23" sizset="38"><span style="font-size:18px;">    <span class="hljs-keyword">if</span> (mBackground != <span class="hljs-keyword">null</span>) {
        mBackground.setCallback(<span class="hljs-keyword">null</span>);
        unscheduleDrawable(mBackground);
    }

    …
    <span class="hljs-keyword">if</span> (background != <span class="hljs-keyword">null</span>) {
        background.setCallback(<span class="hljs-keyword">this</span>);
    }</span></code><ul class="pre-numbering" style="ZOOM: 1; FILTER:  "><li><span style="font-size:18px;">1</span></li></ul>

我们可以看出:当View改变背景时将会无条件将原背景(如果原背景是Drawable的话)的Drawable.Callback设置为null

什么情况下会出现Bug?

有了上面这些知识,我们可以通过下面这个步骤产生一个Bug:

  1. DrawableA 设置成ViewV的背景。现在A的callback指向V
  2. 将A设置成LayerDrawable L中的一层。现在A的callback指向L
  3. 现在为V设置另一个背景,V会把原背景(A)的callback强制设置成null,破坏了A与L之间的联系。
  4. BUG出现了:更新DrawableA不会让L更新了。

解决方法就是在更新V的背景之后再创造LayerDrawableL。Bug发生与解决的例子可以在这里下载。

为了方便看官,我也贴了一部分关键代码到这边来,你可以通过注释理解这段代码。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button btn1 = (Button) findViewById(R.id.button1);
    btn1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 1. 将 launcherIconDrawable.callback 赋值给 actionBar
            actionBar.setBackgroundDrawable(launcherIconDrawable);
            animateActionBarWorking();
        }
    });
    Button btn2 = (Button) findViewById(R.id.button2);
    btn2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 1. 将 launcherIconDrawable.callback 赋值给 actionBar
            actionBar.setBackgroundDrawable(launcherIconDrawable);
            animateActionBarNotWorking();
        }
    });
    actionBar = getSupportActionBar();
    launcherIconDrawable = getResources().getDrawable(R.drawable.launcher_repeat);
    colorLayer = new ColorDrawable(Color.rgb(0, 255, 0));
    actionBar.setBackgroundDrawable(colorLayer);
}

/* 这个函数运行后ActionBar不会得到更新。 */
private void animateActionBarNotWorking() {
    Drawable[] layers = new Drawable[] { colorLayer, launcherIconDrawable };
    LayerDrawable layerDrawable = new LayerDrawable(layers);
    actionBar.setBackgroundDrawable(layerDrawable);
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 255);
    valueAnimator.setDuration(1000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 4. Updates launcherIconDrawable will not trigger action bar background to update
            // as launcherIconDrawable.callback is null
            launcherIconDrawable.setAlpha((Integer) animation.getAnimatedValue());
        }
    });
    valueAnimator.start();
}

/* 由于先移除了launcherIconDrawable与ActionBar的联系,这个函数运行后会让ActionBar得到更新。
private void animateActionBarWorking() {
    actionBar.setBackgroundDrawable(null);
    animateActionBarNotWorking();
}
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:97324次
    • 积分:2113
    • 等级:
    • 排名:第17965名
    • 原创:94篇
    • 转载:145篇
    • 译文:6篇
    • 评论:5条
    最新评论