文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Androidshape标签使用方法介绍

2024-04-02 19:55

关注

作为Android开发,shape标签的使用定然不陌生。

shape标签基本使用语法

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape=["rectangle" | "oval" | "line" | "ring"] >
    <corners
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer"
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer" />
    <gradient
        android:angle="integer"
        android:centerX="integer"
        android:centerY="integer"
        android:centerColor="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type=["linear" | "radial" | "sweep"]
        android:useLevel=["true" | "false"] />
    <padding
        android:left="integer"
        android:top="integer"
        android:right="integer"
        android:bottom="integer" />
    <size
        android:width="integer"
        android:height="integer" />
    <solid
        android:color="color" />
    <stroke
        android:width="integer"
        android:color="color"
        android:dashWidth="integer"
        android:dashGap="integer" />
</shape>

shape标签可用于各种背景绘制,然而每需要一个新的背景,即使只有细微的改动,诸如一个角度的改变、颜色的改变,都需要重新创建一个xml文件以配置新背景的shape标签。

通过了解shape标签是如何进行背景绘制的,就可以后续进行自定义属性开发来解放大量shape标签下的xml文件的创建。

Shape标签生成GradientDrawable对象

首先来了解一下,shape标签下的xml文件是如何最终被解析为GradientDrawable对象。

View对象的background属性最终是一个Drawable对象,shape标签下的xml文件也是被赋予给了background属性,最终也是生成了一个Drawable对象。

在View的构造函数中可看到是通过TypedArray.getDrawable获得Drawable对象赋予background属性。

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
        ....
        background = a.getDrawable(attr);
        ....
}

追踪下去,Resources.loadDrawable -> ResourcesImpl.loadDrawable -> ResourcesImpl.loadXmlDrawable。

因为是在xml文件中定义,因此必然需要一个xml解析器进行解析。在此处就获取了一个XmlResourceParser,然后传入Drawable.createFromXmlForDensity。

private Drawable loadXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
            int id, int density, String file)
            throws IOException, XmlPullParserException {
        try (
                XmlResourceParser rp =
                        loadXmlResourceParser(file, id, value.assetCookie, "drawable")
        ) {
            return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
        }
    }

平时解析layout文件的时候经常会使用LayoutInflater,那么Drawable是否也存在对应的DrawableInflater呢?继续往下走,就会发现答案是肯定的。

@NonNull
    static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
            @Nullable Theme theme) throws XmlPullParserException, IOException {
        return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
                density, theme);
    }

此处通过Resources.getDrawableInflater获取到DrawableInflater,接着就使用DrawableInflater的inflateFromXmlForDensity方法进行解析。

@NonNull
    Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs, int density, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
        ....
        Drawable drawable = inflateFromTag(name);
        if (drawable == null) {
            drawable = inflateFromClass(name);
        }
        drawable.setSrcDensityOverride(density);
        drawable.inflate(mRes, parser, attrs, theme);
        return drawable;
    }

在DrawableInflater的inflateFromXmlForDensity方法中可以看见,通过inflateFromTag方法,生成了Drawable对象,并最终将其返回,那么shape标签生成GradientDrawable对象的逻辑就在该方法内了。

private Drawable inflateFromTag(@NonNull String name) {
        switch (name) {
            case "selector":
                return new StateListDrawable();
            case "animated-selector":
                return new AnimatedStateListDrawable();
            case "level-list":
                return new LevelListDrawable();
            case "layer-list":
                return new LayerDrawable();
            case "transition":
                return new TransitionDrawable();
            case "ripple":
                return new RippleDrawable();
            case "adaptive-icon":
                return new AdaptiveIconDrawable();
            case "color":
                return new ColorDrawable();
            case "shape":
                return new GradientDrawable();
            case "vector":
                return new VectorDrawable();
            case "animated-vector":
                return new AnimatedVectorDrawable();
            case "scale":
                return new ScaleDrawable();
            case "clip":
                return new ClipDrawable();
            case "rotate":
                return new RotateDrawable();
            case "animated-rotate":
                return new AnimatedRotateDrawable();
            case "animation-list":
                return new AnimationDrawable();
            case "inset":
                return new InsetDrawable();
            case "bitmap":
                return new BitmapDrawable();
            case "nine-patch":
                return new NinePatchDrawable();
            case "animated-image":
                return new AnimatedImageDrawable();
            default:
                return null;
        }
    }

