Skip to content

Commit 22b7fc0

Browse files
MarinKacajmarink
andauthored
Fetch deferred drawable from attribute resolves #40 (#41)
* Fetch deferred drawable from attribute * Add drawable page to demo app Co-authored-by: marink <marink@backbase.com>
1 parent 33fa9ce commit 22b7fc0

10 files changed

Lines changed: 248 additions & 2 deletions

File tree

deferred-resources/api/deferred-resources.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ public abstract interface class com/backbase/deferredresources/DeferredDrawable
100100
public abstract fun resolve (Landroid/content/Context;)Landroid/graphics/drawable/Drawable;
101101
}
102102

103+
public final class com/backbase/deferredresources/DeferredDrawable$Attribute : com/backbase/deferredresources/DeferredDrawable {
104+
public fun <init> (I)V
105+
public fun <init> (ILkotlin/jvm/functions/Function2;)V
106+
public fun <init> (IZ)V
107+
public fun <init> (IZLkotlin/jvm/functions/Function2;)V
108+
public synthetic fun <init> (IZLkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
109+
public fun equals (Ljava/lang/Object;)Z
110+
public fun hashCode ()I
111+
public fun resolve (Landroid/content/Context;)Landroid/graphics/drawable/Drawable;
112+
public fun toString ()Ljava/lang/String;
113+
}
114+
103115
public final class com/backbase/deferredresources/DeferredDrawable$Constant : com/backbase/deferredresources/DeferredDrawable {
104116
public fun <init> (Landroid/graphics/drawable/Drawable;)V
105117
public fun equals (Ljava/lang/Object;)Z

deferred-resources/src/androidTest/java/com/backbase/deferredresources/DeferredDrawableJavaTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.graphics.drawable.Drawable;
44
import android.graphics.drawable.GradientDrawable;
5+
import androidx.core.util.Preconditions;
56
import com.backbase.deferredresources.test.R;
67
import kotlin.Unit;
78
import org.junit.Test;
@@ -21,6 +22,23 @@ public void resourceConstructor_withTransformationFunction_syntaxWorks() {
2122
Drawable resolved = deferred.resolve(TestContext.getContext());
2223
assertThat(resolved).isInstanceOf(GradientDrawable.class);
2324
GradientDrawable resolvedGradient = (GradientDrawable) resolved;
25+
Preconditions.checkNotNull(resolvedGradient);
26+
assertThat(DeferredDrawableTest.getGradientRadiusCompat(resolvedGradient)).isEqualTo(0.4f);
27+
}
28+
29+
@Test
30+
public void attributeConstructor_withTransformationFunction_syntaxWorks() {
31+
DeferredDrawable deferred =
32+
new DeferredDrawable.Attribute(android.R.attr.homeAsUpIndicator, (drawable, context) -> {
33+
GradientDrawable gradientDrawable = (GradientDrawable) drawable;
34+
gradientDrawable.setGradientRadius(0.4f);
35+
return Unit.INSTANCE;
36+
});
37+
38+
Drawable resolved = deferred.resolve(TestContext.AppCompatContext(true));
39+
assertThat(resolved).isInstanceOf(GradientDrawable.class);
40+
GradientDrawable resolvedGradient = (GradientDrawable) resolved;
41+
Preconditions.checkNotNull(resolvedGradient);
2442
assertThat(DeferredDrawableTest.getGradientRadiusCompat(resolvedGradient)).isEqualTo(0.4f);
2543
}
2644
}

deferred-resources/src/androidTest/java/com/backbase/deferredresources/DeferredDrawableTest.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,55 @@ internal class DeferredDrawableTest {
7878
assertThat(resolved.gradientRadiusCompat).isEqualTo(0.3f)
7979
}
8080

81+
@Test fun attribute_withMutateFalse_resolvesWithContext() {
82+
assumeFalse(
83+
"XML drawable does not have correct radius on API 21 and 22",
84+
Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22
85+
)
86+
87+
val deferred = DeferredDrawable.Attribute(android.R.attr.homeAsUpIndicator, mutate = false)
88+
89+
val resolved = deferred.resolve(AppCompatContext())
90+
assertThat(resolved).isInstanceOf(GradientDrawable::class.java)
91+
resolved as GradientDrawable
92+
assertThat(resolved.gradientRadiusCompat).isEqualTo(defaultOvalGradientFraction)
93+
94+
resolved.gradientRadius = 0.5f
95+
96+
// Since it's not mutated, transformations SHOULD apply to re-loaded instances:
97+
val loadedAgain = AppCompatResources.getDrawable(context, R.drawable.oval) as GradientDrawable
98+
assertThat(loadedAgain.gradientRadiusCompat).isEqualTo(0.5f)
99+
}
100+
101+
@Test fun attribute_withMutateTrue_resolvesWithContextAndMutates() {
102+
assumeFalse("XML drawable does not have correct radius on API 21", Build.VERSION.SDK_INT == 21)
103+
104+
val deferred = DeferredDrawable.Attribute(android.R.attr.homeAsUpIndicator)
105+
106+
val resolved = deferred.resolve(AppCompatContext())
107+
assertThat(resolved).isInstanceOf(GradientDrawable::class.java)
108+
resolved as GradientDrawable
109+
assertThat(resolved.gradientRadiusCompat).isEqualTo(defaultOvalGradientFraction)
110+
111+
resolved.gradientRadius = 0.4f
112+
113+
// Since it's mutated, transformations SHOULD NOT apply to re-loaded instances:
114+
val loadedAgain = AppCompatResources.getDrawable(context, R.drawable.oval) as GradientDrawable
115+
assertThat(loadedAgain.gradientRadiusCompat).isEqualTo(defaultOvalGradientFraction)
116+
}
117+
118+
@Test fun attribute_withTransformations_resolvesWithContextAndMutatesAndAppliesTransformation() {
119+
val deferred = DeferredDrawable.Attribute(android.R.attr.homeAsUpIndicator) {
120+
require(this is GradientDrawable)
121+
gradientRadius = 0.3f
122+
}
123+
124+
val resolved = deferred.resolve(AppCompatContext())
125+
assertThat(resolved).isInstanceOf(GradientDrawable::class.java)
126+
resolved as GradientDrawable
127+
assertThat(resolved.gradientRadiusCompat).isEqualTo(0.3f)
128+
}
129+
81130
internal companion object {
82131

83132
@JvmStatic internal val GradientDrawable.gradientRadiusCompat: Float

deferred-resources/src/androidTest/res/values/test_theme.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
<item name="colorPrimary">#987654</item>
55
<item name="titleTextColor">@color/stateful_color_with_attr</item>
66
<item name="subtitleTextColor">@color/stateful_color_without_attr</item>
7+
<item name="android:homeAsUpIndicator">@drawable/oval</item>
78
</style>
89

910
<style name="TestTheme.Light" parent="Theme.AppCompat.Light">
1011
<item name="colorPrimary">#987654</item>
1112
<item name="titleTextColor">@color/stateful_color_with_attr</item>
1213
<item name="subtitleTextColor">@color/stateful_color_without_attr</item>
14+
<item name="android:homeAsUpIndicator">@drawable/oval</item>
1315
</style>
1416
</resources>

deferred-resources/src/main/java/com/backbase/deferredresources/DeferredDrawable.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package com.backbase.deferredresources
22

33
import android.content.Context
44
import android.graphics.drawable.Drawable
5+
import android.util.TypedValue
6+
import androidx.annotation.AttrRes
57
import androidx.annotation.DrawableRes
68
import androidx.appcompat.content.res.AppCompatResources
9+
import com.backbase.deferredresources.internal.resolveAttribute
710
import dev.drewhamilton.extracare.DataApi
811

912
/**
@@ -58,4 +61,47 @@ public interface DeferredDrawable {
5861
return drawable?.apply { transformations(context) }
5962
}
6063
}
64+
65+
/**
66+
* A wrapper for a [Drawable] [resId]. Optionally [mutate]s each resolved Drawable. Optionally provide
67+
* [transformations] (such as [Drawable.setTint]) to apply each time the Drawable is resolved.
68+
*
69+
* If [transformations] are supplied, [mutate] should be true.
70+
*/
71+
@DataApi public class Attribute @JvmOverloads constructor(
72+
@AttrRes private val resId: Int,
73+
private val mutate: Boolean = true,
74+
private val transformations: Drawable.(Context) -> Unit = {}
75+
) : DeferredDrawable {
76+
77+
private val drawableTypeValue = TypedValue()
78+
79+
/**
80+
* Convenience constructor that sets [mutate] to true when [transformations] are supplied.
81+
*/
82+
public constructor(
83+
@AttrRes resId: Int,
84+
transformations: Drawable.(Context) -> Unit
85+
) : this(resId, mutate = true, transformations = transformations)
86+
87+
/**
88+
* Resolve [resId] to a [Drawable] with the given [context]. If [mutate] is true, returns the result of
89+
* [Drawable.mutate] instead of the original Drawable. Applies [transformations] before returning.
90+
*/
91+
override fun resolve(context: Context): Drawable? {
92+
val original = AppCompatResources.getDrawable(context, context.resolveAttribute(
93+
resId = resId,
94+
attributeTypeName = "drawable resource id",
95+
reusedTypedValue = drawableTypeValue,
96+
expectedTypes = intArrayOf(
97+
TypedValue.TYPE_REFERENCE, // could be a direct reference to a drawable resource by id
98+
TypedValue.TYPE_STRING // could be initially interpreted a string, e.g. "res/drawable/oval.xml"
99+
)
100+
) {
101+
resourceId
102+
})
103+
val drawable = if (mutate) original?.mutate() else original
104+
return drawable?.apply { transformations(context) }
105+
}
106+
}
61107
}

demo/src/main/java/com/backbase/deferredresources/demo/DeferredResourceViews.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import androidx.core.view.setMargins
1313
import androidx.core.view.setPadding
1414
import androidx.core.widget.TextViewCompat
1515
import com.backbase.deferredresources.DeferredColor
16+
import com.backbase.deferredresources.DeferredDrawable
1617
import com.backbase.deferredresources.DeferredFormattedPlurals
1718
import com.backbase.deferredresources.DeferredPlurals
1819
import com.backbase.deferredresources.DeferredText
1920
import com.backbase.deferredresources.demo.databinding.ColorsBinding
21+
import com.backbase.deferredresources.demo.databinding.DrawablesBinding
2022
import com.backbase.deferredresources.demo.databinding.PluralsBinding
2123
import com.backbase.deferredresources.viewx.setText
2224
import com.google.android.material.textview.MaterialTextView
@@ -83,3 +85,22 @@ class DeferredPluralsView(context: Context) : DeferredResourceView(context) {
8385
other.text = plurals.resolve(context, 100)
8486
}
8587
}
88+
89+
class DeferredDrawablesView(context: Context) : DeferredResourceView(context) {
90+
private val binding = DrawablesBinding.inflate(LayoutInflater.from(context), this)
91+
92+
fun display(deferredDrawable: DeferredDrawable.Constant, text: DeferredText) {
93+
binding.constantLabel.text = text.resolve(context)
94+
binding.constant.setImageDrawable(deferredDrawable.resolve(context))
95+
}
96+
97+
fun display(deferredDrawable: DeferredDrawable.Resource, text: DeferredText) {
98+
binding.resourceLabel.text = text.resolve(context)
99+
binding.resource.setImageDrawable(deferredDrawable.resolve(context))
100+
}
101+
102+
fun display(deferredDrawable: DeferredDrawable.Attribute, text: DeferredText) {
103+
binding.attributeLabel.text = text.resolve(context)
104+
binding.attribute.setImageDrawable(deferredDrawable.resolve(context))
105+
}
106+
}

