亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android shape標簽使用方法介紹

 更新時間:2022年09月08日 09:05:34   作者:愿天深海  
shape算是我們常用的一個標簽,他可以生成線條,矩形, 圓形, 圓環(huán),像我們圓角的按鈕就可以通過shape來實現(xiàn),最終Android會把這個帶有shape標簽的圖片解析成一個Drawable對象,這個Drawable對象本質是GradientDrawable

作為Android開發(fā),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標簽可用于各種背景繪制,然而每需要一個新的背景,即使只有細微的改動,諸如一個角度的改變、顏色的改變,都需要重新創(chuàng)建一個xml文件以配置新背景的shape標簽。

通過了解shape標簽是如何進行背景繪制的,就可以后續(xù)進行自定義屬性開發(fā)來解放大量shape標簽下的xml文件的創(chuàng)建。

Shape標簽生成GradientDrawable對象

首先來了解一下,shape標簽下的xml文件是如何最終被解析為GradientDrawable對象。

View對象的background屬性最終是一個Drawable對象,shape標簽下的xml文件也是被賦予給了background屬性,最終也是生成了一個Drawable對象。

在View的構造函數(shù)中可看到是通過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文件的時候經(jīng)常會使用LayoutInflater,那么Drawable是否也存在對應的DrawableInflater呢?繼續(xù)往下走,就會發(fā)現(xiàn)答案是肯定的。

@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對象的邏輯就在該方法內(nèi)了。

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的內(nèi)部靜態(tài)類,它里面所聲明的屬性肯定都是這一個子類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方法內(nèi)了,大致可分為4個步驟:

@Override
    public void draw(Canvas canvas) {
        1、判斷是否需要繪制,如果不需要繪制,則直接return
        if (!ensureValidRect()) {
            // nothing to draw
            return;
        }
        2、獲取各類變量,并依據(jù)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、根據(jù)shape四種屬性繪制對應的圖形
        switch (st.mShape) {
            case RECTANGLE:
            根據(jù)是否有角度,以及角度是否相同,分別采用canvas.drawRect、canvas.drawRoundRect、canvas.drawPath進行繪制
            case OVAL:
            使用canvas.drawOval進行繪制
            case LINE: 
            使用canvas.drawLine進行繪制
            case RING:
            使用canvas.drawPath進行繪制
        }
        4、恢復現(xiàn)場
        if (useLayer) {
            canvas.restore();
        } else {
            mFillPaint.setAlpha(prevFillAlpha);
            if (haveStroke) {
                mStrokePaint.setAlpha(prevStrokeAlpha);
            }
        }
    }

第一部分判斷是否需要繪制全靠ensureValidRect方法,正如方法名字面意思一樣,確保有效的矩形。該方法內(nèi)部邏輯復雜,感興趣的可以自行研究,先看一下方法注釋。

/**
     * 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需要重新計算。

  • 第二部分依據(jù)代碼中的注釋可以非常清楚,獲取各類變量,并依據(jù)useLayer變量設置對應的屬性。useLayer屬性,只有在設置了邊界(筆劃/stroke)和內(nèi)部填充模式,并且形狀不是線型等條件下才為true。 1.根據(jù)設置的屬性判斷是否需要再繪制一個layer; 2.如果需要layer,則創(chuàng)建layer相關屬性并根據(jù)屬性創(chuàng)建新的圖層; 3.如果不需要layer,則只設置相應的fill/stroke屬性即可。
  • 第三部分根據(jù)shape四種屬性繪制對應的圖形。需要注意的是,這里使用的canvas.drawXXXX方法,可能是saveLayer創(chuàng)建的新圖層,也可能是沒有變過的老圖層。對于RECTANGLE,根據(jù)是否有角度,以及角度是否相同,分別采用canvas.drawRect、canvas.drawRoundRect、canvas.drawPath進行繪制。對于OVAL,使用canvas.drawOval進行繪制。對于LINE,使用canvas.drawLine進行繪制。對于RING,先調用了buildRing方法返回一個Path對象,再使用canvas.drawPath進行繪制。
  • 第四部分恢復現(xiàn)場,因為前面有saveLayer方法調用,那么圖層就會發(fā)生變化,如果不恢復那么后續(xù)都會在新圖層上面進行繪制。

到此這篇關于Android shape標簽使用方法介紹的文章就介紹到這了,更多相關Android shape標簽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Android OpenCv4 繪制多邊形的方法

    Android OpenCv4 繪制多邊形的方法

    最近在學習OpenCv,正好今天用Android做一個demo,本文主要介紹了Android OpenCv4 繪制多邊形的方法,感興趣的可以一起來了解一下
    2021-05-05
  • Android中使用ListView繪制自定義表格技巧分享

    Android中使用ListView繪制自定義表格技巧分享

    使用ListView繪制自定義的表格有朋友嘗試過沒有,下面為大家分享下要實現(xiàn)下圖的效果有幾個方面,參照著這幾點做了個簡單的實現(xiàn)不是問題好了,話不多說看代碼
    2013-06-06
  • Android開發(fā)之設置開機自動啟動的幾種方法

    Android開發(fā)之設置開機自動啟動的幾種方法

    這篇文章主要介紹了Android開發(fā)之設置開機自動啟動的幾種方法的相關資料,這里提供三種方法幫助大家實現(xiàn)這樣的功能,需要的朋友可以參考下
    2017-08-08
  • Android實現(xiàn)仿美團、順豐快遞數(shù)據(jù)加載效果

    Android實現(xiàn)仿美團、順豐快遞數(shù)據(jù)加載效果

    本片文章教給大家用Android實現(xiàn)美團和順豐快遞APP的數(shù)據(jù)加載的動畫效果,有興趣的朋友跟著學習嘗試下吧。
    2017-12-12
  • Android消息處理機制Looper和Handler詳解

    Android消息處理機制Looper和Handler詳解

    Android應用程序是通過消息來驅動的,系統(tǒng)為每一個應用程序維護一個消息隊例,應用程序的主線程不斷地從這個消息隊例中獲取消息(Looper),然后對這些消息進行處理(Handler),這樣就實現(xiàn)了通過消息來驅動應用程序的執(zhí)行,本文將詳細分析Android應用程序的消息處理機制
    2014-09-09
  • Android快遞物流信息布局開發(fā)

    Android快遞物流信息布局開發(fā)

    這篇文章主要為大家詳細介紹了Android快遞物流信息布局開發(fā),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Android實現(xiàn)創(chuàng)意LoadingView動畫效果

    Android實現(xiàn)創(chuàng)意LoadingView動畫效果

    這篇文章主要介紹了Android實現(xiàn)創(chuàng)意LoadingView動畫效果的相關資料,需要的朋友可以參考下
    2016-02-02
  • 通過實例解析android Activity啟動過程

    通過實例解析android Activity啟動過程

    這篇文章主要介紹了通過實例解析android Activity啟動過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-09-09
  • Android利用ViewPager實現(xiàn)可滑動放大縮小畫廊效果

    Android利用ViewPager實現(xiàn)可滑動放大縮小畫廊效果

    這篇文章主要介紹了Android利用ViewPager實現(xiàn)可滑動放大縮小畫廊效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • AndroidStudio4.0 New Class的坑(小結)

    AndroidStudio4.0 New Class的坑(小結)

    這篇文章主要介紹了AndroidStudio4.0 New Class的坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07

最新評論