一目了然,通过不同的标签名字生成相应的Drawable对象。shape标签生成GradientDrawable对象,selector标签生成StateListDrawable对象。

GradientDrawable获取shape子标签属性

看GradientDrawable必然要先看GradientState。

每一个Drawable的子类,都会有一个继承于ConstantState的内部静态类,它里面所声明的属性肯定都是这一个子类Drawable中独有的。

final static class GradientState extends ConstantState {
        public @Shape int mShape = RECTANGLE;
        public ColorStateList mSolidColors;
        public ColorStateList mStrokeColors;
        public int mStrokeWidth = -1;
        public float mStrokeDashWidth = 0.0f;
        public float mRadius = 0.0f;
        public float[] mRadiusArray = null;
        ....
}
@IntDef({RECTANGLE, OVAL, LINE, RING})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Shape {}

可以看到Shape定义了四个值的取值范围。那么GradientState里的这些属性又是怎么获取的呢?

@Override
    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
        super.inflate(r, parser, attrs, theme);
        mGradientState.setDensity(Drawable.resolveDensity(r, 0));
        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
        updateStateFromTypedArray(a);
        a.recycle();
        inflateChildElements(r, parser, attrs, theme);
        updateLocalState(r);
    }

在GradientDrawable.inflate里,通过inflateChildElements就能获取到各个子标签属性了。

private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
            Theme theme) throws XmlPullParserException, IOException {
        TypedArray a;
        int type;
        ....
            String name = parser.getName();
            if (name.equals("size")) {
                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
                updateGradientDrawableSize(a);
                a.recycle();
            } else if (name.equals("gradient")) {
                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
                updateGradientDrawableGradient(r, a);
                a.recycle();
            } else if (name.equals("solid")) {
                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
                updateGradientDrawableSolid(a);
                a.recycle();
            } else if (name.equals("stroke")) {
                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
                updateGradientDrawableStroke(a);
                a.recycle();
            } else if (name.equals("corners")) {
                a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
                updateDrawableCorners(a);
                a.recycle();
            } else if (name.equals("padding")) {
                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
                updateGradientDrawablePadding(a);
                a.recycle();
            } else {
                Log.w("drawable", "Bad element under <shape>: " + name);
            }
        }
    }

看到了在写shape标签下的xml文件时,熟悉的"corners"、“solid”、“gradient”。

以"corners"为例:

private void updateDrawableCorners(TypedArray a) {
        final GradientState st = mGradientState;
        // Account for any configuration changes.
        st.mChangingConfigurations |= a.getChangingConfigurations();
        // Extract the theme attributes, if any.
        st.mAttrCorners = a.extractThemeAttrs();
        final int radius = a.getDimensionPixelSize(
                R.styleable.DrawableCorners_radius, (int) st.mRadius);
        setCornerRadius(radius);
        // TODO: Update these to be themeable.
        final int topLeftRadius = a.getDimensionPixelSize(
                R.styleable.DrawableCorners_topLeftRadius, radius);
        final int topRightRadius = a.getDimensionPixelSize(
                R.styleable.DrawableCorners_topRightRadius, radius);
        final int bottomLeftRadius = a.getDimensionPixelSize(
                R.styleable.DrawableCorners_bottomLeftRadius, radius);
        final int bottomRightRadius = a.getDimensionPixelSize(
                R.styleable.DrawableCorners_bottomRightRadius, radius);
        if (topLeftRadius != radius || topRightRadius != radius ||
                bottomLeftRadius != radius || bottomRightRadius != radius) {
            // The corner radii are specified in clockwise order (see Path.addRoundRect())
            setCornerRadii(new float[] {
                    topLeftRadius, topLeftRadius,
                    topRightRadius, topRightRadius,
                    bottomRightRadius, bottomRightRadius,
                    bottomLeftRadius, bottomLeftRadius
            });
        }
    }

