Setting widget background to programmatically generated gradient - android

I'm struggling with making my widget to look pretty, but the widget fights back ;(
I have a problem with setting widget layout background to generated linear gradient.
For now, I have found how to generate linear gradient with custom "weigth" of colors for that gradient:
public static PaintDrawable createLinearGradient(final int left, final int top, final int right, final int bottom,
final int[] colors, final float[] positions)
{
ShapeDrawable.ShaderFactory shaderFactory = new ShapeDrawable.ShaderFactory()
{
#Override
public Shader resize(int width, int height)
{
LinearGradient lg = new LinearGradient(left, top, right, bottom, colors, positions, Shader.TileMode.REPEAT);
return lg;
}
};
PaintDrawable gradient = new PaintDrawable();
gradient.setShape(new RectShape());
gradient.setShaderFactory(shaderFactory);
return gradient;
}
And here is a way to set widget background to drawable from drawable folder:
int backgroundId = getDrawableByName(context, "round_transparrent_background");
remoteViews.setInt(R.id.mainLayout, "setBackgroundResource", backgroundId);
I need a way to write this command as following:
Drawable background = createLinearGradient(params ... );
remoteViews.setInt(R.id.mainLayout, "setBackgroundResource", background);

RemoteViews are inherently limited to certain functions. This makes some sense when you consider that any information you pass to your RemoteViews needs to travel across processes, and not all classes/data types are supported.
There is no way to pass an arbitrary Drawable object to RemoteViews -- none of the methods support it, and Drawable in general is not a class whose data can be marshalled across processes. The reason it works for drawable resources is that the resource ID is just an integer, and Android knows how to inflate them into Bitmaps (for pngs) or their respective Drawable implementations (for anything declared with XML).
As I see it, the only strategy that might work would be to actually draw the gradient into a Bitmap using the Canvas APIs and to use an ImageView in your AppWidget to act as the background. Then you would call remoteViews.setImageViewBitmap() to set the content of the ImageView.

Related

CustomSeekBar with fading alpha color gradient, black thin border, and tiled background

The goal
I'm trying to replicate this Dialog.
The current state
But I've only gotten to this point (showing only the relevant part):
What is missing
A way to add a thin black border surrounding my custom Seekbar's "progress area", and a tiled background restricted to that "progress area".
My code
As it is, my CustomSeekBar (which extends AppCompatSeekBar) works fine for setting a starting and ending color in the code. Here is the function:
public void setGradientColor(#ColorInt int leftColor, #ColorInt int rightcolor) {
grad = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,
new int[] {leftColor, rightcolor});
setProgressDrawable(grad);
}
But trying to set the background's image ends up looking like my very last seekbar in the The current state image (the background extends outside of the progress area and fills all of the view's area):
aSeekBar.setBackground(context.getDrawable(R.drawable.checker));
aSeekBar.setGradientColor(Color.parseColor("#00000000"), Color.parseColor("#FF000000"));
My checker.xml file in res/drawable:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/checkerstile"
android:tileMode="repeat"
/>
For the "Border" part of your question, you just have to add a Stroke to your GradientDrawable :
public void setGradientColor(#ColorInt int leftColor, #ColorInt int rightcolor) {
grad = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,
new int[] {leftColor, rightcolor});
grad.setStroke(/* stroke width*/, /* int color*/);
setProgressDrawable(grad);
}
For that, it that easy, sorry i can't help for the background.
I've found a viable solution:
int padding = aSeekBar.getPaddingStart();
InsetDrawable bgImg = new InsetDrawable(context.getDrawable(R.drawable.checker), padding);
aSeekBar.setBackground(bgImg);
Combine this with the solution from Olivier for the border and you get this result:

Android invalidateDrawable() not working

