Android Drawable完全解析(一):Drawable源码分析(中)

Stella981
• 阅读 651

Android Drawable完全解析(一):Drawable源码分析(上) Android Drawable完全解析(一):Drawable源码分析(中) Android Drawable完全解析(一):Drawable源码分析(下)

呃...我不是故意要凑篇幅写个什么上下篇,实在是因为Drawable源码有点长,一篇写不下啦O(∩_∩)O~

鉴于源码一般较长,以后所有源码分析的部分,英文注释非必要情况都不再保留! ####2:Drawable源码分析/翻译 继续上Drawable源码:

package android.graphics.drawable;

public abstract class Drawable {
    ****
    略
    ****

    /**
    这个方法很重要,故保留英文注释!
    调用mutate(),使当前Drawable实例mutable,这个操作不可逆。
    一个mutable的Drawable实例不会和其他Drawable实例共享它的状态。
    当你需要修改一个从资源文件加载的Drawable实例时,mutate()方法尤其有用。
    默认情况下,所有加载同一资源文件生成的Drawable实例都共享一个通用的状态,
    如果你修改了其中一个Drawable实例,所有的相关Drawable实例都会发生同样的变化。

    这个方法在[其实你不懂:Drawable着色(tint)的兼容方案 源码解析]
    这篇文章里有过介绍,就是为了限定Drawable实例的编辑生效范围仅限于自身。
     * Make this drawable mutable. This operation cannot be reversed. A mutable
     * drawable is guaranteed to not share its state with any other drawable.
     * This is especially useful when you need to modify properties of drawables
     * loaded from resources. By default, all drawables instances loaded from
     * the same resource share a common state; if you modify the state of one
     * instance, all the other instances will receive the same modification.
     *
     * Calling this method on a mutable Drawable will have no effect.
     *
     * @return This drawable.
     * @see ConstantState
     * @see #getConstantState()
     */
    public @NonNull Drawable mutate() {
        return this;
    }

    /**
    被隐匿
     * @hide
     */
    public void clearMutated() {
        // Default implementation is no-op.
    }

