当前位置:主页 > android教程 > Android shape标签

Android shape标签使用方法介绍

发布:2023-03-06 11:30:01 59


给网友们整理相关的编程文章,网友关嘉纳根据主题投稿了本篇教程内容,涉及到Android、shape标签、Android、shape、Android shape标签相关内容,已被965网友关注,下面的电子资料对本篇知识点有更加详尽的解释。

Android shape标签

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

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 : " + 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;
        /*  we need a layer iff we're drawing both a fill and stroke, and the
            stroke is non-opaque, and our shapetype actually supports
            fill+stroke. Otherwise we can just draw the stroke (if any) on top
            of the fill (if any) without worrying about blending artifacts.
         */
        final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
                 currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
        /*  Drawing with a layer is slower than direct drawing, but it
            allows us to apply paint effects like alpha and colorfilter to
            the result of multiple separate draws. In our case, if the user
            asks for a non-opaque alpha value (via setAlpha), and we're
            stroking, then we need to apply the alpha AFTER we've drawn
            both the fill and the stroke.
        */
        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 {
            /*  if we're not using a layer, apply the dither/filter to our
                individual paints
            */
            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方法,正如方法名字面意思一样,确保有效的矩形。该方法内部逻辑复杂,感兴趣的可以自行研究,先看一下方法注释。

/**
     * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
     * rectangle (mRect) and the gradient itself, since it depends on our
     * rectangle too.
     * @return true if the resulting rectangle is not empty, false otherwise
     */

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

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

  • 第二部分依据代码中的注释可以非常清楚,获取各类变量,并依据useLayer变量设置对应的属性。useLayer属性,只有在设置了边界(笔划/stroke)和内部填充模式,并且形状不是线型等条件下才为true。 1.根据设置的属性判断是否需要再绘制一个layer; 2.如果需要layer,则创建layer相关属性并根据属性创建新的图层; 3.如果不需要layer,则只设置相应的fill/stroke属性即可。
  • 第三部分根据shape四种属性绘制对应的图形。需要注意的是,这里使用的canvas.drawXXXX方法,可能是saveLayer创建的新图层,也可能是没有变过的老图层。对于RECTANGLE,根据是否有角度,以及角度是否相同,分别采用canvas.drawRect、canvas.drawRoundRect、canvas.drawPath进行绘制。对于OVAL,使用canvas.drawOval进行绘制。对于LINE,使用canvas.drawLine进行绘制。对于RING,先调用了buildRing方法返回一个Path对象,再使用canvas.drawPath进行绘制。
  • 第四部分恢复现场,因为前面有saveLayer方法调用,那么图层就会发生变化,如果不恢复那么后续都会在新图层上面进行绘制。

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


参考资料

相关文章

  • Android自定义View实现天气预报折线图

    发布:2023-03-08

    这篇文章主要为大家详细介绍了Android自定义View实现天气预报折线图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下


  • Android Flutter实现上拉加载组件的示例代码

    发布:2023-03-03

    既然列表有下拉刷新外当然还有上拉加载更多操作了,本次就为大家详细介绍如何利用Flutter实现为列表增加上拉加载更多的交互,感兴趣的可以了解一下


  • Android PowerManagerService省电模式策略控制

    发布:2023-03-02

    这篇文章主要介绍了Android PowerManagerService省电模式策略控制,本文基于前两篇文章的基础介绍展开详情,感兴趣的小伙伴可以参考一下


  • Android使用AlarmManager设置闹钟功能

    发布:2023-03-11

    这篇文章主要为大家详细介绍了Android使用AlarmManager设置闹钟功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下


  • Android Jetpack组件库LiveData源码深入探究

    发布:2023-03-11

    LiveData是Jetpack组件的一部分,更多的时候是搭配ViewModel来使用,相对于Observable,LiveData的最大优势是其具有生命感知的,换句话说,LiveData可以保证只有在组件( Activity、Fragment、Service)处于活动生命周期状态的时候才会更新数据


  • Android 手写RecyclerView实现列表加载

    发布:2023-03-03

    这篇文章主要介绍了Android 手写RecyclerView实现列表加载,涉及到列表的需求,肯定第一时间想到RecyclerView,即便是自定义View,那么RecyclerView也会是首选,为什么会选择RecyclerView而不是ListView,主要就是RecyclerView的内存复用机制,这也是RecyclerView的核心 


  • Android Studio调试Gradle插件详情

    发布:2023-03-05

    这篇文章主要介绍了Android Studio调试Gradle插件详情,文章围绕主题展开详细的内容戒杀,具有一定的参考价值,需要的小伙伴可以参考一下


  • Android MPAndroidChart绘制原理

    发布:2023-03-02

    这篇文章主要介绍了Android MPAndroidChart绘制原理,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下


网友讨论