I have a bunch of drawables in a custom view. I want the user to be able to press on one or multiple drawables and it changes colors. Currently, each drawable is just a StateListDrawable with two states: state_pressed and not pressed. Every time I press a drawable, setState returns true so I'm assuming that it is actually changed, but I don't see the drawable image change. Is invalidateDrawable not doing anything? What am I doing wrong? How can I redraw the one drawable when pressed without needing call customView.invalidate() and redrawing the whole thing each time? I was doing that originally but found that my app ran very slowly/inefficiently. Thanks!
The flow:
Custom View (contains set of our custom class - TouchKey)
- Custom class TouchKey containing drawable and info
- Upon press or release, custom class finds which drawable to change
Here's code for a button touch within TouchKey class (MyTouch is a custom class tracking all the touches on the android device):
public void pressed(MyTouch touch) {
boolean successfulStateChange = this.drawable.setState(new int[]{android.
R.attr.state_pressed});
this.customView.invalidateDrawable(drawable);
}
public void released(MyTouch touch) {
boolean successfulStateChange = this.drawable.setState(new int[]{-android.
R.attr.state_pressed});
this.customView.invalidateDrawable(drawable);
}
How my StateListDrawable is being drawn in my custom view:
public class CustomView extends View {
private TreeMap<Integer, TouchKey> keymap;
/* Initialization Code Stuff Here - call drawKey */
// StateListDrawable Creation
private StateListDrawable drawKey(Canvas canvas, int bounds_l,
int bounds_t, int bounds_r, int bounds_b)
throws Resources.NotFoundException, XmlPullParserException, IOException {
StateListDrawable key = new StateListDrawable();
key.addState(new int[]{android.R.attr.state_pressed},
ContextCompat.getDrawable(mContext, R.drawable.key_pressed));
key.addState(new int[]{-android.R.attr.state_pressed},
ContextCompat.getDrawable(mContext, R.drawable.key_released));
key.setBounds(bound_l, bounds_t, bounds_r, bounds_b);
key.draw(canvas);
return key;
}
}
I was doing that originally but found that my app ran very slowly/inefficiently
If do it so you have a big advantages in some place in your code, or (I suppose) doesn't scale images before drawing. So try to find a logic on your code that have a huge advantage on system. Because View.invalidate() so fast method.
Other 0,02$ :
I suppose that you develop something like a keyboard. For this case you need invalidate just region of your canvas.
View.invalidate(new Rect(0, 0, 49, 49));
I had the problem too, but I solve it in the end. The reason for this problem is that the Drawable object which you are using in the context doesn't setup it's Bounds by call setBounds(Rect), so it's Bounds is Rect(0,0,0,0) by default. This cause invalidateDrawable() of View which Drawable attached not working.
See the View.invalidateDrawable():
#Override
public void invalidateDrawable(#NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
rebuildOutline();
}
}
Look check Drawable.getDirtyBounds():
/**
* Return the drawable's dirty bounds Rect. Note: for efficiency, the
* returned object may be the same object stored in the drawable (though
* this is not guaranteed).
* <p>
* By default, this returns the full drawable bounds. Custom drawables may
* override this method to perform more precise invalidation.
*
* #return The dirty bounds of this drawable
*/
#NonNull
public Rect getDirtyBounds() {
return getBounds();
}
So the Rect dirty is Rect(0,0,0,0), if you not setup Drawable.Bounds.Therefore View.invalidate() doesn't working.
So what you have to do is setup Drawable.Bounds in some place in the code,like that:
#Override
public void draw(#NonNull Canvas canvas) {
Rect localRect = canvas.getClipBounds();
this.setBounds(localRect);
...
}

how to convert shape to ShapDrawable in android

I have a number of shapes, and I have one view. I need to dynamically (i.e. programmatically) select a shape to set as the background of my view based on user inputs. So my question: who do I programmatically turn a shape into a ShapeDrawable or such?
I already look at How to change shape color dynamically?. Those posts assume the shape is already attached to a view. But me all my shapes are free agents.
It seems that is does not work with ShapeDrawable, but take a look at my GradientDrawable example:
you can create gradient drawable dynamically.. use below class
import android.graphics.drawable.GradientDrawable;
public class SomeDrawable extends GradientDrawable {
public SomeDrawable(int pStartColor, int pCenterColor, int pEndColor, int pStrokeWidth, int pStrokeColor, float cornerRadius) {
super(Orientation.BOTTOM_TOP,new int[]{pStartColor,pCenterColor,pEndColor});
setStroke(pStrokeWidth,pStrokeColor);
setShape(GradientDrawable.RECTANGLE);
setCornerRadius(cornerRadius);
}
}
and use this class as below
SomeDrawable drawable = new SomeDrawable(Color.parseColor("Start Color Code"),Color.parseColor("Center Color Code"),Color.parseColor("End Color Code"),1,Color.BLACK,00);
yourLayout.setBackgroundDrawable(drawable);
I just found that I can do
myview.setBackgroundResource(R.drawable.my_shape)

Android - Multiple colors & size in drawable shape style

I'm trying to have a circle background for my TextView, so I created a shape style as below.
But I need to have multiple colors with multiple sizes (while the textSize stays constant), so I need to set the width/height in the style.
From my understanding..Layer List puts all the shapes on top of one another? Because I need to call it 12 times at different places, so it seems quite cumbersome to have 12 shape style xmls.
Is there a better way to have all the different shape/size combinations inside one XML?
Shape Style:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<corners android:radius="10dp"/>
<solid android:color="#color/girl_orange"/>
<size
android:width="84dp"
android:height="84dp" />
</shape>
Called in layout xml by:
android:background="#drawable/skills_circle"
Thanks in advance!!
create a custom Drawable, this way you can have milions combinations of size/color:
class CircleDrawable extends Drawable {
...
}
So I followed the advice from pskink and created a CircleDrawable class.
It works quite nicely for my application (although I don't know if it's the right way...), so I thought I'd share it.
public CircleDrawable(Bitmap bitmap, Context context) {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
CircleDrawable.context = context;
drawable = new ShapeDrawable(new OvalShape());
setColor(); // supports multiple color
setSize(); //supports multiple size
}
private void setColor() {
// some algorithm to pick the right color...
if (...)
int color = context.getResources().getColor(R.color.pale_blue);
paint.setColor(color);
}
/*
* algorithm to set size here...
*/
#Override
public void draw(Canvas canvas) {
//draw circle in the middle of the TextView
canvas.drawCircle(textViewSize, textViewSize, circleSize, paint);
}
And in the main code where I need to dynamically draw the circles:
final float scale = getApplicationContext().getResources().getDisplayMetrics().density;
int pixels = (int) (107.0f * scale + 0.5f);
skills.setWidth(pixels);
skills.setHeight(pixels);
skills.setBackground(new CircleDrawable(null, getApplicationContext()));
And I ended up with a bunch of circles with different shapes and colors.

