Setting GradientDrawable through RemoteView - android

Here is what I want to do: I have a widget and I want to set its background depending upon users choice of colors. It has to be a gradient. The backgound is to be set by setting background of the linearLayout. For testing, I did it for a dummy-background as:
remoteViews.setInt(R.id.layout, "setBackgroundResource", R.drawable.widget_background);
I have seen this question: Call setImageDrawable from RemoteViews but I am not able to understand how to implement. I even can't find setXYZ() as mentioned there. Here is what I have tried until now:
Making a gradient drawable dynamically. In this approach, I am not able to set the background beacause AFAIK all the methods take id of the drawable and I have a drawable object.
Tried ImageView as a background (before LinearLayout). It does not provide proper margin to widget. Since the widget text is dynamic, sometimes it goes out of the imageView which is not what I want
Making a bg.xml in which I have:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="7dp"
android:topRightRadius="7dp" />
</shape>
Now I am totally confused and stuck. Can someone help(probably more of code and less of links) ASAP? Also, please don't close this question as already asked.

Tried ImageView as a background (before LinearLayout). It does not provide proper margin to widget. Since the widget text is dynamic, sometimes it goes out of the imageView which is not what I want
I'm not entirely sure what you mean, but if you use a FrameLayout / RelativeLayout for your root layout, and then put the ImageView inside with fill parent, your image should be exactly the size of your widget.
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="6dp" >
<ImageView
android:id="#+id/widgetBg"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY" />
// Other views
</FrameLayout>
Also, this is what I'm doing to dynamically change the color & alpha of a rounded corner gradient background. Then use setImageViewBitmap( ) to apply to the imageview. Probably there is a better way.
public static Bitmap getBackground(int bgColor, int width, int height, Context context) {
try {
// convert to HSV to lighten and darken
int alpha = Color.alpha(bgColor);
float[] hsv = new float[3];
Color.colorToHSV(bgColor, hsv);
hsv[2] -= .1;
int darker = Color.HSVToColor(alpha, hsv);
hsv[2] += .3;
int lighter = Color.HSVToColor(alpha, hsv);
// create gradient useng lighter and darker colors
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT,new int[] { darker, lighter});
gd.setGradientType(GradientDrawable.RECTANGLE);
// set corner size
gd.setCornerRadii(new float[] {4,4,4,4,4,4,4,4});
// get density to scale bitmap for device
float dp = context.getResources().getDisplayMetrics().density;
// create bitmap based on width and height of widget
Bitmap bitmap = Bitmap.createBitmap(Math.round(width * dp), Math.round(height * dp),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
gd.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
gd.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

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.

How to merge two Drawables dynamically in 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

How to color some percent of a layout?

I want to color e.g. 20 percent of layout background and after some time color 40 percent of it and so on.
How can I achieve this in android ?
You can start off with a ClipDrawable. This will clip another drawable — for instance, a ShapeDrawable — based on the drawable level.
Then in your timer callback:
int level; // from 0 to 10000 = 100%
view.getBackground.setLevel(level);
Drawable#setLevel
EDIT: Here's an example:
Define the shape drawable in /res/drawable. Call it bkgd_shape.xml.
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#color/background"/>
</shape>
Define the background drawable in /res/drawable. Let's call it bkgd_level.xml:
<?xml version="1.0" encoding="utf-8"?>
<clip
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/bkgd_shape"
android:clipOrientation="horizontal"
android:gravity="left|clip_horizontal|fill_vertical"/>
You might be able to put a color in directly for the drawable source, but I haven't tried it.
Set it as the background of your layout:
android:background="#drawable/bkgd_level"
Call setLevel on the drawable:
int level; // from 0 to 10000 = 100%
view.getBackground.setLevel(level);
You can put any type of view in the background and update it's with and set the color you'd like it to have dynamically.
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
int height = dm.heightPixels;
Expanding across the width then, just go by percentage.
int percentChange = .2; //Update this accordingly, put in its own function possibly
int backgroundWidth = width * percentChange;
For example, using an imageView in the background:
ImageView background = (ImageView) findViewById(R.id.expandingBackground);
background.requestLayout();
background.getLayoutParams().width = backgroundWidth;
Hope that helps, good luck.

How to make a drawable rectangle with two colors in xml in android?

I want to make something like this by using xml in android. I achieved something like it using gradient with angle 45 degree but I don't want gradient but plain color like this. Any suggestion is greatly appreciated. Thanks in Advance.
This is what I want to make using xml.
I need many like this so I can not load bitmaps in drawable folder.]1
You can draw two triangles of the required colors using the Android PathShape class
The below answer is shamelessly copied from here How to make gradient background in android
Try with this :
<gradient
android:angle="90" //you need to change angle as per your needs (270 might work in your case)
android:centerColor="#555994" //try playing with colors of start, center and End colors to get desired result. also try removing some.
android:endColor="#b5b6d2"
android:startColor="#555994"
android:type="linear" />
<corners
android:radius="0dp"/>
To use above code you need to make a drawable .xml file, copy & paste the above code into this file.
Thanks.
I do not know if it is possible in XML.
In Java, one possible way is to create a Shape and build a ShapeDrawable with it.
TwoTrianglesDrawable.java
public class TwoTrianglesDrawable extends ShapeDrawable {
public TwoTrianglesDrawable(){
super();
setShape(new TwoTrianglesShape());
}
private class TwoTrianglesShape extends Shape {
#Override
public void draw(Canvas canvas, Paint paint) {
Path path = new Path();
path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
Path path1 = new Path();
path1.setFillType(Path.FillType.INVERSE_EVEN_ODD);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(Color.YELLOW);
Point a = new Point(0, 0);
Point b = new Point(0, (int) getHeight());
Point c = new Point((int)getWidth(), (int)getHeight());
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.close();
canvas.drawPath(path, paint);
paint.setColor(Color.BLUE);
Point a1 = new Point(0, 0);
Point b1 = new Point((int)getWidth(),0);
Point c1 = new Point((int)getWidth(), (int)getHeight());
path1.moveTo(a1.x, a1.y);
path1.lineTo(b1.x, b1.y);
path1.lineTo(c1.x, c1.y);
path1.close();
canvas.drawPath(path1, paint);
}
}
}
Using, for example, as a RelativeLayout background:
RelativeLayout layout = (RelativeLayout) findViewById(R.id.layout);
ShapeDrawable background = new TwoTrianglesDrawable();
layout.setBackground(background);//Requires API 16 or higher.
This will give you two colors half and half vertically. Put this code in a drawable resource.
<item
android:top="320dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/red" />
</shape>
</item>
<item android:bottom="320dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/yellow" />
</shape>
</item>

Constructing a RatingBar using images loaded from the web

I have a form which I'm dynamically generating from data I receive from a web service. This web service provides images which need to be used in the creation of input elements. I'm having difficuly in setting the progressDrawable of a RatingBar. Though XML I'm able to apply a custom image using the following as the progressDrawable:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background" android:drawable="#drawable/custom_star" />
<item android:id="#+android:id/secondaryProgress" android:drawable="#drawable/custom_star" />
<item android:id="#+android:id/progress" android:drawable="#drawable/custom_star" />
</layer-list>
where custom_star is a simple .png image, and with #android:style/Widget.RatingBar as the RatingBar style. This works fine:
but I'm wanting to change custom_star dynamically.
In code, I have tried setting the progress drawable using a bitmap directly:
Drawable d = new BitmapDrawable(getResources(), downloadedImage);
ratingBar.setProgressDrawable(d);
and also by constructing a layer-list:
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {
getResources().getDrawable(R.drawable.custom_star),
getResources().getDrawable(R.drawable.custom_star),
getResources().getDrawable(R.drawable.custom_star)
});
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.secondaryProgress);
layerDrawable.setId(2, android.R.id.progress);
ratingBar.setProgressDrawable(layerDrawable);
Neither works for me; both result in the custom_star drawable appearing once, stretched by the dimensions of the RatingBar:
Any ideas?
Update:
Luksprog's answer below has made an improvement, but I'm still having a couple of issues. Now, the star drawable is not stretched and the value can be set by touch, but it appears as so with 3/5 selected:
and 5/5 selected:
I believe the scaling of the images can be fixed with a few tweaks, but annoyingly the secondaryProgress drawable doesn't seem to be set - the drawable used for the greyed out not-selected stars. Without that, it's not very usable.
Any ideas?
When using the default progressDrawable or a progressDrawable set through a theme all will be ok as in the constructor for the RatingBar(its superclass ProgressBar to be more precise) widget a method will be called to "make tiles" from that drawable. When using the setProgressDrawable method this doesn't happen and if you pass a simple BitmapDrawable or a LayerDrawable(with simple BitmapDrawables) that Drawable will simply be stretched to cover the widget's background area(what you see know).
In order to make it work you would need to manually do what the RatingBar does at start, create the tiles along with the ClipDrawables that it uses. I've written a method for this, following the source code of the ProgressBar widget:
private Drawable buildRatingBarDrawables(Bitmap[] images) {
final int[] requiredIds = { android.R.id.background,
android.R.id.secondaryProgress, android.R.id.progress };
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
Drawable[] pieces = new Drawable[3];
for (int i = 0; i < 3; i++) {
ShapeDrawable sd = new ShapeDrawable(new RoundRectShape(
roundedCorners, null, null));
BitmapShader bitmapShader = new BitmapShader(images[i],
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
sd.getPaint().setShader(bitmapShader);
ClipDrawable cd = new ClipDrawable(sd, Gravity.LEFT,
ClipDrawable.HORIZONTAL);
if (i == 0) {
pieces[i] = sd;
} else {
pieces[i] = cd;
}
}
LayerDrawable ld = new LayerDrawable(pieces);
for (int i = 0; i < 3; i++) {
ld.setId(i, requiredIds[i]);
}
return ld;
}
Then you would use the LayerDrawable returned by this method with the setProgressDrawable method. The RatingBar set its width multiplying the width of one of the state bitmaps with the number of stars, so in order to show the right amount of stars this has to be calculated as well.
Your ids are wrong, you have to set
#android:id/background
#android:id/secondaryProgress
#android:id/progress
You can also define a drawable in xml containing your customization
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/secondaryProgress"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/progress"
android:drawable="#drawable/star_full" />
</layer-list>
And you can use setMax on the RatingBar object to set the maximum amount of stars that can be displayed
It's much easier with such solution:
RatingBar ratingBar = new RatingBar(new ContextThemeWrapper(context, R.style.AppTheme_RatingBar), null, 0);

Categories

Resources