How to merge two Drawables dynamically in Android? - android

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:
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.

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:

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

Related

How to apply rounded corner to a view based on its width in Android Kotlin

I am working on a custom Progress bar as photos below:
Basically, I created a drawable xml background file:
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid
android:color="#color/jungleGreen"/>
<corners
android:bottomLeftRadius="40dp"
android:topLeftRadius="40dp"/>
</shape>
Then I applied it to the view that I am using:
<View
android:id="#+id/progress_bar_placeholder_view"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginEnd="45dp"
android:background="#drawable/background_filled_patronage_progressbar"
app:layout_constraintTop_toTopOf="parent"/>
It is totally fine and I can achieve scenario 1 and 2, but when the bar is getting closer to the end, how can I programmatically set the rounded corner for the top right and the bottom right part of the view until it looks like in photo 3?
Thanks.
try this
public static void customView(View v, int backgroundColor, int borderColor)
{
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.RECTANGLE);
shape.setCornerRadii(new float[] { 8, 8, 8, 8, 0, 0, 0, 0 });
shape.setColor(backgroundColor);
shape.setStroke(3, borderColor);
v.setBackground(shape);
}
Source : How to create android shape background programmatically?
Madhav's solution works for me, so basically I will need to pass in the width of the view, then calculate the corner radius number and put it into gradientDrawable as below:
private fun setupGraphBackground(view: View, graphWidth: Int) {
val gradientDrawable = GradientDrawable()
gradientDrawable.shape = GradientDrawable.RECTANGLE
gradientDrawable.setColor(resources.getColor(R.color.jungleGreen))
gradientDrawable.setStroke(0, null)
gradientDrawable.cornerRadii = floatArrayOf(45f, 45f,
graphWidth * calculationRules, graphWidth * calculationRules,
graphWidth * calculationRules, graphWidth * calculationRules,
45f, 45f)
view.background = gradientDrawable
}
Where as calculationRules is the rule I want so that I can get the right radius of the topRight and bottomRight corner.

Radio button drawable top not showing with setCompoundDrawablesWithIntrinsicBounds(); and with setButtonDrawable(); showing in wrong position

I got Radio Button with button set as a drawable bottom in the layout. In java I set drawable top with setCompoundDrawablesWithIntrinsicBounds(0,R.drawable.wrong,0,R.drawable.check); method and drawable top is not showing.
So I tried different method with setButtonDrawable(); which doesn't change the Radio Button drawablebottom, which is good and it sets the Drawable in place next to Button.
.
How to change its position to top?
You can reuse your previous drawable to preserve the drawable bottom with:
Drawable drawableTop = ContextCompat.getDrawable(getContext(),R.drawable.wrong);
//Drawable drawableBottom = ContextCompat.getDrawable(getContext(),R.drawable.check);
Drawable[] drawables = button.getCompoundDrawables();
// left top right bottom
button.setCompoundDrawablesWithIntrinsicBounds(drawables[0], drawableTop, drawables[2], drawables[3]);
Or you can try the following:
Drawable drawableTop = ContextCompat.getDrawable(getContext(),R.drawable.wrong);
Drawable drawableBottom = ContextCompat.getDrawable(getContext(),R.drawable.check);
int topHeight = drawableTop.getIntrinsicHeight();
int topWidth = drawableTop.getIntrinsicWidth();
drawableTop.setBounds(0, 0, topWidth, topHeight);
topHeight = drawableBottom.getIntrinsicHeight();
topWidth = drawableBottom.getIntrinsicWidth();
drawableBottom.setBounds(0, 0, topWidth, topHeight);
button.setCompoundDrawables(null, drawableTop, null, drawableBottom);

Android Drawable Overflowing From Button Borders

I am using the following code to scale an image to 150dpx150dp and place in inside a 150dpx150dp button but the image overflows the button in all dimensions:
float densityScale = getResources().getDisplayMetrics().density;
float scaledImageWidth = 150 * densityScale;
float scaledImageHeight = 150 * densityScale;
Drawable image = getResources().getDrawable(R.drawable.user_photo);
Bitmap bitmap = ((BitmapDrawable)image).getBitmap();
Drawable scaledImage = new BitmapDrawable(Bitmap.createScaledBitmap(bitmap, (int)scaledImageWidth, (int)scaledImageHeight, true));
scaledImage.setBounds(0, 0, (int)scaledImageWidth, (int)scaledImageHeight);
((Button)findViewById(R.id.button)).setCompoundDrawables(null, scaledImage, null, null);
((Button)findViewById(R.id.button)).setPadding(0,0,0,0);
Button and image are of the same size. Why does this overflow happen and how can it be fixed?
(I know I can use an ImageButton for this but I want to use button drawable because there are cases where I will need to add text etc.)
You need to have a smaller drawable. Button already has a background with a certain padding

Changing the size of the seekbar programmatically but cant get the thumb to be larger than the bar

