How to merge two Drawables dynamically in Android?
Asked Answered
S

2

19

so I have two different Drawables which I need to merge and get a single Drawable at runtime. I want the first Drawable to be at the top and the other one at the bottom. I came across LayerDrawable and it looks like it's exactly what I need, but I'm having trouble trying to arrange the Drawables.

So I have an ImageButton which is 48x48 dp and this is where the final Drawable goes. The first Drawable is a plus button (20x20 dp) and the second one is a small dot (4x4 dp) below the plus button.

The plus button Drawable is loaded using font glyphs. I'm creating the dot button Drawable using this xml snippet:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
       android:shape="oval">
    <solid
        android:color="@color/white_40"/>
    <size
        android:width="4dp"
        android:height="4dp"/>
</shape>

My first approach was to just add both Drawables to the LayerDrawable, but when I do that, the width/height attributes of the dot specified in the xml are ignored, and it stretches to cover the plus icon.

LayerDrawable finalDrawable = new LayerDrawable(new Drawable[] {plusIcon, dotIcon});

The above results in this:



The second approach I tried was by using setLayerInset to try and position the two Drawables.

    LayerDrawable finalDrawable = new LayerDrawable(new Drawable[] {plusIcon, dotIcon});
    finalDrawable.setLayerInset(0, 0, 0, 0, 0);
    finalDrawable.setLayerInset(1, dp(22), dp(44), dp(22), 0);

The above code snippet ended up placing the dot in the correct position, but it also started affecting the position and size of the plus button and it ended up looking like this:
enter image description here

But what I really want is to have the plus button in the centre of the ImageButton and the plus icon just below it. Does anyone have an idea where I'm going wrong and how can I position the two drawables correctly?

PS: My app supports API 15+, so I can't use a bunch of methods from LayerDrawable's API like setLayerGravity, `setPaddingMode, etc.

Stillage answered 17/7, 2017 at 20:4 Comment(0)
B
24

Edit

This code will work on API levels below 23:

ImageButton button = (ImageButton) findViewById(R.id.button);

Drawable plusIcon = ContextCompat.getDrawable(this, R.drawable.plus);
Drawable dotIcon = ContextCompat.getDrawable(this, R.drawable.oval);

int horizontalInset = (plusIcon.getIntrinsicWidth() - dotIcon.getIntrinsicWidth()) / 2;

LayerDrawable finalDrawable = new LayerDrawable(new Drawable[] {plusIcon, dotIcon});
finalDrawable.setLayerInset(0, 0, 0, 0, dotIcon.getIntrinsicHeight());
finalDrawable.setLayerInset(1, horizontalInset, plusIcon.getIntrinsicHeight(), horizontalInset, 0);

button.setImageDrawable(finalDrawable);

Original

The following code works for me:

ImageButton button = (ImageButton) findViewById(R.id.button);

Drawable plusIcon = ContextCompat.getDrawable(this, R.drawable.plus);
Drawable dotIcon = ContextCompat.getDrawable(this, R.drawable.oval);

LayerDrawable finalDrawable = new LayerDrawable(new Drawable[] {plusIcon, dotIcon});
finalDrawable.setLayerInsetBottom(0, dotIcon.getIntrinsicHeight());
finalDrawable.setLayerGravity(1, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);

button.setImageDrawable(finalDrawable);

This produces the following ui:

enter image description here

Boxfish answered 17/7, 2017 at 21:2 Comment(6)
Hey Ben, thanks for your answer. But setLayerGravity is only available from API 23 onwards iirc. My app supports API 15 and above.Stillage
Hey, thanks a lot! The edited answer makes it work. Just a minor question though, I see that if I change the size of the dot at the bottom, the plus icon will have to move up to make room for the dot icon as its relative position inside the ImageButton depends on the size of the dot. Is there any way that I can position the plus icon exactly at the center (Gravity.CENTER) and the dot icon, let's say 4 dp below it?Stillage
I think at this point we're getting outside the realm of what LayerDrawable was really meant to do. I'd consider perhaps creating a vector drawable that contained both the dot and the plus or maybe using a FrameLayout/ConstraintLayout (on top of your button) to position the two drawables.Boxfish
Yeah the thing is I can't use layouts to position the drawables as I can only return a Drawable which gets applied to the ImageButton. So that's exactly why I was trying to achieve this using a LayerDrawable. Any thoughts?Stillage
Seems like each layer of the LayerDrawable will be scaled up to fit the LayerDrawable's size, which is the largest size of any of its layers. This "layer size" includes insets. So if you define some size padding, you can inset the plus by (padding + dot diameter) on all sides, and inset the dot by (dot diameter + padding + plus height + padding) on the top and ((2*diameter + 2*padding + plus width) - diameter) / 2 on each side.Boxfish
I was able to get this work. Thanks Ben for all the help!Stillage
D
1

This is how i solved this:

final View view = findViewById(R.id.view_in_layout);

Drawable bottom = ContextCompat.getDrawable(context, R.drawable.ic_bottom);
Drawable top = ContextCompat.getDrawable(context, R.drawable.ic_top);

//bottom = index 0, top = index 1
final LayerDrawable layer = new LayerDrawable(new Drawable[]{bottom, top});

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom,
                  int oldLeft, int oldTop, int oldRight, int oldBottom) {
        //get the real height after it is calculated
        int height = view.getHeight();
        //set the height half of the available space for example purposes,
        //the drawables will have half of the available height
        int halfHeight = height / 2;
        //the bottom drawable need to start when the top drawable ends
        layer.setLayerInset(0, 0, halfHeight, 0, 0);
        //the top drawable need to end before the bottom drawable starts
        layer.setLayerInset(1, 0, 0, 0, halfHeight);

        view.setBackground(layer);
        view.removeOnLayoutChangeListener(this);
    }
});

You can choose different dimensions for your drawables or moving them with the indexes. I used View.OnLayoutChangeListener(...) for example to wait for the current available height of the view

Doan answered 17/7, 2017 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.