Ninepatch Drawable with ColorFilter

I'm create some calendar view and what I want to do is to create a background for a LineairLayout that is clickabe.
Therefore I create a StateListDrawable with two images:
The image for the background
The image when the item has been pressed
So far that works with this piece of code:
NinePatchDrawable background = (NinePatchDrawable) context.getResources().getDrawable(R.drawable.calendar_item);
Drawable backgroundFocus = context.getResources().getDrawable(R.drawable.calendar_focus);
int stateFocused = android.R.attr.state_focused;
int statePressed = android.R.attr.state_pressed;
StateListDrawable sld = new StateListDrawable();
sld.addState(new int[]{ stateFocused, statePressed}, backgroundFocus);
sld.addState(new int[]{-stateFocused, statePressed}, backgroundFocus);
sld.addState(new int[]{-stateFocused}, background);
return sld;
But I would like to do something extra. I'dd like the user to be able to pass in a color that he wants to use to display the background. So the background var must be variable, but it must be based on the nine-patch drawable.
So I thought I could just do something like this:
background.setColorFilter(Color.RED, PorterDuff.Mode.DST_IN);
Where Color.RED must be replaced by the color of choice of the user.
But that doesn't seem to be working. The nine-patch is created perfectly but without the color fiilter being applied.
I also tried other PoterDuff.Mode 's:
SRC
SRC_ATOP
DST_IN
...
If you have any clue what I'm doing wrong or what I could do else to solve my issue please let me know! :-)
Kr,
Dirk
I don't think you can assign ColorFilters for each Drawable in a StateListDrawable. Reason: The ColorFilter will be removed/replaced when the StateListDrawable changes state. To see this in action, change the order of the statements such that:
background.setColorFilter(Color.RED, PorterDuff.Mode.DST_IN);
comes after the creation of the StateListDrawable. You'll see that the ColorFilter IS applied. But, as soon as the state changes(click, then release), the ColorFilter isn't there any more.
StateListDrawables allow you to set a ColorFilter: StateListDrawable#setColorFilter(ColorFilter). This is how the supplied (or null) ColorFilter is used:
StateListDrawable#onStateChange(int[]):
#Override
protected boolean onStateChange(int[] stateSet) {
....
if (selectDrawable(idx)) { // DrawableContainer#selectDrawable(int)
return true;
}
....
}
DrawableContainer#selectDrawable(int):
public boolean selectDrawable(int idx) {
....
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
Drawable d = mDrawableContainerState.mDrawables[idx];
mCurrDrawable = d;
mCurIndex = idx;
if (d != null) {
....
// So, at this stage, any ColorFilter you might have supplied
// to `d` will be replaced by the ColorFilter you
// supplied to the StateListDrawable, or `null`
// if you didn't supply any.
d.setColorFilter(mColorFilter);
....
}
} else {
....
}
}
Workaround:
If at all a possible, use an ImageView (match_parent for dimensions) for visual communication. Set the StateListDrawable that you've created as the ImageView's background. Create another StateListDrawable for the overlay:
StateListDrawable sldOverlay = new StateListDrawable();
// Match Colors with states (and ultimately, Drawables)
sldOverlay.addState(new int[] { statePressed },
new ColorDrawable(Color.TRANSPARENT));
sldOverlay.addState(new int[] { -statePressed },
new ColorDrawable(Color.parseColor("#50000000")));
// Drawable that you already have
iv1.setBackground(sld);
// Drawable that you just created
iv1.setImageDrawable(sldOverlay);
Another possibility: use a FrameLayout in place of LinearLayout. LinearLayouts do not have a foreground property.
// StateListDrawable
frameLayout.setBackground(sld);
// For tint
frameLayout.setForeground(sldOverlay);
It does involve overdraw, making it a sub-optimal solution/workaround. Perhaps you can look at extending StateListDrawable and DrawableContainer. And since you are not using a ColorFilter for the StateListDrawable, you can remove d.setColorFilter(mColorFilter); from overridden DrawableContainer#selectDrawable(int).

Categories

Resources