1 /*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.ui.res
18
19 import android.content.res.Configuration
20 import android.content.res.Resources
21 import android.content.res.XmlResourceParser
22 import android.util.TypedValue
23 import android.util.Xml
24 import androidx.annotation.DrawableRes
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.remember
27 import androidx.compose.ui.graphics.vector.ImageVector
28 import androidx.compose.ui.graphics.vector.compat.AndroidVectorParser
29 import androidx.compose.ui.graphics.vector.compat.createVectorImageBuilder
30 import androidx.compose.ui.graphics.vector.compat.isAtEnd
31 import androidx.compose.ui.graphics.vector.compat.parseCurrentVectorNode
32 import androidx.compose.ui.graphics.vector.compat.seekToStartTag
33 import androidx.compose.ui.platform.LocalContext
34 import androidx.compose.ui.platform.LocalResources
35 import java.lang.ref.WeakReference
36 import org.xmlpull.v1.XmlPullParserException
37
38 /**
39 * Load an ImageVector from a vector resource.
40 *
41 * This function is intended to be used for when low-level ImageVector-specific functionality is
42 * required. For simply displaying onscreen, the vector/bitmap-agnostic [painterResource] is
43 * recommended instead.
44 *
45 * @param id the resource identifier
46 * @return the vector data associated with the resource
47 */
48 @Composable
ImageVectornull49 fun ImageVector.Companion.vectorResource(@DrawableRes id: Int): ImageVector {
50 val context = LocalContext.current
51 val res = LocalResources.current
52 val theme = context.theme
53
54 return remember(id, res, theme, res.configuration) { vectorResource(theme, res, id) }
55 }
56
57 @Throws(XmlPullParserException::class)
ImageVectornull58 fun ImageVector.Companion.vectorResource(
59 theme: Resources.Theme? = null,
60 res: Resources,
61 resId: Int
62 ): ImageVector {
63 val value = TypedValue()
64 res.getValue(resId, value, true)
65
66 return loadVectorResourceInner(
67 theme,
68 res,
69 res.getXml(resId).apply { seekToStartTag() },
70 value.changingConfigurations
71 )
72 .imageVector
73 }
74
75 /**
76 * Helper method that parses a vector asset from the given [XmlResourceParser] position. This method
77 * assumes the parser is already been positioned to the start tag
78 */
79 @Throws(XmlPullParserException::class)
loadVectorResourceInnernull80 internal fun loadVectorResourceInner(
81 theme: Resources.Theme? = null,
82 res: Resources,
83 parser: XmlResourceParser,
84 changingConfigurations: Int
85 ): ImageVectorCache.ImageVectorEntry {
86 val attrs = Xml.asAttributeSet(parser)
87 val resourceParser = AndroidVectorParser(parser)
88 val builder = resourceParser.createVectorImageBuilder(res, theme, attrs)
89
90 var nestedGroups = 0
91 while (!parser.isAtEnd()) {
92 nestedGroups =
93 resourceParser.parseCurrentVectorNode(res, attrs, theme, builder, nestedGroups)
94 parser.next()
95 }
96 return ImageVectorCache.ImageVectorEntry(builder.build(), changingConfigurations)
97 }
98
99 /**
100 * Object responsible for caching [ImageVector] instances based on the given theme and drawable
101 * resource identifier
102 */
103 internal class ImageVectorCache {
104
105 /** Key that binds the corresponding theme with the resource identifier for the vector asset */
106 data class Key(val theme: Resources.Theme, val id: Int)
107
108 /**
109 * Tuple that contains the [ImageVector] as well as the corresponding configuration flags that
110 * the [ImageVector] depends on. That is if there is a configuration change that updates the
111 * parameters in the flag, this vector should be regenerated from the current configuration
112 */
113 data class ImageVectorEntry(val imageVector: ImageVector, val configFlags: Int)
114
115 private val map = HashMap<Key, WeakReference<ImageVectorEntry>>()
116
getnull117 operator fun get(key: Key): ImageVectorEntry? = map[key]?.get()
118
119 fun prune(configChanges: Int) {
120 val it = map.entries.iterator()
121 while (it.hasNext()) {
122 val entry = it.next()
123 val imageVectorEntry = entry.value.get()
124 if (
125 imageVectorEntry == null ||
126 Configuration.needNewResources(configChanges, imageVectorEntry.configFlags)
127 ) {
128 it.remove()
129 }
130 }
131 }
132
setnull133 operator fun set(key: Key, imageVectorEntry: ImageVectorEntry) {
134 map[key] = WeakReference<ImageVectorEntry>(imageVectorEntry)
135 }
136
clearnull137 fun clear() {
138 map.clear()
139 }
140 }
141