开源项目Plaid学习(六)ForegroundImageView

前言

上次介绍了一系列的TextView,这回来介绍一系列ImageView,大致关系是ImageView->ForegroundImageView->FourThreeImageView->BadgedFourThreeImageView。

源码

/**
 * An extension to {@link ImageView} which has a foreground drawable.
 */
public class ForegroundImageView extends ImageView {

    private Drawable foreground;

    public ForegroundImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView);

        final Drawable d = a.getDrawable(R.styleable.ForegroundView_android_foreground);
        if (d != null) {
            setForeground(d);
        }
        a.recycle();
        setOutlineProvider(ViewOutlineProvider.BOUNDS);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (foreground != null) {
            foreground.setBounds(0, 0, w, h);
        }
    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return super.verifyDrawable(who) || (who == foreground);
    }

    @Override
    public void jumpDrawablesToCurrentState() {
        super.jumpDrawablesToCurrentState();
        if (foreground != null) foreground.jumpToCurrentState();
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        if (foreground != null && foreground.isStateful()) {
            foreground.setState(getDrawableState());
        }
    }

    /**
     * Returns the drawable used as the foreground of this view. The
     * foreground drawable, if non-null, is always drawn on top of the children.
     *
     * @return A Drawable or null if no foreground was set.
     */
    public Drawable getForeground() {
        return foreground;
    }

    /**
     * Supply a Drawable that is to be rendered on top of the contents of this ImageView
     *
     * @param drawable The Drawable to be drawn on top of the ImageView
     */
    public void setForeground(Drawable drawable) {
        if (foreground != drawable) {
            if (foreground != null) {
                foreground.setCallback(null);
                unscheduleDrawable(foreground);
            }

            foreground = drawable;

            if (foreground != null) {
                foreground.setBounds(0, 0, getWidth(), getHeight());
                setWillNotDraw(false);
                foreground.setCallback(this);
                if (foreground.isStateful()) {
                    foreground.setState(getDrawableState());
                }
            } else {
                setWillNotDraw(true);
            }
            invalidate();
        }
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (foreground != null) {
            foreground.draw(canvas);
        }
    }

    @Override
    public void drawableHotspotChanged(float x, float y) {
        super.drawableHotspotChanged(x, y);
        if (foreground != null) {
            foreground.setHotspot(x, y);
        }
    }
}

很明显使用了一个自定义属性attrs_foreground_view.xml:

<resources>
    <declare-styleable name="ForegroundView">
        <attr name="android:foreground" />
        <attr name="android:foregroundInsidePadding" />
        <attr name="android:foregroundGravity" />
    </declare-styleable>
</resources>

前缀是android,说明这些属性已经在系统里面定义好了,可以直接拿出来用而不用再定义format。foreground这个属性就是foreground drawable。
这个控件的目的非常明确,也就是在原有的图片之前加一个前景图片。
然后就从代码顺序来进行拓展和学习。

setOutlineProvider(ViewOutlineProvider.BOUNDS);

这个是在构造器里的最后一行代码。对于ViewOutlineProvider,官方的解释是

public abstract class ViewOutlineProvider
extends Object
java.lang.Object
↳ android.view.ViewOutlineProvider
Interface by which a View builds its Outline , used for shadow casting and clipping.

=_=好吧那什么又是outline呢?

public final class Outline
extends Object
java.lang.Object
↳ android.graphics.Outline
Defines a simple shape, used for bounding graphical regions.
Can be computed for a View, or computed by a Drawable, to drive the shape of shadows cast by a View, or to clip the contents of the View.

Outline的中文翻译就是轮廓,可以用来裁剪和投影。再回到ViewOutlineProvider,有三种方式:

BACKGROUND

Default outline provider for Views, which queries the Outline from the View's background, or generates a 0 alpha, rectangular Outline the size of the View if a background isn't present.
BOUNDS
Maintains the outline of the View to match its rectangular bounds, at 1.0f alpha.
PADDED_BOUNDS
Maintains the outline of the View to match its rectangular padded bounds, at 1.0f alpha.

BACKGROUND是默认类型,而后面两个其实差不多,区别只是padding。这三个是默认自带的,如果有需要,还可以自己定义一个ViewOutlineProvider,只需override掉getOutline方法。
比如安卓SDK源码里面BACKGROUND的实现:

public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            Drawable background = view.getBackground();
            if (background != null) {
                background.getOutline(outline);
            } else {
                outline.setRect(0, 0, view.getWidth(), view.getHeight());
                outline.setAlpha(0.0f);
            }
        }
    };

至于Bounds的实现就是else那一小段。也就是说直接取view的矩形区域作为outline。ImageView确实有getBackground()方法不过很少使用,是怕万一设置了Background导致outline不对?这也只是我的猜测。

onSizeChanged

这个方法是从View一路继承下来的,View那里啥也不干,就是给后面继承的;比较有意思的是ImageView并没有重写这个方法,其尺寸是和作为来源的图片有关的。在这里,其实也就是把foreground的bounds设置得和本身一样。
ImageView的源码里面并没有调用任何onSizeChanged方法,也就是说其本身并不会改变尺寸,而这个函数在这里重写,是为了在外界强行改变尺寸时,确保foreground的边界也随之变化。