    //下面几个方法介绍了通过不同的方式创建Drawable实例:
    //流、XML、文件地址
    public static Drawable createFromStream(InputStream is, String srcName) {
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
        try {
            return createFromResourceStream(null, null, is, srcName);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }
    public static Drawable createFromResourceStream(Resources res, TypedValue value,
            InputStream is, String srcName) {
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
        try {
            return createFromResourceStream(res, value, is, srcName, null);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }
    public static Drawable createFromResourceStream(Resources res, TypedValue value,
            InputStream is, String srcName, BitmapFactory.Options opts) {
        if (is == null) {
            return null;
        }
        Rect pad = new Rect();
        if (opts == null) opts = new BitmapFactory.Options();
        opts.inScreenDensity = Drawable.resolveDensity(res, 0);
        Bitmap  bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
        if (bm != null) {
            byte[] np = bm.getNinePatchChunk();
            if (np == null || !NinePatch.isNinePatchChunk(np)) {
                np = null;
                pad = null;
            }
            final Rect opticalInsets = new Rect();
            bm.getOpticalInsets(opticalInsets);
            return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
        }
        return null;
    }
    public static Drawable createFromXml(Resources r, XmlPullParser parser)
            throws XmlPullParserException, IOException {
        return createFromXml(r, parser, null);
    }
    public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme)
            throws XmlPullParserException, IOException {
        AttributeSet attrs = Xml.asAttributeSet(parser);
        int type;
        //noinspection StatementWithEmptyBody
        while ((type=parser.next()) != XmlPullParser.START_TAG
                && type != XmlPullParser.END_DOCUMENT) {
            // Empty loop.
        }
        if (type != XmlPullParser.START_TAG) {
            throw new XmlPullParserException("No start tag found");
        }
        Drawable drawable = createFromXmlInner(r, parser, attrs, theme);
        if (drawable == null) {
            throw new RuntimeException("Unknown initial tag: " + parser.getName());
        }
        return drawable;
    }
    public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        return createFromXmlInner(r, parser, attrs, null);
    }
    public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
            Theme theme) throws XmlPullParserException, IOException {
        return r.getDrawableInflater().inflateFromXml(parser.getName(), parser, attrs, theme);
    }
    public static Drawable createFromPath(String pathName) {
        if (pathName == null) {
            return null;
        }
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
        try {
            Bitmap bm = BitmapFactory.decodeFile(pathName);
            if (bm != null) {
                return drawableFromBitmap(null, bm, null, null, null, pathName);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
        return null;
    }
    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
        inflate(r, parser, attrs, null);
    }

    /**
    从XML文件中加载Drawable实例,Drawable实例接受主题设置的风格
     */
    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
        mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
        a.recycle();
    }
    /**
    从XML文件中加载Drawable实例
     */
    void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r,
            @NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs,
            @AttrRes int visibleAttr) throws XmlPullParserException, IOException {
        mVisible = attrs.getBoolean(visibleAttr, mVisible);
    }

    /**
    这段注释很重要,故保留英文注释!

    ConstantState这个抽象类被用于存储 多个Drawable实例间 共享的 常量状态值及数据。
    如从同一个图片资源创建的多个BitmapDrawable实例,它们将共享
    同一个存储在它们的ConstantState中的Bitmap。
     * This abstract class is used by {@link Drawable}s to store shared constant state and data
     * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
     * share a unique bitmap stored in their ConstantState.
     *
    newDrawable可以运用ConstantState创建一个新的Drawable实例
     * <p>
     * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
     * from this ConstantState.
     * </p>
     *
    Drawable#getConstantState可以获取一个Drawable关联的ConstantState。
    调用Drawable#mutate(),则将为新创建的Drawable实例单独关联一个ConstantState。
     * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
     * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
     * Drawable.
     */
    public static abstract class ConstantState {
        /**
        运用ConstantState创建一个新的Drawable实例
         */
        public abstract @NonNull Drawable newDrawable();
        /**
         运用ConstantState创建一个新的Drawable实例
         */
        public @NonNull Drawable newDrawable(@Nullable Resources res) {
            return newDrawable();
        }
        /**
        运用ConstantState创建一个新的Drawable实例
         */
        public @NonNull Drawable newDrawable(@Nullable Resources res,
                @Nullable @SuppressWarnings("unused") Theme theme) {
            return newDrawable(res);
        }
        /**
        返回会影响Drawable实例的一个bit掩码变化设置
         */
        public abstract @Config int getChangingConfigurations();
        /**
        返回所有的像素数
        public int addAtlasableBitmaps(@NonNull Collection<Bitmap> atlasList) {
            return 0;
        }
        /** @hide */
        protected final boolean isAtlasable(@Nullable Bitmap bitmap) {
            return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888;
        }
        /**
        返回当前共享状态是否可以设置主题
         */
        public boolean canApplyTheme() {
            return false;
        }
    }

    /**
    返回当前Drawable的用于存储共享状态值的ConstantState实例
     */
    public @Nullable ConstantState getConstantState() {
        return null;
    }
    //通过Bitmap实例创建Drawable实例
    private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
            Rect pad, Rect layoutBounds, String srcName) {
        if (np != null) {
            return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
        }
        return new BitmapDrawable(res, bm);
    }

    /**
    确保色彩过滤器和当前色彩与色彩模式一致
     */
    @Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
            @Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) {
        if (tint == null || tintMode == null) {
            return null;
        }
        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
        if (tintFilter == null) {
            return new PorterDuffColorFilter(color, tintMode);
        }
        tintFilter.setColor(color);
        tintFilter.setMode(tintMode);
        return tintFilter;
    }

    /**
    如果主题有效,则从中获取样式属性,
    如果主题无效,则返回没有样式的资源。
     */
    static @NonNull TypedArray obtainAttributes(@NonNull Resources res, @Nullable Theme theme,
            @NonNull AttributeSet set, @NonNull int[] attrs) {
        if (theme == null) {
            return res.obtainAttributes(set, attrs);
        }
        return theme.obtainStyledAttributes(set, attrs, 0, 0);
    }

    /**
    根据 原始像素值,资源单位密度和目标设备单位密度 获得一个float像素值
     */
    static float scaleFromDensity(float pixels, int sourceDensity, int targetDensity) {
        return pixels * targetDensity / sourceDensity;
    }
    static int scaleFromDensity(
            int pixels, int sourceDensity, int targetDensity, boolean isSize) {
        if (pixels == 0 || sourceDensity == targetDensity) {
            return pixels;
        }
        final float result = pixels * targetDensity / (float) sourceDensity;
        if (!isSize) {
            return (int) result;
        }
        final int rounded = Math.round(result);
        if (rounded != 0) {
            return rounded;
        } else if (pixels > 0) {
            return 1;
        } else {
            return -1;
        }
    }

    //获取单位密度
    static int resolveDensity(@Nullable Resources r, int parentDensity) {
        final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
        return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
    }
    static void rethrowAsRuntimeException(@NonNull Exception cause) throws RuntimeException {
        final RuntimeException e = new RuntimeException(cause);
        e.setStackTrace(new StackTraceElement[0]);
        throw e;
    }

    /**
   通过解析tintMode属性枚举值获得一个PorterDuff.Mode
   
   被隐匿
     * @hide
     */
    public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
        switch (value) {
            case 3: return Mode.SRC_OVER;
            case 5: return Mode.SRC_IN;
            case 9: return Mode.SRC_ATOP;
            case 14: return Mode.MULTIPLY;
            case 15: return Mode.SCREEN;
            case 16: return Mode.ADD;
            default: return defaultMode;
        }
    }
}

