I have implemented pinch to zoom on a ViewGroup whose child Views have background drawables. Upon onScaleEnd, I redraw child Views at the new scale so they're more crisp. However, after re-drawing at smaller scales the rounded corners radii are too big, giving the rectangle shape the wrong appearance. I have code that works for changing the corner radius for all the shapes that works fine on my Kindle Fire HD (based on 4.0.3), but does nothing on my Galaxy Nexus (4.2.2) phone.
Note: I do NOT mutate the drawable because I do want all the drawables based on that resource to change their appearance when I do this.
I've looked at: change corner radius of drawable programmatically
and several other posts, but nothing even hints at different behaviors across devices.
Here's the code:
final LayerDrawable layerDrawable = (LayerDrawable) context.getResources().getDrawable(drawableId);
final Drawable drawable = layerDrawable.findDrawableByLayerId(drawableLayerId);
if (drawable instanceof GradientDrawable) {
final float newRadius = nodeScale > SMALL_TREE_NODE_LAYOUT_THRESHOLD ? normalRadius : smallRadius;
// this doesn't work
((GradientDrawable) drawable).setCornerRadius(newRadius);
// this doesn't work either
((GradientDrawable) drawable).setCornerRadii(new float[] {newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius });
}
Is this a bug in 4.2.2?
To get around this, I finally just wrote the code to re-create the Drawables when I need them. This was needed anyway to change all the corner radii, shrink/grow number of shadow rects, etc.
Related
Having a bit of a problem on pre-Lollipop devices concerning setting grayscale on my imageviews. Specifically, setting transparency after using this code to convert an imageview to greyscale:
public static void setGrayScale(ImageView view) {
Paint paint = new Paint();
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
paint.setColorFilter(filter);
view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
}
On 5.0+ devices, the image is converted to greyscale then the alpha value is applied, resulting in a nice looking image.
On pre 5.0 devices (specifically Nexus 4 with 4.2.2), it appears that the alpha value isn't being respected, and a darker grey appears instead.
Does anyone know why this behavior would be happening? It's the same exact setAlpha value (0.6f). I tried different methods of applying greyscale and also attempted to setAlpha before and after to no effect.
I think the cause is the implementation of ColorMatrixColorFilter class is changed from Lollipop as you can see in grepcode.
Workaround for your problem is set different value for lollipop above in values-v21 folder and pre-lollipop value inside values.
I have a stack of bitmaps that I need to render one above the other. I achieve this with a relative layout and several ImageViews on top of each other which all have a Bitmap assigned to it.
This works great, but when the top layers is semi-transparent, the colours of the lower bitmap are off.
All my bitmaps use Config.ARGB_8888.
Say the top layer is red with an alpha of 50% and the bottom layer is green with an alpha of 100%.
I can either set the colour of the bitmap to red, then the alpha of the ImageView to 0.5f and it will render the green colour below fine (darker green with some red mixed in).
If I set the bitmap pixels to a 50% red like this: bmp.eraseColor(0x7Fff0000); and leave the imageView alpha on 100%, the green below will be displayed as yellow, mixing red and green, rather than overlaying it.
Unfortunately I can not use the (working) fist version because the alpha on the Bitmap above is not going to be uniform.
Is there a blend mode setting to use true colours when using semi transparent pixels in a Bitmap?
EDIT: I have also tried to set several PorterDuffXfermodes to the ImageViews but none gives the right result.
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY)); //OVERLAY//ADD//SCREEN//DARKEN//LIGHTEN
imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
Got it, needed to premultiply the alpha to get the desired result.
EDIT: LOOK AT SOLUTION ABOVE
i m freaking out. all i just want to do, is setting a linear GradientDrawable, which changes the vertical center of the gradient... drawing the gradient works fine, but how can i change the center of it?!?
RelativeLayout bgScreen = (RelativeLayout) findViewById(R.id.player_screen);
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[] {startColor,endColor});
gd.setCornerRadius(0f);
gd.setAlpha(200);
bgScreen.setBackground(gd);
public void redrawOrChangeBackgroundGradient(){
//??? either change center of existing
gd.setGradientCenter(float x, float y) //ONLY works in RADIAL_GRADIENT or SWEEP_GRADIENT.
//??? or complete redraw Gradient with different center
}
here s a picture example of how i want to change the gradient via code
cannot be that hard, can it?
The lacking ability to set the center programmatically for linear GradientDrawables is a registered issue already.
But there is a workaround described here. Basically, you should create a PaintDrawable from a LinearGradient and set it as your view's background drawable. Following this solution, you can set the center in your LinearGradient constructor by mapping the colors to the positions array:
float[] positions
May be null. The relative positions [0..1] of each corresponding color
in the colors array. If this is null, the colors are distributed
evenly along the gradient line.
(not tested, but it should do the trick for you)
I am creating different custom views with two different colors. According to my app features user will drag those objects on the screen, when dragging objects will overlap each other. I want to differentiated the overlapped area, how to set the combined color for overlapped Area.
Look at the below image. Here I am using canvas for creating those custom views two circles are two different views.
EDIT: If I use opacity 128 I am able to see the background color, but I want the combination color of overlapped objects colors.
The Color mixing you are looking for is sometimes called Intuitive Color Mixing, or the RYB Color System:
RYB:
CC license
A quote from this paper by Nathan Gossett and Baoquan Chen on algorithms for intuitive color mixing summarizes how the intuitive color system works:
"In this model, Red, Yellow and Blue are used as pure primary colors. Red and Yellow mix to form Orange, Yellow and Blue mix to form Green, and Blue and Red mix to form Purple [...]. These are the colors an untrained viewer would expect to obtain using children's paint [...]. In addition, many people do not think of White as the mixture of all colors, but instead as the absence of color (a blank canvas). A more common assumption would be that mixing many colors together would result in a muddy dark brown color."
RYB is not implemented in Android's blend modes and can't really be simulated by mixing alpha/different blend modes.
Most computer graphics applications make use of the RGB or CMYK color spaces:
CMYK:
CC license
CMY is based on subtractive color. Subtractive color mixing means that, starting with white, as we add color, the result gets darker. CMYK is used for mixing colors in images intended for printing in e.g. Photoshop and Illustrator.
RGB:
CC license
RGB is based on Additive Color. The colors on a computer screen are created with light using the additive color method. Additive color mixing begins with black and as more color is added, the result gets lighter and ends in white.
This site discusses CMYK and RGB in more detail.
In neither RGB nor CMYK does mixing blue and yellow produce green, or generally speaking, intuitive color mixes. To implement a RYB color system on Android would be quite involved. The paper by Nathan Gossett and Baoquan Chen quoted above proposes a solution with an algorithm implemented in C at the very end of the paper. This algorithm could be implemented in a custom blend on Android. Drawable.setColorFilter() uses PorterDuffColorfilter which extends ColorFilter. Subclassing ColorFilter as discussed in this SO question would have to be done in native code using the NDK.
CMYK Color Mixing Workaround
In case you are interested in using a CMYK color mixing as a workaround, I've included a basic example of how it can be done below. In this example, A cyan-color circle and a yellow-color circle will be blended to produce a green intersection.
Starting with these .png image files created in Adobe Illustrator:
with hex color value 0x00ffff (cyan) and 0xffff00 (yellow).
Add them to your drawable folder with names cyancircle.png and yellowcircle.png.
Configure your main.xml layout as follows:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#ffffff"
android:padding="30dp">
<ImageView
android:id="#+id/bluecircle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/cyancircle">
</ImageView>
<ImageView
android:id="#+id/yellowcircle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/yellowcircle"
android:layout_marginTop="30dp">
</ImageView>
</RelativeLayout>
Create your Activity:
import android.app.Activity;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.widget.ImageView;
public class PorterDuffTestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView yellowCircle = (ImageView)findViewById(R.id.yellowcircle);
yellowCircle.getDrawable().setColorFilter(0x88ffff00, PorterDuff.Mode.MULTIPLY);
}
}
Output:
The limitation to this method is that the top shape's alpha has to be set to 50% (the "88" in "0x88ffff00"). For yellow this works reasonable well but for other colors the alpha effect may not be acceptable (the color may appear to be another color, e.g. red becomes pink with low alpha values on white background). Which blend mode is ultimately acceptable for you depends on the set of colors you are going to use and will take some experimentation. Also note the background color may affect the circles' colors in blend mode. In this example the background is set to white.
I have done another example for 6 objects.
Key Points:
onDraw method will not be override for Object Views, and background also will set to transparence
setBackgroundColor(Color.TRANSPARENT);
But, the onDraw method will be renamed as onDrawEx will be called from Overlay View.
public void onDrawEx(Canvas canvas) {
Overlay view will pass a custom canvas to draw into. before pass to object view it will do necessary translate.
mOverlayView = new View(this){
#Override
protected void onDraw(Canvas canvas) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
Canvas canvasBitmap = new Canvas(bitmap);
ViewGroup viewGroup = (ViewGroup)getParent();
for(int i = 0 ; i < viewGroup.getChildCount()-1;i++){
ObjectView objectView = (ObjectView) viewGroup.getChildAt(i);
canvasBitmap.save();
canvasBitmap.translate(objectView.getTranslationX(), objectView.getTranslationY());
objectView.onDrawEx(canvasBitmap);
canvasBitmap.restore();
}
canvas.drawBitmap(bitmap, 0, 0, new Paint());
}
};
use the mPaint.setXfermode(new PorterDuffXfermode(Mode.ADD)); to add colors. But all objects should use colors like 0xFF000030,0xFF0000C0,0xFF003000,0xFF00C000,0xFF300000,0xC00000 then only for all posible overlapping we can get different colors. this is depends on you max number of object.
int k = 0 ;
for(int i = 0 ; i < 2;i++,k++){
int color = 0xFF000000|(0x000030<<i*2);
frameLayout.addView(new ObjectView(this,color,k*50,k*50,k), new FrameLayout.LayoutParams(50, 50));
}
for(int i = 0 ; i < 2;i++,k++){
int color = 0xFF000000|(0x003000<<i*2);
frameLayout.addView(new ObjectView(this,color,k*50,k*50,k), new FrameLayout.LayoutParams(50, 50));
}
for(int i = 0 ; i < 2;i++,k++){
int color = 0xFF000000|(0x300000<<i*2);
frameLayout.addView(new ObjectView(this,color,k*50,k*50,k), new FrameLayout.LayoutParams(50, 50));
}
Here I have modified to support version 8
use
mPaint.setXfermode(new PixelXorXfermode(0x00000000));
for
mPaint.setXfermode(new PorterDuffXfermode(Mode.ADD));
I used layout parameter for translation.
The simplest solution I can think would be to simply use the alpha channel by setting each to 0.5 opacity, either in code or xml. Then the colours should fade into each other when they overlap. This does the mean the colours in the non-overlapping sections will be a little faded, though, and depending on what background you have behind the shapes it may not look good to have them be at all transparent.
I have created a sample Activity with two views
DemoDrawShapeActivity where in the view2 i use canvas.clipPath and canvas.translate
to work this i set minimum sdk version 4
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
path2.addCircle(getWidth()/2, getHeight()/2, getWidth()/2, Direction.CCW);
canvas.drawPath(path2, paint);
canvas.clipPath(path2);
canvas.save();
canvas.translate(-getTranslationX()+view1.getTranslationX(), -getTranslationY()+view1.getTranslationY());
paint.setColor(Color.BLUE|Color.RED);
canvas.drawPath(path1, paint);
canvas.restore();
}
you can edit paint.setColor(Color.BLUE|Color.RED); to get necessary color according to your logic.
i am using the setTranslationX setTranslationY to move the views.
public boolean onTouchEvent(MotionEvent event) {
switch(event.getActionMasked()){
case MotionEvent.ACTION_UP:
touched[0]=touched[1]=false;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
if(touched[1]){
view2.setTranslationX(event.getX()-view2.getWidth()/2);
view2.setTranslationY(event.getY()-view2.getHeight()/2);
}else if(touched[0]){
view1.setTranslationX(event.getX()-view1.getWidth()/2);
view1.setTranslationY(event.getY()-view1.getHeight()/2);
view2.invalidate();
}
}
return true;
}
I think you may be looking to draw in a blend mode.
Android will let you do this, just look at this first link.
Composite operations in Android Canvas
Here are all the compositing options
http://developer.android.com/reference/android/graphics/PorterDuffXfermode.html
And their explanations from Mozilla
https://developer.mozilla.org/en/Canvas_tutorial/Compositing
You may take the help from this -
http://www.learnopengles.com/android-lesson-five-an-introduction-to-blending/
and
www.pushing-pixels.org/category/android/page/6
I'm trying to create a 'glow' effect using the Android Path class. However, the gradient is not being warped to fit around the path. Instead, it is simply being display 'above' it and clipped to the path's stroke. Using a square path, the image below shows what I mean:
Instead, that should look more like this:
In other words, the gradient follows the path, and in particular wraps around the corners according to the radius set in the CornerPathEffect.
Here is the relevant part of the code:
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(30, 0, 50, 0,
new int[] {0x00000000, 0xFF0000FF, 0x00000000}, null, Shader.TileMode.MIRROR);
paint.setShader(gradient);
PathEffect cornerEffect = new CornerPathEffect(10);
paint.setPathEffect(cornerEffect);
canvas.drawPath(boxPath, paint);
Any ideas?
Another alternative is to get a 'soft-edged brush' effect when defining the stroke width. I've experimented with BlurMaskFilters, but those give a uniform blur rather than a transition from opaque to transparent. Does anyone know if that's possible?
How about drawing with a soft brush bitmap? Make a soft circular brush with opacity decreasing radially outward using image editing software like Photoshop. Save as drawable, load it in a bitmap and draw it evenly spaced along your path. Make the bitmap with white coloured brush. This way you can simply multiply the given colour(Here blue) to your bitmap using PorterDuffColorFilter.
brush1=BitmapFactory.decodeResource(getResources(), R.drawable.brush_custom_one);
//This contains radially decreasing opacity brush
porter_paint.setColorFilter(new PorterDuffColorFilter(paint.getColor(), Mode.MULTIPLY));
for (int i=1;i<matrix.size();i++) {
//matrix contains evenly spaced points along path
Point point = matrix.get(matrix.get(i));
canvas.drawBitmap(brush1, point.x,point.y, porter_paint);}
The brush used is (It's there):
The final result is:
Turns out there was a stupidly obvious way of doing this. Simply re-use the same path, and adjust the stroke width and alpha on each drawing pass. Example code:
float numberOfPasses = 20;
float maxWidth = 15;
for (float i = 0; i <= numberOfPasses; i++){
int alpha = (int) (i / numberOfPasses * 255f);
float width = maxWidth * (1 - i / numberOfPasses);
paint.setARGB(alpha, 0, 0, 255);
paint.setStrokeWidth(width);
canvas.drawPath(path, paint);
}
See below for an example of the result. The left path was drawn using this method, the right path, for comparison, is drawn in a single stroke with maxWidth and 255 alpha.
This mainly works. There are two problems:
The gradient isn't as smooth as it could be. This is because each pass being drawn over the previous one results in the alpha building up too quickly, reaching 255 before the final strokes. Experimenting a bit with the line int alpha = (int) (i / numberOfPasses * 125f); (note the change to 125f rather than 255f) helps.
The path looks like it has been 'cut' on the insides of the corners. Probably some result of the CornerPathEffect applied.
What you're wanting to do, if I understand it right, is to have the gradient effectively form a "brush" for the stroke.
This is exactly what I also was trying to achieve recently, but as far as I can tell the API doesn't provide any straightforward means to do it. I have recently created an SVG to Android Canvas converter class and so I am working a lot in Inkscape lately, too. So, when I was looking into it, I wondered if it's even possible to do it in Inkscape. However, even in Inkscape it's a very non-trivial thing to do. After some searching I eventually came across this image of a gradient being applied along the course of a path, together with a download link for a tutorial beneath:
http://www.flickr.com/photos/35772571#N03/3312087295/
What I was personally trying to do at the time was to create some semi-circles where the path is a kind of neon glow as opposed to a flat colour. Talking in terms of both the Android API and the SVG standard, it seems that the only way to to do this is to create a radial gradient that's centred perfectly on the circle, and position a series of color stops in exactly the right places. Pretty tricky to do, and I certainly don't know how you'd do it to a shape like a square.
Sorry that this is a bit of a 'I couldn't do it either' rather than a useful answer! I'll follow this with interest as I'm eager to know a solution for a kind of 'soft brush' effect too.
Can be very complicated to draw a gradient than follow a path.
So I suggest you to use some library already done than make it for you.
One can be Sc-Gauges.
Have some usefully classe than you can use for your goal.
For first include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
After create an image or what you want with a canvas where draw:
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now the code:
// Dimensions
int padding = 24;
Rect drawArea = new Rect(padding, padding, 700 - padding, 500 - padding);
// Get the main layout
ImageView imageContainer = (ImageView) this.findViewById(R.id.image);
assert imageContainer != null;
// Create a bitmap and link a canvas
Bitmap bitmap = Bitmap.createBitmap(
drawArea.width() + padding * 2, drawArea.height() + padding * 2,
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.parseColor("#f5f5f5"));
// Create the path building a bezier curve from the left-top to the right-bottom angles of
// the drawing area.
Path path = new Path();
path.moveTo(drawArea.left, drawArea.top);
path.quadTo(drawArea.centerX(), drawArea.top, drawArea.centerX(), drawArea.centerY());
path.quadTo(drawArea.centerX(), drawArea.bottom, drawArea.right, drawArea.bottom);
// Feature
ScCopier copier = new ScCopier();
copier.setPath(path);
copier.setColors(Color.RED, Color.GREEN, Color.BLUE);
copier.setWidths(20);
copier.draw(canvas);
// Add the bitmap to the container
imageContainer.setImageBitmap(bitmap);
And this the result:
The first part of the code is just for create a bitmap where draw.
What you interest is the second part where use ScCopier.
Just give the path, the color and the with.
Note than is you are inside a view you can use onDraw for draw directly on the view canvas.
This library can used to create gauge of every kind.
If you want take a look to this site ScComponents have some free and not gauges components.