欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android加載View中Background詳解

 更新時(shí)間:2015年03月03日 08:53:59   投稿:hebedich  
本文講解的是Android什么時(shí)候進(jìn)行View中Background的加載,十分的詳盡,十分全面細(xì)致,附上所有代碼,這里推薦給大家,希望大家能夠喜歡。

對(duì)大多數(shù)Android的開(kāi)發(fā)者來(lái)說(shuō),最經(jīng)常的操作莫過(guò)于對(duì)界面進(jìn)行布局,View中背景圖片的加載是最經(jīng)常做的。但是我們很少關(guān)注這個(gè)過(guò)程,這篇文章主要解析view中背景圖片加載的流程。了解view中背景圖片的加載(資源的加載)可以讓我們對(duì)資源加載的過(guò)程進(jìn)行一些優(yōu)化,另外當(dāng)需要進(jìn)行整個(gè)應(yīng)用的換膚時(shí),也可以更得心應(yīng)手。

View圖片的加載,我們最常見(jiàn)的就是通過(guò)在XML文件當(dāng)中進(jìn)行drawable的設(shè)置,然后讓Android系統(tǒng)幫我們完成,或者手動(dòng)寫(xiě)代碼加載成Bitmap,然后加載到View上。這篇文章主要分析Android在什么時(shí)候以及怎么幫我們完成背景圖片的加載的,那么我們就從Activity.setContentView還是LayoutInflater.inflate(...)方法開(kāi)始分析。

不管是從Activity.setContentView(...)還是LayoutInflater.inflate(...)方法進(jìn)行View的初始化,最終都會(huì)到達(dá)LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)這個(gè)方法中。在這里我們主要關(guān)注View的背景圖片加載,對(duì)于XML如何解析和加載就放過(guò)了。

復(fù)制代碼 代碼如下:

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;
            View result = root;
            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                final String name = parser.getName();
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp;
                    if (TAG_1995.equals(name)) {
                        temp = new BlinkLayout(mContext, attrs);
                    } else {
                        temp = createViewFromTag(root, name, attrs);
                    }
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                     // Inflate all children under temp
                    rInflate(parser, temp, attrs, true);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                        + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
            return result;
        }
    }
上面這么長(zhǎng)一串代碼,其實(shí)思路很清晰,就是針對(duì)XML文件進(jìn)行解析,然后根據(jù)XML解析出的每一個(gè)節(jié)點(diǎn)進(jìn)行View的初始化,緊接著將View的Layout參數(shù)設(shè)置到View上,然后將View添加到它的父控件上。
為了了解View是怎么被加載出來(lái)的,我們只需要了解
 temp = createViewFromTag(root, name, attrs);
跟進(jìn)去看看。
    /*
     * default visibility so the BridgeInflater can override it.
     */
    View createViewFromTag(View parent, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
        if (DEBUG) System.out.println("******** Creating view: " + name);
        try {
            View view;
            if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
            else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
            else view = null;
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
            }
            if (view == null) {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            }
            if (DEBUG) System.out.println("Created view is: " + view);
            return view;
        } catch (InflateException e) {
            throw e;
        } catch (ClassNotFoundException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
    }

上面代碼的重點(diǎn)在于try...Catch里的內(nèi)容。try包起來(lái)的東西就是對(duì)View進(jìn)行初始化,注意到上面代碼中有幾個(gè)Factory,這些Factory可以在View進(jìn)行初始化,也就是說(shuō)其實(shí)我們可以在這里干預(yù)View的初始化。從上面代碼我們可以知道,如果我們自定義了一個(gè)Factory,那么當(dāng)前要初始化的View會(huì)優(yōu)先被我們自定義的Factory初始化,而不通過(guò)系統(tǒng)默認(rèn)的Factory初始化。那么如果我們要自定義Factory,應(yīng)該在哪里定義呢?容易想到,F(xiàn)actory必須要趕在資源加載前自定義完成,所以我們應(yīng)該在onCreate(...)的this.setContentView(...)之前設(shè)置LayoutInflater.Factory。

  getLayoutInflater().setFactory(factory);
接下來(lái)我們看到上面函數(shù)里面的

復(fù)制代碼 代碼如下:

  if (-1 == name.indexOf('.')) {
        view = onCreateView(parent, name, attrs);
    } else {
        view = createView(name, null, attrs);
    }

這段函數(shù)就是對(duì)View進(jìn)行初始化,有兩種情況,一種是系統(tǒng)自帶的View,它在

 if (-1 == name.indexOf('.'))