Drawable类本身源码先写到这儿,接着往下看。

####3:Drawable绘制流程 看过Drawable源码,其实我们还是不清楚: Drawable实例到底是如何被绘制到屏幕上面? Drawable源码中的那些方法又是什么时候被谁调用的?

我们回想一下,使用Drawable最通常的步骤: 通过Resource获取Drawable实例 将获取的Drawable实例当做背景设置给View或者作为ImageView的src进行显示:

下面就逐步分析理解Drawable的绘制流程。 ######3.1:通过Resource获取Drawable实例 最常用写法:getResources().getDrawable(int id),看下关键代码:

public class Resources {
    ****
    public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
        final Drawable d = getDrawable(id, null);
        *****
        return d;
    }
    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
            throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            //
            impl.getValue(id, value, true);
            //将获取到的Drawable实例返回
            return impl.loadDrawable(this, value, id, theme, true);
        } ****
    }
}
一路追踪下去:
public class ResourcesImpl {
    //Resource实例,TypedValue,资源ID,Theme实例,true
    @Nullable
    Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
            boolean useCache) throws NotFoundException {
        try {
            ********
            //是否属于ColorDrawable
            final boolean isColorDrawable;
            //Drawable缓存
            final DrawableCache caches;
            final long key;
            //判断资源是否属于颜色资源
            if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
                    && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
                isColorDrawable = true;
                caches = mColorDrawableCache;
                key = value.data;
            } else {
                //如果是加载一张普通的图片,不属于颜色资源
                isColorDrawable = false;
                caches = mDrawableCache;
                key = (((long) value.assetCookie) << 32) | value.data;
            }
            if (!mPreloading && useCache) {
                final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
                if (cachedDrawable != null) {
                    return cachedDrawable;
                }
            }
            //如果在Drawable缓存里面未找到资源ID对应的Drawable实例,继续
            final Drawable.ConstantState cs;
            if (isColorDrawable) {
                cs = sPreloadedColorDrawables.get(key);
            } else {
                //如果不属于颜色资源,则从sPreloadedDrawables中查询
                //sPreloadedDrawables只有在执行cacheDrawable方法时
                //才会进行数据添加:而第一次加载图片时候还未执行cacheDrawable
                //所以此时cs = null.
                cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
            }
            Drawable dr;
            if (cs != null) {
                dr = cs.newDrawable(wrapper);
            } else if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            } else {
                //当第一次加载图片资源时候,cs=null且不属于颜色资源,
                //实际是通过loadDrawableForCookie来获取Drawable实例
                dr = loadDrawableForCookie(wrapper, value, id, null);
            }
            *********
    }
    private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
            Resources.Theme theme) {
        ****
        final String file = value.string.toString();
        ****
        final Drawable dr;
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
        try {
            if (file.endsWith(".xml")) {
                //如果是从xml文件加载Drawable
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(wrapper, rp, theme);
                rp.close();
            } else {
                //从图片资源加载Drawable,执行Drawable.createFromResourceStream
                //获取Drawable实例
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                is.close();
            }
        } catch (Exception e) {
            ****
        }
        ****
        return dr;
    }
}
一路追踪下去:
public abstract class Drawable {
    public static Drawable createFromResourceStream(Resources res, TypedValue value,
            InputStream is, String srcName, BitmapFactory.Options opts) {
        ****
            return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
        ****
    }
    private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
            Rect pad, Rect layoutBounds, String srcName) {
        if (np != null) {
            //如果加载的图片资源是.9 PNG,返回NinePatchDrawable实例
            return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
        }
        //对于普通图片资源,返回BitmapDrawable
        return new BitmapDrawable(res, bm);
    }
}