demo/src/main/java/com/backbase/deferredresources/demo/DemoPagerAdapter.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,50 @@
11
package com.backbase.deferredresources.demo
22

33
import android.graphics.Color
4+
import android.graphics.drawable.GradientDrawable
45
import android.view.ViewGroup
56
import android.widget.LinearLayout
67
import androidx.recyclerview.widget.RecyclerView
78
import com.backbase.deferredresources.DeferredColor
9+
import com.backbase.deferredresources.DeferredDrawable
810
import com.backbase.deferredresources.DeferredFormattedPlurals
911
import com.backbase.deferredresources.DeferredText
1012

1113
class DemoPagerAdapter : RecyclerView.Adapter<DemoPagerAdapter.DeferredResourceViewHolder>() {
1214

1315
private val formattedPluralsResource = DeferredFormattedPlurals.Resource(R.plurals.horses)
16+
private val oval by lazy {
17+
GradientDrawable(
18+
GradientDrawable.Orientation.BL_TR,
19+
intArrayOf(Color.parseColor("#00ff00"), Color.parseColor("#000fff"))
20+
).apply {
21+
gradientType = GradientDrawable.RADIAL_GRADIENT
22+
cornerRadius = 0.8f
23+
shape = GradientDrawable.OVAL
24+
}
25+
}
1426

1527
fun getPageName(position: Int): CharSequence = when (position) {
1628
0 -> "Colors"
1729
1 -> "Plurals resource"
30+
2 -> "Drawables"
1831
else -> throw IllegalArgumentException("Position $position in adapter with size $itemCount")
1932
}
2033

21-
override fun getItemCount(): Int = 2
34+
override fun getItemCount(): Int = ViewType.values().size
2235

2336
override fun getItemViewType(position: Int): Int = when (position) {
2437
0 -> ViewType.COLORS.ordinal
2538
1 -> ViewType.PLURALS.ordinal
39+
2 -> ViewType.DRAWABLES.ordinal
2640
else -> throw IndexOutOfBoundsException("Position $position in adapter with size $itemCount")
2741
}
2842

2943
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeferredResourceViewHolder {
3044
val view = when (ViewType.values()[viewType]) {
3145
ViewType.COLORS -> DeferredColorsView(parent.context)
3246
ViewType.PLURALS -> DeferredPluralsView(parent.context)
47+
ViewType.DRAWABLES -> DeferredDrawablesView(parent.context)
3348
}.apply {
3449
layoutParams = LinearLayout.LayoutParams(
3550
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -58,6 +73,14 @@ class DemoPagerAdapter : RecyclerView.Adapter<DemoPagerAdapter.DeferredResourceV
5873
is DeferredPluralsView -> when (position) {
5974
1 -> view.display(formattedPluralsResource)
6075
}
76+
is DeferredDrawablesView -> {
77+
view.display(DeferredDrawable.Constant(oval), DeferredText.Constant("Constant: "))
78+
view.display(DeferredDrawable.Resource(R.drawable.oval), DeferredText.Constant("Resource: "))
79+
view.display(
80+
DeferredDrawable.Attribute(android.R.attr.homeAsUpIndicator),
81+
DeferredText.Constant("Attribute: ")
82+
)
83+
}
6184
}
6285
}
6386

@@ -66,6 +89,6 @@ class DemoPagerAdapter : RecyclerView.Adapter<DemoPagerAdapter.DeferredResourceV
6689
) : RecyclerView.ViewHolder(root)
6790

6891
private enum class ViewType {
69-
COLORS, PLURALS
92+
COLORS, PLURALS, DRAWABLES
7093
}
7194
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--Used in a test that doesn't call mutate()-->
2+
<shape
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:shape="oval">
5+
6+
<gradient
7+
android:type="radial"
8+
android:gradientRadius="0.8"
9+
android:startColor="#00ff00"
10+
android:endColor="#000fff" />
11+
12+
</shape>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<merge
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
6+
tools:layout_height="match_parent"
7+
tools:layout_width="match_parent"
8+
9+
tools:parentTag="android.widget.ScrollView">
10+
11+
<LinearLayout
12+
android:id="@+id/container"
13+
android:layout_width="match_parent"
14+
android:layout_height="wrap_content"
15+
16+
android:clipToPadding="false"
17+
android:orientation="vertical"
18+
android:padding="8dp">
19+
20+
<com.google.android.material.textview.MaterialTextView
21+
android:id="@+id/constantLabel"
22+
android:labelFor="@id/constant"
23+
android:layout_width="wrap_content"
24+
android:layout_height="wrap_content" />
25+
26+
<ImageView
27+
android:id="@+id/constant"
28+
android:layout_width="40dp"
29+
android:layout_height="40dp"
30+
android:layout_gravity="center"
31+
tools:ignore="ContentDescription" />
32+
33+
<com.google.android.material.textview.MaterialTextView
34+
android:id="@+id/resourceLabel"
35+
android:labelFor="@id/resource"
36+
android:layout_width="wrap_content"
37+
android:layout_height="wrap_content" />
38+
39+
<ImageView
40+
android:id="@+id/resource"
41+
android:layout_width="40dp"
42+
android:layout_height="40dp"
43+
android:layout_gravity="center"
44+
tools:ignore="ContentDescription" />
45+
46+
<com.google.android.material.textview.MaterialTextView
47+
android:id="@+id/attributeLabel"
48+
android:labelFor="@id/attribute"
49+
android:layout_width="wrap_content"
50+
android:layout_height="wrap_content" />
51+
52+
<ImageView
53+
android:id="@+id/attribute"
54+
android:layout_width="40dp"
55+
android:layout_height="40dp"
56+
android:layout_gravity="center"
57+
tools:ignore="ContentDescription" />
58+
</LinearLayout>
59+
60+
</merge>

demo/src/main/res/values/styles.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
<!--Remap legacy AppCompat attributes to MaterialComponent attributes-->
2222
<item name="colorPrimaryDark">?colorPrimaryVariant</item>
2323
<item name="colorAccent">?colorSecondary</item>
24+
25+
<item name="android:homeAsUpIndicator">@drawable/oval</item>
26+
<item name="homeAsUpIndicator">@drawable/oval</item>
2427
</style>
2528

2629
</resources>

0 commit comments

Comments
 (0)