Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing parts of drawable on Android

I'm preparing drawables for my application. I've got a lot of radiobuttons, which are being displayed as images with optional frame (when checked). One drawable looks like the following:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <layer-list>
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="@color/colorPrimary" />
                </shape>
            </item>

            <item>
                <inset android:insetTop="@dimen/selectionBorderSize"
                    android:insetLeft="@dimen/selectionBorderSize"
                    android:insetRight="@dimen/selectionBorderSize"
                    android:insetBottom="@dimen/selectionBorderSize">
                    <layer-list>
                        <item>
                            <shape android:shape="rectangle">
                                <solid android:color="#ffffff"></solid>
                            </shape>
                        </item>
                        <item>
                            <bitmap android:src="@drawable/sharp7" >
                                <padding android:bottom="@dimen/selectionBorderSize"
                                    android:top="@dimen/selectionBorderSize"
                                    android:left="@dimen/selectionBorderSize"
                                    android:right="@dimen/selectionBorderSize" />
                            </bitmap>
                        </item>
                    </layer-list>
                </inset>
            </item>
        </layer-list>
    </item>

    <item android:state_checked="false">
        <layer-list>
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="#ffffff" />
                </shape>
            </item>

            <item>
                <inset android:insetTop="@dimen/selectionBorderSize"
                    android:insetLeft="@dimen/selectionBorderSize"
                    android:insetRight="@dimen/selectionBorderSize"
                    android:insetBottom="@dimen/selectionBorderSize">
                    <layer-list>
                        <item>
                            <shape android:shape="rectangle">
                                <solid android:color="#ffffff"></solid>
                            </shape>
                        </item>
                        <item>
                            <bitmap android:src="@drawable/sharp7" >
                                <padding android:bottom="@dimen/selectionBorderSize"
                                    android:top="@dimen/selectionBorderSize"
                                    android:left="@dimen/selectionBorderSize"
                                    android:right="@dimen/selectionBorderSize" />
                            </bitmap>
                        </item>
                    </layer-list>
                </inset>
            </item>
        </layer-list>
    </item>

    <item>
        <shape android:shape="rectangle">
            <solid android:color="#ffffff" />
        </shape>
    </item>
</selector>

Another one:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <layer-list>
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="@color/colorPrimary" />
                </shape>
            </item>

            <item>
                <inset android:insetTop="@dimen/selectionBorderSize"
                    android:insetLeft="@dimen/selectionBorderSize"
                    android:insetRight="@dimen/selectionBorderSize"
                    android:insetBottom="@dimen/selectionBorderSize">
                    <layer-list>
                        <item>
                            <shape android:shape="rectangle">
                                <solid android:color="#ffffff"></solid>
                            </shape>
                        </item>
                        <item>
                            <bitmap android:src="@drawable/sharp6" >
                                <padding android:bottom="@dimen/selectionBorderSize"
                                    android:top="@dimen/selectionBorderSize"
                                    android:left="@dimen/selectionBorderSize"
                                    android:right="@dimen/selectionBorderSize" />
                            </bitmap>
                        </item>
                    </layer-list>
                </inset>
            </item>
        </layer-list>
    </item>

    <item android:state_checked="false">
        <layer-list>
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="#ffffff" />
                </shape>
            </item>

            <item>
                <inset android:insetTop="@dimen/selectionBorderSize"
                    android:insetLeft="@dimen/selectionBorderSize"
                    android:insetRight="@dimen/selectionBorderSize"
                    android:insetBottom="@dimen/selectionBorderSize">
                    <layer-list>
                        <item>
                            <shape android:shape="rectangle">
                                <solid android:color="#ffffff"></solid>
                            </shape>
                        </item>
                        <item>
                            <bitmap android:src="@drawable/sharp6" >
                                <padding android:bottom="@dimen/selectionBorderSize"
                                    android:top="@dimen/selectionBorderSize"
                                    android:left="@dimen/selectionBorderSize"
                                    android:right="@dimen/selectionBorderSize" />
                            </bitmap>
                        </item>
                    </layer-list>
                </inset>
            </item>
        </layer-list>
    </item>

    <item>
        <shape android:shape="rectangle">
            <solid android:color="#ffffff" />
        </shape>
    </item>
</selector>

If you don't wanna play "find 2 differences", the only thing that changes is the image in <bitmap> tag.

I'm feeling whole WET1 here. Is there a way to reuse part of this drawable?

1 WET, eg. not DRY

like image 295
Spook Avatar asked Jan 19 '16 20:01

Spook


1 Answers

In short: There are no parameters for XML drawables, thus this might get a bit complicated.

Usually1 I would try to separate all the single <item> contents into separate drawable files and then include them with <item android:drawable="..." />. Those can then reused in other drawables.

For example you could move the following item into a separate file:

<item>
    <shape android:shape="rectangle">
        <solid android:color="#ffffff" />
    </shape>
</item>

Then you can include (reuse) it anywhere it is needed:

<item android:drawable="@drawable/shared_drawable" />

1 In your case however you might only save ~10% by using this approach since the <bitmap> elements are buried deep in the hierarchy.


Another, slightly exotic approach would be using a Gradle task to generate multiple XML drawables from a single drawable template. This obviously requires that you use Gradle or Android Studio respectively.

You can put your drawable file into the /res/raw folder (or any other folder that doesn't cause problems). I'll name this XML template file drawable_template.xml as referenced below. In this file we use a Groovy template variable ${bitmapdrawable} as the placeholder for your actual bitmap drawable's name:

...
    <item>
        <bitmap android:src="@drawable/${bitmapdrawable}"><!-- placeholder for gradle -->
            ...
        </bitmap>
    </item>
...

Now we need to define a Gradle task to copy the drawable template to the actual /res/drawable folder with the desired bitmap drawables included:

def drawablesToGenerate = ['sharp5', 'sharp6', 'sharp7', 'sharp8']  // bitmap names
task drawableTemplate << {
    drawablesToGenerate.each { drawableName ->  // for each drawable
        copy {
            println("copy template for ${drawableName}")
            from 'src/main/res/raw'  // source folder
            into 'src/main/res/drawable'  // target folder
            include 'drawable_template.xml'  // template file
            // rename file to final drawable
            rename('drawable_template.xml', "drawable_gen_${drawableName}.xml")
            expand(bitmapdrawable: "${drawableName}")
        }
    }
}
preBuild.dependsOn drawableTemplate

That script can be put into the app's (module's) build.gradle file.

Now the final drawables with the different included bitmaps are generated from a single template file at compile time. They have the name drawable_gen_sharpX.xml and can be used as normal drawables.

like image 154
Floern Avatar answered Oct 19 '22 18:10

Floern