這里面進(jìn)行初始化,因?yàn)槿绻窍到y(tǒng)自帶的View,傳入的那么一般不帶系統(tǒng)的前綴"android.view."。另一個(gè)分支初始化的是我們自定義的View。我們跟進(jìn)onCreateView看看。

復(fù)制代碼 代碼如下:

  protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;
        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // always use ourselves when inflating ViewStub later
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(this);
            }
            return view;
        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        }
    }

從onCreateView(...)中我們知道,其實(shí)createViewFromTag(...)中對(duì)View的初始化最終都是通過(guò)createView(...)這個(gè)函數(shù)進(jìn)行初始化的,不同只在于系統(tǒng)控件需要通過(guò)onCreateView(...)加上前綴,以便類(lèi)加載器(ClassLoader)正確地通過(guò)類(lèi)所在的包初始化這個(gè)類(lèi)。createView(...)這個(gè)函數(shù)的思路很清晰,不看catch里面的內(nèi)容,try里面開(kāi)頭的兩個(gè)分支就是用來(lái)將所要用的類(lèi)構(gòu)造函數(shù)提取出來(lái),Android系統(tǒng)會(huì)對(duì)使用過(guò)的類(lèi)構(gòu)造函數(shù)進(jìn)行緩存,因?yàn)橄馮extView這些常用的控件可能會(huì)被使用很多次。接下來(lái),就是通過(guò)類(lèi)構(gòu)造函數(shù)對(duì)View進(jìn)行初始化了。我們注意到傳入構(gòu)造函數(shù)的mConstructorArgs是一個(gè)包含兩個(gè)元素的數(shù)組。

 final Object[] mConstructorArgs = new Object[2];
那么我們就很清楚了,它就是調(diào)用系統(tǒng)控件中對(duì)應(yīng)兩個(gè)參數(shù)的構(gòu)造函數(shù)。為了方便,我們就從最基礎(chǔ)的View進(jìn)行分析。