hasOverlappingRendering

boolean hasOverlappingRendering ()
Returns whether this View has content which overlaps.
This function, intended to be overridden by specific View types, is an optimization when alpha is set on a view. If rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer and then composited into place, which can be expensive. If the view has no overlapping rendering, the view can draw each primitive with the appropriate alpha value directly. An example of overlapping rendering is a TextView with a background image, such as a Button. An example of non-overlapping rendering is a TextView with no background, or an ImageView with only the foreground image. The default implementation returns true; subclasses should override if they have cases which can be optimized.
The current implementation of the saveLayer and saveLayerAlpha methods in Canvas
necessitates that a View return true if it uses the methods internally without passing the CLIP_TO_LAYER_SAVE_FLAG.
Note: The return value of this method is ignored if forceHasOverlappingRendering(boolean)
has been called on this view.

意思就是,假如有重叠的情况要不要考虑重叠效果。这里应该是foreground的会直接遮盖后面的内容,就不必再考虑重叠效果了。

verifyDrawable

boolean verifyDrawable (Drawable dr)
If your view subclass is displaying its own Drawable objects, it should override this function and return true for any Drawable it is displaying. This allows animations for those drawables to be scheduled.
Be sure to call through to the super class when overriding this function.

意思就是假如显示自己的drawable就应该重写该方法,调用super方法并返回true。
看了一下源码,从View->ImageView都有这个方法,而且挺短的:

// from View
    protected boolean verifyDrawable(@NonNull Drawable who) {
        // Avoid verifying the scroll bar drawable so that we don't end up in
        // an invalidation loop. This effectively prevents the scroll bar
        // drawable from triggering invalidations and scheduling runnables.
        return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
    }
// from ImageView
    @Override
    protected boolean verifyDrawable(@NonNull Drawable dr) {
        return mDrawable == dr || super.verifyDrawable(dr);
    }

首先要搞明白这个函数什么时候调用,就是在scheduleDrawable(),unscheduleDrawable()还有invalidateDrawable()时调用,其功能和名字一样。
View里面验证其是不是前景或者背景,ImageView里面加上是不是源的验证。
同时我们也可以看出,其实在View里面已经有了ForegroundInfo这个东西,里面带了Drawable,而且从API1就有。那么为什么要大费周章再弄一个呢?而且也支持Gravity。
光从这一个自定义View来看,确实找不到很好的理由。可能之后系列的自定义View会继续扩展吧。不过也可能作者是真没想那么多也说不准……

jumpDrawablesToCurrentState & drawableStateChanged

void jumpDrawablesToCurrentState ()
Call Drawable.jumpToCurrentState()
on all Drawable objects associated with this view.
Also calls jumpToCurrentState()
if there is a StateListAnimator attached to this view.

就是说,Drawable可能会有不同的state也就是状态,这个就是把所有相关的Drawable跳到当前状态。这里只需要再把foreground跳一下就行。drawableStateChanged也是同理。

setForeground

核心方法。首先判断是不是已经设置好了,然后取消之前的一些回调和schedule;再判断新的Drawable,为空则跳过draw步骤;否则,设置边界,设置会调用draw,然后设置一下回调和状态。
foreground.setCallback(this)这一句,this其实指代的是ImageView里面的Callback,其实也就是一个接口,实现invalidateDrawable,scheduleDrawable和unscheduleDrawable三个方法。其实这个回调接口在View里面就有了:public class View implements Drawable.Callback……
invalidate在字面上的意思是“废止”,有点不太好理解,其实就是告诉系统有东西变化了,让系统来重画。

drawableHotspotChanged

Hotspot字面意思是热点,据说是用来实现ripple效果的。这里也对前景设置一下就好了。

实际效果

对于本控件,因为没有设置foreground的具体尺寸,因此会扩充到整个控件大小,真正的“前景”。而且由于设置了不重叠模式,前景会挡住后面的内容。总而言之,暂时来看用处并不是非常大,急需一个可以设置foreground尺寸的功能。
对于之前谈到过的View自带foreground,我试了一下,ImageView不能显示而FrameLayout可以。当然FrameLayout是通过设置背景再设置前景达到这样的目的,本质上来说并不是ImageView。
所以说,作者写这个控件确实是有原因的。当然这个只是初步,在下一期将介绍Plaid真正用到的BadgedFourThreeImageView。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Correctness AdapterViewChildren Summary: AdapterViews can...
    MarcusMa阅读 8,855评论 0 6
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,412评论 2 45
  • 传奇。 我当时也不知道为什么起这个名字。可能我觉得主人公会死,比较悲剧,就用了这么一个标题。 其实起标题真不能太随...
    ARUNANHUI阅读 250评论 0 0
  • 听着小石器在操场大喊,我明白,自己已经变成了大二的学姐了。这句学姐真的是来之不易,我也相信很多跟我一样走进大学的人...
    新乡学院物电杨坷阅读 333评论 2 0
  • 我看见火 听见宿命在内心滑过的涟漪 就像死神举起镰刀 那一瞬间的美妙与绝望 但我怕我靠近时煽动的微风 带来 火焰的...
    Natalias阅读 343评论 1 1