I have to make a seekbar where i can change almost everything programmatically. And now I'm having a problem to change the thumb and the bar size. Everything works great if the bar is larger or the same size as the thumb, but if the thumb is larger than the bar, I can't get it to show the entire thumb with the correct size of the bar.
Here's my code to create the thumb:
public void setSeekBarThumb(int width, int height, int rColor, int gColor, int bColor){
ShapeDrawable thumb = new ShapeDrawable( new OvalShape() );
thumb.setIntrinsicHeight( height );
thumb.setIntrinsicWidth( width );
thumb.setBounds(new Rect(0, 0, width, height));
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed},thumb );//add to the state list with the state pressed
thumb = new ShapeDrawable( new OvalShape() );
thumb.setIntrinsicHeight( height );
thumb.setIntrinsicWidth( width );
thumb.setBounds(new Rect(0, 0, width, height));
states.addState(new int[] { },thumb );//add to the state list with no state
seekbar.setThumb(states);
seekbar.setThumbOffset(0);
}
heres my code to create the bar:
public void setSeekBarProgress(int width, int height, int rColor, int gColor, int bColor){
GradientDrawable shape = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{Color.WHITE,Color.rgb(rColor, gColor, bColor)});
shape.setCornerRadius(50);
shape.setSize(width, height);
shape.setBounds(0, 0, width, height);
ClipDrawable clip = new ClipDrawable(shape, Gravity.LEFT,ClipDrawable.HORIZONTAL);
shape = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{Color.WHITE, getResources().getColor(R.color.DimGray)});
shape.setCornerRadius(50);//change the corners of the rectangle
shape.setSize(width, height);
shape.setBounds(0, 0, width, height);
LayerDrawable mylayer= new LayerDrawable(new Drawable[]{shape, clip,});
seekbar.setProgressDrawable(mylayer);
seekbar.setProgress(50);
}
Here is my code to put everything together, where I set the height of the seek bar:
public MySeekBar(Activity activity, AbsoluteLayout ParentLayout, SeekBarParameters parameters)
{
super(activity.getApplicationContext());
seekbar =(SeekBar)activity.getLayoutInflater().inflate(R.layout.horizontal_seek_bar, ParentLayout,false);
seekbar.setMax(100);
setSeekBarThumb(parameters.widthThumb, parameters.heightThumb,0,100,0);
setSeekBarProgress(parameters.width, parameters.height,255,69,0);
int drawHeight;
if(parameters.height>parameters.heightThumb) drawHeight=parameters.height;
else drawHeight=parameters.heightThumb;
AbsoluteLayout.LayoutParams params = new AsoluteLayout.LayoutParams(parameters.width,drawHeight,parameters.xPosition, parameters.yPosition);
ParentLayout.addView(seekbar, params);
}
The XML code where I inflate the bar from:
<SeekBar xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:max="100"
android:progress="0"
android:secondaryProgress="0"
android:maxHeight="10000dip"
android:id="#+id/slider"
/>
To change the size of the seek bar I use ParentLayout.addView(seekbar, params).
When adding the view, if I use the height that I want to the bar, the thumb gets cutted. If I use the height of the thumb, the bar gets to the same height of the thumb. Android resizes the bar to the size I use to add the view.
I tryed setting the size of the GradientDrawable that is the bar with shape.setSize(width, height), I also tried shape.setBounds(0, 0, width, height), and I tried creating another image with the right size and adding to the LayerDrawable mylayer. But nothing worked.
When creating this through XML it's possible to use paddingTop and paddingBottom to get the bar to the right size. But I don't know how to do this to the GradientDrawable programmatically.
well, too bad there were no responses but i was able to answer the question myself. The answer is to use InsetDrawable. You have to put the one of the drawables of the bar into a InsetDrawable.
Thats the code:
public void setSeekBarProgress(int width, int height, int rColor, int gColor, int bColor){
GradientDrawable shape2 = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{Color.WHITE,Color.rgb(rColor, gColor, bColor)});
shape2.setCornerRadius(50);
ClipDrawable clip = new ClipDrawable(shape2, Gravity.LEFT,ClipDrawable.HORIZONTAL);
GradientDrawable shape1 = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{Color.WHITE, getResources().getColor(R.color.DimGray)});
shape1.setCornerRadius(50);//change the corners of the rectangle
InsetDrawable d1= new InsetDrawable(shape1,5,5,5,5);//the padding u want to use
LayerDrawable mylayer = new LayerDrawable(new Drawable[]{d1,clip});
seekbar.setProgressDrawable(mylayer);
seekbar.setProgress(50);
}
I hope this can help someone else with the same problem.

Android: Setting padding in a overlay image background

I'm trying to create a dynamic icon menu for my android application.
The icon is made from 2 drawable, a 9-patch background image with the icon image overlay on it.
With reference to overlay two images in android to set an imageview, I've the following code which have the overlay nicely.
Resources res = parent.getResources();
Drawable icon_bg = res.getDrawable(R.drawable.menu_icon_bg);
Drawable icon = res.getDrawable(R.drawable.menu_icon);
// Not working
int int_icon_height = icon.getIntrinsicHeight();
int int_icon_width = icon.getIntrinsicWidth();
Rect rect_icon_bg_bound = new Rect();
rect_icon_bg_bound.left = 0;//(int) Math.round(int_icon_width*-1.5);
rect_icon_bg_bound.right = (int) Math.round(int_icon_width*1.5);
rect_icon_bg_bound.top = 0;//(int)Math.round(int_icon_height*-1.5);
rect_icon_bg_bound.bottom = (int)Math.round(int_icon_height*1.5);
icon_bg.setBounds(rect_icon_bg_bound);
Drawable[] layers = new Drawable[2];
layers[0] = icon_bg;
layers[1] = icon;
LayerDrawable layerDrawable = new LayerDrawable(layers);
ImageView iv_icon_combined = new ImageView(getApplicationContext());
iv_icon_combined.setImageDrawable(layerDrawable);
The problem I'm facing right now is adding a padding so that the icon will have some spacing within the background.
Hope to get some enlightenment here, thanks in advance.
I just ran into this same issue. Take a look at the setLayerInset method on LayerDrawable. It takes the layer level as an argument and then the modifiers for the left, top, right, bottom bounds of the drawable.

Categories

Resources