復(fù)制代碼 代碼如下:

  public View(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public View(Context context, AttributeSet attrs, int defStyle) {
     this(context);
     TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
             defStyle, 0);
     Drawable background = null;
     int leftPadding = -1;
     int topPadding = -1;
     int rightPadding = -1;
     int bottomPadding = -1;
     int startPadding = UNDEFINED_PADDING;
     int endPadding = UNDEFINED_PADDING;
     int padding = -1;
     int viewFlagValues = 0;
     int viewFlagMasks = 0;
     boolean setScrollContainer = false;
     int x = 0;
     int y = 0;
     float tx = 0;
     float ty = 0;
     float rotation = 0;
     float rotationX = 0;
     float rotationY = 0;
     float sx = 1f;
     float sy = 1f;
     boolean transformSet = false;
     int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
     int overScrollMode = mOverScrollMode;
     boolean initializeScrollbars = false;
     boolean leftPaddingDefined = false;
     boolean rightPaddingDefined = false;
     boolean startPaddingDefined = false;
     boolean endPaddingDefined = false;
     final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
     final int N = a.getIndexCount();
      for (int i = 0; i < N; i++) {
          int attr = a.getIndex(i);
          switch (attr) {
              case com.android.internal.R.styleable.View_background:
                  background = a.getDrawable(attr);
                  break;
              case com.android.internal.R.styleable.View_padding:
                  padding = a.getDimensionPixelSize(attr, -1);
                  mUserPaddingLeftInitial = padding;
                  mUserPaddingRightInitial = padding;
                  leftPaddingDefined = true;
                  rightPaddingDefined = true;
                  break;
   //省略一大串無(wú)關(guān)的函數(shù)
 }

由于我們只關(guān)注View中的背景圖是怎么加載的,注意這個(gè)函數(shù)其實(shí)就是遍歷AttributeSet attrs這個(gè)東西,然后對(duì)View的各個(gè)屬性進(jìn)行初始化。我們直接進(jìn)入

 background = a.getDrawable(attr);
這里看看(TypedArray.getDrawable)。

復(fù)制代碼 代碼如下:

    public Drawable getDrawable(int index) {
        final TypedValue value = mValue;
        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
            if (false) {
                System.out.println("******************************************************************");
                System.out.println("Got drawable resource: type="
                                   + value.type
                                   + " str=" + value.string
                                   + " int=0x" + Integer.toHexString(value.data)
                                   + " cookie=" + value.assetCookie);
                System.out.println("******************************************************************");
            }
            return mResources.loadDrawable(value, value.resourceId);
        }
        return null;
    }

我們發(fā)現(xiàn)它調(diào)用mResources.loadDrawable(...),進(jìn)去看看。

復(fù)制代碼 代碼如下:

    /*package*/ Drawable loadDrawable(TypedValue value, int id)
            throws NotFoundException {
        if (TRACE_FOR_PRELOAD) {
            // Log only framework resources
            if ((id >>> 24) == 0x1) {
                final String name = getResourceName(id);
                if (name != null) android.util.Log.d("PreloadDrawable", name);
            }
        }
        boolean isColorDrawable = false;
        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
                value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
            isColorDrawable = true;
        }
        final long key = isColorDrawable ? value.data :
                (((long) value.assetCookie) << 32) | value.data;
        Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
        if (dr != null) {
            return dr;
        }
        Drawable.ConstantState cs = isColorDrawable
                ? sPreloadedColorDrawables.get(key)
                : (sPreloadedDensity == mConfiguration.densityDpi
                        ? sPreloadedDrawables.get(key) : null);
        if (cs != null) {
            dr = cs.newDrawable(this);
        } else {
            if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            }
            if (dr == null) {
                if (value.string == null) {
                    throw new NotFoundException(
                            "Resource is not a Drawable (color or path): " + value);
                }
                String file = value.string.toString();
                if (TRACE_FOR_MISS_PRELOAD) {
                    // Log only framework resources
                    if ((id >>> 24) == 0x1) {
                        final String name = getResourceName(id);
                        if (name != null) android.util.Log.d(TAG, "Loading framework drawable #"
                                + Integer.toHexString(id) + ": " + name
                                + " at " + file);
                    }
                }
                if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
                        + value.assetCookie + ": " + file);
                if (file.endsWith(".xml")) {
                    try {
                        XmlResourceParser rp = loadXmlResourceParser(
                                file, id, value.assetCookie, "drawable");
                        dr = Drawable.createFromXml(this, rp);
                        rp.close();
                    } catch (Exception e) {
                        NotFoundException rnf = new NotFoundException(
                            "File " + file + " from drawable resource ID #0x"
                            + Integer.toHexString(id));
                        rnf.initCause(e);
                        throw rnf;
                    }
                } else {
                    try {
                        InputStream is = mAssets.openNonAsset(
                                value.assetCookie, file, AssetManager.ACCESS_STREAMING);
        //                System.out.println("Opened file " + file + ": " + is);
                        dr = Drawable.createFromResourceStream(this, value, is,
                                file, null);
                        is.close();
        //                System.out.println("Created stream: " + dr);
                    } catch (Exception e) {
                        NotFoundException rnf = new NotFoundException(
                            "File " + file + " from drawable resource ID #0x"
                            + Integer.toHexString(id));
                        rnf.initCause(e);
                        throw rnf;
                    }
                }
            }
        }
        if (dr != null) {
            dr.setChangingConfigurations(value.changingConfigurations);
            cs = dr.getConstantState();
            if (cs != null) {
                if (mPreloading) {
                    if (verifyPreloadConfig(value, "drawable")) {
                        if (isColorDrawable) {
                            sPreloadedColorDrawables.put(key, cs);
                        } else {
                            sPreloadedDrawables.put(key, cs);
                        }
                    }
                } else {
                    synchronized (mTmpValue) {
                        //Log.i(TAG, "Saving cached drawable @ #" +
                        //        Integer.toHexString(key.intValue())
                        //        + " in " + this + ": " + cs);
                        if (isColorDrawable) {
                            mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                        } else {
                            mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                        }
                    }
                }
            }
        }
        return dr;
    }

就是這個(gè)函數(shù)了,所有View的背景的加載都在這里了。這個(gè)函數(shù)的邏輯就比較復(fù)雜了,大體說(shuō)來(lái)就是根據(jù)背景的類(lèi)型(純顏色、定義在XML文件中的,或者是一張靜態(tài)的背景),如果緩存里面有,就直接用緩存里的。

總結(jié)一下,經(jīng)過(guò)上面的分析,我們知道了,Android就是在Activity.setContentView(...)中為我們進(jìn)行資源文件的加載,精確到具體的函數(shù)的話(huà),資源文件的加載就是在每一個(gè)被初始化的View的構(gòu)造函數(shù)中進(jìn)行加載的。