由此可见,通过Resource实例加载一张资源图片: .9图返回1个NinePatchDrawable实例; 普通图片返回1个BitmapDrawable实例。 ######3.2:将获取的Drawable实例当做背景设置给View 最常用写法:targetView.setBackgroundDrawable(Drawable bg), 同样看一下关键代码

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    ****
    public void setBackgroundDrawable(Drawable background) {
        ****
        if (background == mBackground) {
            //如果当前背景和background相同,直接return
            return;
        }
        boolean requestLayout = false;
        mBackgroundResource = 0;
        if (mBackground != null) {
            if (isAttachedToWindow()) {
                //如果当前View实例已经被绘制到屏幕上,则首先取消
                //该View实例原始背景Drawable的动画
                mBackground.setVisible(false, false);
            }
            //移除该View实例原始背景Drawable的动画监听接口
            mBackground.setCallback(null);
            //取消该View实例原始背景Drawable的所有事件
            unscheduleDrawable(mBackground);
        }
        if (background != null) {
            ****
            //设置background的布局方向和View实例一致,
            //Drawable.setLayoutDirection见上一篇文章
            background.setLayoutDirection(getLayoutDirection());
            if (background.getPadding(padding)) {
                //如果Drawable实例background有padding
                resetResolvedPaddingInternal();
                switch (background.getLayoutDirection()) {
                    case LAYOUT_DIRECTION_RTL:
                        //布局方向从右至左
                        mUserPaddingLeftInitial = padding.right;
                        mUserPaddingRightInitial = padding.left;
                        internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
                        break;
                    case LAYOUT_DIRECTION_LTR:
                    default:
                        //布局方向从左至右
                        mUserPaddingLeftInitial = padding.left;
                        mUserPaddingRightInitial = padding.right;
                        //internalSetPadding会将四个参数值和View实例的padding进行比对,若不同则会重新布局+重建View的外部轮廓
                        internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
                }
                mLeftPaddingDefined = false;
                mRightPaddingDefined = false;
            }
            if (mBackground == null
                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                requestLayout = true;
            }
            //设置当前View实例的背景为传入的Drawable实例 background
            mBackground = background;
            if (background.isStateful()) {
                //如果background会根据状态值变更外观,则设置其状态为
                //当前View实例的state
                background.setState(getDrawableState());
            }
            if (isAttachedToWindow()) {
                //如果当前View实例已经被绘制到屏幕上
                //且实例和实例的父控件及递归获得的根布局都处于可见状态,
                //则设置background开启动画效果
                background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
            }
            applyBackgroundTint();
            //设置background动画接口监听为View实例本身(View实现了 Drawable.Callback):
            //public class View implements Drawable.Callback
            background.setCallback(this);
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                //需要重新布局
                requestLayout = true;
            }
        } else {
            mBackground = null;
            if ((mViewFlags & WILL_NOT_DRAW) != 0
                    && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
                mPrivateFlags |= PFLAG_SKIP_DRAW;
            }
            requestLayout = true;
        }
        computeOpaqueFlags();
        if (requestLayout) {
            //重新布局
            requestLayout();
        }
        mBackgroundSizeChanged = true;
        //重绘View实例
        invalidate(true);
        //重建View实例的外部轮廓
        invalidateOutline();
    }
}