通过setCornerRadius和setCornerRadii,把角度值赋值给了mGradientState属性。

GradientDrawable进行shape绘制

绘制自然是在draw方法内了,大致可分为4个步骤:

@Override
    public void draw(Canvas canvas) {
        1、判断是否需要绘制,如果不需要绘制,则直接return
        if (!ensureValidRect()) {
            // nothing to draw
            return;
        }
        2、获取各类变量,并依据useLayer变量设置对应的属性
        // remember the alpha values, in case we temporarily overwrite them
        // when we modulate them with mAlpha
        final int prevFillAlpha = mFillPaint.getAlpha();
        final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
        // compute the modulate alpha values
        final int currFillAlpha = modulateAlpha(prevFillAlpha);
        final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha);
        final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null &&
                mStrokePaint.getStrokeWidth() > 0;
        final boolean haveFill = currFillAlpha > 0;
        final GradientState st = mGradientState;
        final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mBlendModeColorFilter;
        
        final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
                 currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
        
        if (useLayer) {
            if (mLayerPaint == null) {
                mLayerPaint = new Paint();
            }
            mLayerPaint.setDither(st.mDither);
            mLayerPaint.setAlpha(mAlpha);
            mLayerPaint.setColorFilter(colorFilter);
            float rad = mStrokePaint.getStrokeWidth();
            canvas.saveLayer(mRect.left - rad, mRect.top - rad,
                             mRect.right + rad, mRect.bottom + rad,
                             mLayerPaint);
            // don't perform the filter in our individual paints
            // since the layer will do it for us
            mFillPaint.setColorFilter(null);
            mStrokePaint.setColorFilter(null);
        } else {
            
            mFillPaint.setAlpha(currFillAlpha);
            mFillPaint.setDither(st.mDither);
            mFillPaint.setColorFilter(colorFilter);
            if (colorFilter != null && st.mSolidColors == null) {
                mFillPaint.setColor(mAlpha << 24);
            }
            if (haveStroke) {
                mStrokePaint.setAlpha(currStrokeAlpha);
                mStrokePaint.setDither(st.mDither);
                mStrokePaint.setColorFilter(colorFilter);
            }
        }
        3、根据shape四种属性绘制对应的图形
        switch (st.mShape) {
            case RECTANGLE:
            根据是否有角度,以及角度是否相同,分别采用canvas.drawRect、canvas.drawRoundRect、canvas.drawPath进行绘制
            case OVAL:
            使用canvas.drawOval进行绘制
            case LINE: 
            使用canvas.drawLine进行绘制
            case RING:
            使用canvas.drawPath进行绘制
        }
        4、恢复现场
        if (useLayer) {
            canvas.restore();
        } else {
            mFillPaint.setAlpha(prevFillAlpha);
            if (haveStroke) {
                mStrokePaint.setAlpha(prevStrokeAlpha);
            }
        }
    }

第一部分判断是否需要绘制全靠ensureValidRect方法,正如方法名字面意思一样,确保有效的矩形。该方法内部逻辑复杂,感兴趣的可以自行研究,先看一下方法注释。


检查变量mGradientIsDirty,如果是true,那么就重新计算mRect和gradient。返回值为mRect是否非空(也就是mRect有一个非零的大小)。

mGradientIsDirty会在一些方法中被赋值为true,例如改变了颜色、改变了gradient相关的,这意味着mRect和gradient需要重新计算。

到此这篇关于Android shape标签使用方法介绍的文章就介绍到这了,更多相关Android shape标签内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