以上就是本文的全部?jī)?nèi)容了,希望對(duì)大家能夠有所幫助。

相關(guān)文章

  • Android小程序?qū)崿F(xiàn)音樂(lè)播放列表

    Android小程序?qū)崿F(xiàn)音樂(lè)播放列表

    這篇文章主要為大家詳細(xì)介紹了Android小程序?qū)崿F(xiàn)音樂(lè)播放列表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • 接口對(duì)象的實(shí)例化在接口回調(diào)中的使用方法

    接口對(duì)象的實(shí)例化在接口回調(diào)中的使用方法

    下面小編就為大家?guī)?lái)一篇接口對(duì)象的實(shí)例化在接口回調(diào)中的使用方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • Android(2.2/2.3系統(tǒng))Gallery解決默認(rèn)和橫豎屏切換選中狀態(tài)問(wèn)題

    Android(2.2/2.3系統(tǒng))Gallery解決默認(rèn)和橫豎屏切換選中狀態(tài)問(wèn)題

    本文主要介紹Android Gallery,在Android開(kāi)發(fā)過(guò)程中肯定會(huì)遇到版本不同,在開(kāi)發(fā)過(guò)程中需要調(diào)整的,這里針對(duì)Android(2.2/2.3系統(tǒng)版本) Gallery解決默認(rèn)和橫豎屏切換選中狀態(tài)問(wèn)題
    2016-07-07
  • Android開(kāi)發(fā)之ListView列表刷新和加載更多實(shí)現(xiàn)方法

    Android開(kāi)發(fā)之ListView列表刷新和加載更多實(shí)現(xiàn)方法

    這篇文章主要介紹了Android開(kāi)發(fā)之ListView列表刷新和加載更多實(shí)現(xiàn)方法,實(shí)例分析了ListView列表操作的相關(guān)技巧,需要的朋友可以參考下
    2015-06-06
  • Android實(shí)現(xiàn)圖片在屏幕內(nèi)縮放和移動(dòng)效果

    Android實(shí)現(xiàn)圖片在屏幕內(nèi)縮放和移動(dòng)效果

    這篇文章主要為大家詳細(xì)介紹了Android控制圖片在屏幕內(nèi)縮放和移動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-02-02
  • 淺析Android手機(jī)衛(wèi)士關(guān)閉自動(dòng)更新

    淺析Android手機(jī)衛(wèi)士關(guān)閉自動(dòng)更新

    保存數(shù)據(jù)的四種方式,網(wǎng)絡(luò),廣播提供者,SharedPreferences,數(shù)據(jù)庫(kù)。接下來(lái)通過(guò)本文給大家介紹android手機(jī)衛(wèi)士關(guān)閉自動(dòng)更新的相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧
    2016-04-04
  • Android實(shí)現(xiàn)下載m3u8視頻文件問(wèn)題解決

    Android實(shí)現(xiàn)下載m3u8視頻文件問(wèn)題解決

    這篇文章主要介紹了Android實(shí)現(xiàn)下載m3u8視頻文件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2023-01-01
  • Android實(shí)現(xiàn)老虎機(jī)小游戲代碼示例

    Android實(shí)現(xiàn)老虎機(jī)小游戲代碼示例

    大家好,本篇文章主要講的是Android實(shí)現(xiàn)老虎機(jī)小游戲代碼示例,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽
    2021-12-12
  • 淺析安卓(Android)的性能優(yōu)化

    淺析安卓(Android)的性能優(yōu)化

    性能優(yōu)化是一個(gè)大的范疇,如果有人問(wèn)你在Android中如何做性能優(yōu)化的,也許都不知道從哪開(kāi)始說(shuō)起。那么這篇文章我們從布局優(yōu)化和內(nèi)存優(yōu)化兩個(gè)方面來(lái)展開(kāi)說(shuō)如何進(jìn)行Android的性能優(yōu)化。
    2016-08-08
  • Android三種方式生成矢量圖之VectorDrawable類(lèi)使用詳解

    Android三種方式生成矢量圖之VectorDrawable類(lèi)使用詳解

    這篇文章主要介紹了Android三種方式生成矢量圖的VectorDrawable類(lèi),2014年6月26日的I/O?2014開(kāi)發(fā)者大會(huì)上谷歌正式推出了Android?L,它帶來(lái)了全新的設(shè)計(jì)語(yǔ)言Material?Design,新的API也提供了這個(gè)類(lèi)VectorDrawable
    2023-02-02

最新評(píng)論