由此可见,setBackgroundDrawable方法,调用了Drawable实例的一系列方法,最终引发了View实例的重新布局(requestLayout())重绘(invalidate(true))及重建View实例的外部轮廓(invalidateOutline())。 invalidate会触发draw方法,我们继续看View.draw方法的关键代码:

    public void draw(Canvas canvas) {
        ****
        /*
        翻译可能不甚准确,欢迎英语好的同学留言指正O(∩_∩)O~

        Draw方法会执行以下几个步骤,且必须按顺序执行:
        1:绘制View实例的背景
        2:如有必要,保存画布图层以备褪色
        3:绘制View实例的内容
        4:绘制View实例的中的子控件
        5:如有必要,绘制边缘并恢复图层
        6:绘制滚动条
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        //从步骤顺序上看,和Drawable相关的就是第1步,只看第1步代码
        // Step 1, draw the background, if needed
        int saveCount;
        if (!dirtyOpaque) {
            //绘制背景
            drawBackground(canvas);
        }
        ****
    }

    一路追踪下去:

    private void drawBackground(Canvas canvas) {
        //mBackground就是之前setBackgroundDrawable传入的Drawable实例
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        //设置background绘制范围为View实例的所在范围
        setBackgroundBounds();
        ****
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            //最终调用Drawable.draw(Canvas canvas)将Drawable实例
            //绘制到屏幕上
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            //最终调用Drawable.draw(Canvas canvas)将Drawable实例
            //绘制到屏幕上
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

由此可见,View实例的背景Drawable实例最终还是调用自身的Drawable.draw(@NonNull Canvas canvas)方法绘制到屏幕上。

继续查看Drawable.draw方法:

public abstract class Drawable {
    //Drawable中的draw是一个抽象方法,应该是为了众多的
    //子类Drawable拥有自定义的绘制逻辑进行重写
    public abstract void draw(@NonNull Canvas canvas);
}

在分析Resource.getDrawable时候已经知道,
对于普通的图片资源,获取到的是一个BitmapDrawable实例,
我们就来看看BitmapDrawable的draw具体的绘制逻辑:

public class BitmapDrawable extends Drawable {
    @Override
    public void draw(Canvas canvas) {
        ****
        if (shader == null) {
            ****
            //最终调用了Canvas.drawBitmap方法,将Drawable实例中的bitmap绘制到View实例关联的画布上
            canvas.drawBitmap(bitmap, null, mDstRect, paint);
            if (needMirroring) {
                canvas.restore();
            }
        } ****
    }
}

至此,将获取的Drawable实例当做背景设置给View,和Drawable相关的一系列逻辑就分析完了,大致如下:

  • 1:setBackgroundDrawable方法,调用了Drawable的一系列方法,设置了Drawable实例一系列属性值,最终引发了View实例的重新布局(requestLayout()),重绘(invalidate(true))及重建View实例的外部轮廓(invalidateOutline())
  • 2:在View实例重绘过程的第一步,将得到的Drawable实例(View实例的背景)绘制到屏幕上,实质是调用了Drawable.draw(@NonNull Canvas canvas)
  • 3:Drawable.draw本身是个抽象方法,绘制具体逻辑由其子类实现。 我们以之前获得的BitmapDrawable为例进行分析: 最终调用了Canvas.drawBitmap方法,将Drawable实例中的bitmap绘制到View实例关联的画布上

Drawable绘制流程今天先写到这儿,现在是2017/03/07 20:41,加班码字到现在有点累了,明后天继续把ImageView和Drawable关联的部分写完吧!

未完待续...

以上就是个人分析的一点结果,若有错误,请各位同学留言告知!

That's all !

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
Android Drawable完全解析(一):Drawable源码分析(下)
AndroidDrawable完全解析(一):Drawable源码分析(上)(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.jianshu.com%2Fp%2F384a70897ba6)AndroidDrawable完全解析(一):Drawable源码分析(中)(htt
Wesley13 Wesley13
3年前
Unity横屏
Android下发现Unity里面的Player设置,并不能完全有效,比如打开了自动旋转,启动的时候还是会横屏,修改XML添加以下代码<applicationandroid:icon"@drawable/ic\_launcher"                    android:label"@string/app\_name"
Stella981 Stella981
3年前
Android Drawable完全解析(一):Drawable源码分析(上)
AndroidDrawable完全解析(一):Drawable源码分析(上)(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.jianshu.com%2Fp%2F384a70897ba6)AndroidDrawable完全解析(一):Drawable源码分析(中)(htt
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这